diff --git a/packages/nodes-base/nodes/RssFeedRead/RssFeedReadTrigger.node.ts b/packages/nodes-base/nodes/RssFeedRead/RssFeedReadTrigger.node.ts index b956a4e18d226..a0607f3b868b3 100644 --- a/packages/nodes-base/nodes/RssFeedRead/RssFeedReadTrigger.node.ts +++ b/packages/nodes-base/nodes/RssFeedRead/RssFeedReadTrigger.node.ts @@ -9,6 +9,11 @@ import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import Parser from 'rss-parser'; import moment from 'moment-timezone'; +interface PollData { + lastItemDate?: string; + lastTimeChecked?: string; +} + export class RssFeedReadTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'RSS Feed Trigger', @@ -39,12 +44,12 @@ export class RssFeedReadTrigger implements INodeType { }; async poll(this: IPollFunctions): Promise { - const pollData = this.getWorkflowStaticData('node'); + const pollData = this.getWorkflowStaticData('node') as PollData; const feedUrl = this.getNodeParameter('feedUrl') as string; - const now = moment().utc().format(); - const dateToCheck = - (pollData.lastItemDate as string) || (pollData.lastTimeChecked as string) || now; + const dateToCheck = Date.parse( + pollData.lastItemDate ?? pollData.lastTimeChecked ?? moment().utc().format(), + ); if (!feedUrl) { throw new NodeOperationError(this.getNode(), 'The parameter "URL" has to be set!'); @@ -73,14 +78,15 @@ export class RssFeedReadTrigger implements INodeType { return [this.helpers.returnJsonArray(feed.items[0])]; } feed.items.forEach((item) => { - if (Date.parse(item.isoDate as string) > Date.parse(dateToCheck)) { + if (Date.parse(item.isoDate as string) > dateToCheck) { returnData.push(item); } }); - const maxIsoDate = feed.items.reduce((a, b) => - new Date(a.isoDate as string) > new Date(b.isoDate as string) ? a : b, - ).isoDate; - pollData.lastItemDate = maxIsoDate; + + if (feed.items.length) { + const maxIsoDate = Math.max(...feed.items.map(({ isoDate }) => Date.parse(isoDate!))); + pollData.lastItemDate = new Date(maxIsoDate).toISOString(); + } } if (Array.isArray(returnData) && returnData.length !== 0) { diff --git a/packages/nodes-base/nodes/RssFeedRead/test/RssFeedRead.test.ts b/packages/nodes-base/nodes/RssFeedRead/test/RssFeedRead.test.ts new file mode 100644 index 0000000000000..0f7b13fbe929d --- /dev/null +++ b/packages/nodes-base/nodes/RssFeedRead/test/RssFeedRead.test.ts @@ -0,0 +1,64 @@ +import { mock } from 'jest-mock-extended'; +import type { IPollFunctions } from 'n8n-workflow'; +import Parser from 'rss-parser'; +import { returnJsonArray } from 'n8n-core'; +import { RssFeedReadTrigger } from '../RssFeedReadTrigger.node'; + +jest.mock('rss-parser'); + +const now = new Date('2024-02-01T01:23:45.678Z'); +jest.useFakeTimers({ now }); + +describe('RssFeedReadTrigger', () => { + describe('poll', () => { + const feedUrl = 'https://example.com/feed'; + const lastItemDate = '2022-01-01T00:00:00.000Z'; + const newItemDate = '2022-01-02T00:00:00.000Z'; + + const node = new RssFeedReadTrigger(); + const pollFunctions = mock({ + helpers: mock({ returnJsonArray }), + }); + + it('should throw an error if the feed URL is empty', async () => { + pollFunctions.getNodeParameter.mockReturnValue(''); + + await expect(node.poll.call(pollFunctions)).rejects.toThrowError(); + + expect(pollFunctions.getNodeParameter).toHaveBeenCalledWith('feedUrl'); + expect(Parser.prototype.parseURL).not.toHaveBeenCalled(); + }); + + it('should return new items from the feed', async () => { + const pollData = mock({ lastItemDate }); + pollFunctions.getNodeParameter.mockReturnValue(feedUrl); + pollFunctions.getWorkflowStaticData.mockReturnValue(pollData); + (Parser.prototype.parseURL as jest.Mock).mockResolvedValue({ + items: [{ isoDate: lastItemDate }, { isoDate: newItemDate }], + }); + + const result = await node.poll.call(pollFunctions); + + expect(result).toEqual([[{ json: { isoDate: newItemDate } }]]); + expect(pollFunctions.getWorkflowStaticData).toHaveBeenCalledWith('node'); + expect(pollFunctions.getNodeParameter).toHaveBeenCalledWith('feedUrl'); + expect(Parser.prototype.parseURL).toHaveBeenCalledWith(feedUrl); + expect(pollData.lastItemDate).toEqual(newItemDate); + }); + + it('should return null if the feed is empty', async () => { + const pollData = mock({ lastItemDate }); + pollFunctions.getNodeParameter.mockReturnValue(feedUrl); + pollFunctions.getWorkflowStaticData.mockReturnValue(pollData); + (Parser.prototype.parseURL as jest.Mock).mockResolvedValue({ items: [] }); + + const result = await node.poll.call(pollFunctions); + + expect(result).toEqual(null); + expect(pollFunctions.getWorkflowStaticData).toHaveBeenCalledWith('node'); + expect(pollFunctions.getNodeParameter).toHaveBeenCalledWith('feedUrl'); + expect(Parser.prototype.parseURL).toHaveBeenCalledWith(feedUrl); + expect(pollData.lastItemDate).toEqual(lastItemDate); + }); + }); +}); diff --git a/packages/nodes-base/nodes/RssFeedRead/test/node/RssFeedRead.test.ts b/packages/nodes-base/nodes/RssFeedRead/test/node/RssFeedRead.test.ts index 62806e448ca83..7238dcfa0022b 100644 --- a/packages/nodes-base/nodes/RssFeedRead/test/node/RssFeedRead.test.ts +++ b/packages/nodes-base/nodes/RssFeedRead/test/node/RssFeedRead.test.ts @@ -4,7 +4,7 @@ import { setup, equalityTest, workflowToTests, getWorkflowFilenames } from '@tes // eslint-disable-next-line n8n-local-rules/no-unneeded-backticks const feed = `<![CDATA[Lorem ipsum feed for an interval of 1 minutes with 3 item(s)]]>http://example.com/RSS for NodeThu, 09 Feb 2023 13:40:32 GMTThu, 09 Feb 2023 13:40:00 GMT1<![CDATA[Lorem ipsum 2023-02-09T13:40:00Z]]>http://example.com/test/1675950000http://example.com/test/1675950000Thu, 09 Feb 2023 13:40:00 GMT<![CDATA[Lorem ipsum 2023-02-09T13:39:00Z]]>http://example.com/test/1675949940http://example.com/test/1675949940Thu, 09 Feb 2023 13:39:00 GMT<![CDATA[Lorem ipsum 2023-02-09T13:38:00Z]]>http://example.com/test/1675949880http://example.com/test/1675949880Thu, 09 Feb 2023 13:38:00 GMT`; -describe('Test HTTP Request Node', () => { +describe('Test RSS Feed Trigger Node', () => { const workflows = getWorkflowFilenames(__dirname); const tests = workflowToTests(workflows);