Skip to content

Commit

Permalink
Add function to check if given url is a feed url
Browse files Browse the repository at this point in the history
Return url if a feed url is passed

Add tests for isFeedUrl function

Fix failing tests

Add test for sending feed URL in router.test
  • Loading branch information
Eakam1007 committed Nov 19, 2022
1 parent 977c46f commit 011a50e
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 10 deletions.
11 changes: 10 additions & 1 deletion src/api/feed-discovery/src/middleware.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { logger, createError } = require('@senecacdot/satellite');

const { isTwitchUrl, toTwitchFeedUrl, getBlogBody, getFeedUrls } = require('./util');
const { isTwitchUrl, toTwitchFeedUrl, isFeedUrl, getBlogBody, getFeedUrls } = require('./util');

// A middleware to ensure we get an array
module.exports.checkForArray = () => {
Expand Down Expand Up @@ -43,6 +43,15 @@ module.exports.discoverFeedUrls = () => {
};
}

if (await isFeedUrl(url)) {
return [
{
feedUrl: url,
type: 'blog',
},
];
}

// Otherwise, try to parse out the feed URL from the body of the page
const body = await getBlogBody(url);
if (!body) {
Expand Down
22 changes: 22 additions & 0 deletions src/api/feed-discovery/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ const toTwitchFeedUrl = (twitchChannelUrl) => {
throw new Error('not a Twitch URL');
};

const isFeedUrl = async (url) => {
try {
const { statusCode, headers } = await got(url);
const contentType = headers['content-type'];
const validContentTypes = [
'application/xml',
'application/rss+xml',
'application/atom+xml',
'application/x.atom+xml',
'application/x-atom+xml',
'application/json',
'application/json+oembed',
'application/xml+oembed',
];

return statusCode === 200 && validContentTypes.some((ct) => contentType.includes(ct));
} catch (err) {
return false;
}
};

const getBlogBody = async (blogUrl) => {
try {
logger.debug({ blogUrl }, 'Getting blog body');
Expand Down Expand Up @@ -101,3 +122,4 @@ module.exports.getBlogBody = getBlogBody;
module.exports.getFeedUrls = getFeedUrls;
module.exports.isTwitchUrl = isTwitchUrl;
module.exports.toTwitchFeedUrl = toTwitchFeedUrl;
module.exports.isFeedUrl = isFeedUrl;
57 changes: 48 additions & 9 deletions src/api/feed-discovery/test/router.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('POST /', () => {
};

// Mocking the response body html when call GET request to blog url
nock(blogUrl).get('/').reply(200, mockBlogUrlResponseBody, {
nock(blogUrl).get('/').twice().reply(200, mockBlogUrlResponseBody, {
'Content-Type': 'text/html',
});

Expand All @@ -56,7 +56,7 @@ describe('POST /', () => {
`;

// Mocking the response body html when call GET request to blog url
nock(blogUrl1).get('/').reply(200, mockBlogUrl1ResponseBody, {
nock(blogUrl1).get('/').twice().reply(200, mockBlogUrl1ResponseBody, {
'Content-Type': 'text/html',
});

Expand All @@ -72,7 +72,7 @@ describe('POST /', () => {
`;

// Mocking the response body html when call GET request to blog url
nock(blogUrl2).get('/').reply(200, mockBlogUrl2ResponseBody, {
nock(blogUrl2).get('/').twice().reply(200, mockBlogUrl2ResponseBody, {
'Content-Type': 'text/html',
});

Expand Down Expand Up @@ -131,7 +131,7 @@ describe('POST /', () => {
</html>`;

// Mocking the response body html (send back nothing) when call GET request to blog url
nock(blogUrl).get('/').reply(200, mockBlogBody, {
nock(blogUrl).get('/').twice().reply(200, mockBlogBody, {
'Content-Type': 'text/html',
});

Expand Down Expand Up @@ -199,7 +199,7 @@ describe('POST /', () => {
};

// Mocking the response body html when call GET request to blog url
nock(blogUrl).get('/').reply(200, mockBlogUrlResponseBody, {
nock(blogUrl).get('/').twice().reply(200, mockBlogUrlResponseBody, {
'Content-Type': 'text/html',
});

Expand Down Expand Up @@ -227,7 +227,7 @@ describe('POST /', () => {
};

// Mocking the response body html when call GET request to blog url
nock(blogUrl).get('/').reply(200, mockBlogUrlResponseBody, {
nock(blogUrl).get('/').twice().reply(200, mockBlogUrlResponseBody, {
'Content-Type': 'text/html',
});

Expand Down Expand Up @@ -255,7 +255,7 @@ describe('POST /', () => {
};

// Mocking the response body html when call GET request to blog url
nock(blogUrl).get('/').reply(200, mockBlogUrlResponseBody, {
nock(blogUrl).get('/').twice().reply(200, mockBlogUrlResponseBody, {
'Content-Type': 'text/html',
});

Expand Down Expand Up @@ -294,7 +294,7 @@ describe('POST /', () => {
};

// Mocking the response body html when call GET request to blog url
nock(blogUrl).get('/').reply(200, mockBlogUrlResponseBody, {
nock(blogUrl).get('/').twice().reply(200, mockBlogUrlResponseBody, {
'Content-Type': 'text/html',
});

Expand Down Expand Up @@ -329,7 +329,7 @@ describe('POST /', () => {
};

// Mocking the response body html when call GET request to blog url
nock(youTubeDomain).get(channelUri).reply(200, mockYouTubeChannelUrlResponseBody, {
nock(youTubeDomain).get(channelUri).twice().reply(200, mockYouTubeChannelUrlResponseBody, {
'Content-Type': 'text/html',
});

Expand All @@ -345,6 +345,45 @@ describe('POST /', () => {
const res = await request(app).post('/').send(['https://test321.blogspot.com/']);
expect(res.status).toBe(401);
});

it('should return 200 and the url if the url response has content type "application/xml"', async () => {
const feedUrl = 'https://test321.blogspot.com/feed/user/';

const validContentTypes = [
'application/xml',
'application/rss+xml',
'application/atom+xml',
'application/x.atom+xml',
'application/x-atom+xml',
'application/json',
'application/json+oembed',
'application/xml+oembed',
];

const result = {
feedUrls: [
{
feedUrl: feedUrl,
type: 'blog',
},
],
};

validContentTypes.forEach((type) => {
nock(feedUrl).get('/').reply(200, undefined, { 'Content-Type': 'application/xml' });
});

// Test for all content Types
for (let i = 0; i < validContentTypes.length; i++) {
const res = await request(app)
.post('/')
.set('Authorization', `bearer ${createServiceToken()}`)
.send([feedUrl]);

expect(res.status).toBe(200);
expect(res.body).toEqual(result);
}
});
});

it('should return 200 and 1 Atom feed url using RSS Bridge if the link is from a Twitch channel', async () => {
Expand Down
39 changes: 39 additions & 0 deletions src/api/feed-discovery/test/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const nock = require('nock');
const {
isTwitchUrl,
toTwitchFeedUrl,
isFeedUrl,
getBlogBody,
getFeedUrlType,
getFeedUrls,
Expand Down Expand Up @@ -31,6 +32,44 @@ describe('util.js', () => {
);
});

test('isFeedUrl returns true for a feed url', () => {
const feedUrl = 'https://blog.com/feed/user/';

[
'application/xml',
'application/rss+xml',
'application/atom+xml',
'application/x.atom+xml',
'application/x-atom+xml',
'application/json',
'application/json+oembed',
'application/xml+oembed',
].forEach(async (type) => {
nock(feedUrl).get('/').reply(200, undefined, { 'Content-Type': type });
expect(await isFeedUrl(feedUrl)).toBe(true);
});
});

test('isFeedUrl returns false when given URL returns a non 200 status', async () => {
const feedUrl = 'https://blog.com/feed/user/';
nock(feedUrl).get('/').reply(404, 'Not Found');

expect(await isFeedUrl(feedUrl)).toBe(false);
});

test('isFeedUrl returns false if given URL returns a non feed content type', async () => {
const feedUrl = 'https://blog.com/user/';
nock(feedUrl).get('/').reply(200, '<html></html>', { 'Content-Type': 'text/html' });

expect(await isFeedUrl(feedUrl)).toBe(false);
});

test('isFeedUrl returns false if given an invalid URL is given', async () => {
const feedUrl = 'Not a URL';

expect(await isFeedUrl(feedUrl)).toBe(false);
});

test('getBlogBody returns the expected body for a given URL', async () => {
const blogUrl = 'https://test321.blogspot.com/';
const mockBlogUrlResponseBody = `
Expand Down

0 comments on commit 011a50e

Please sign in to comment.