mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-01 01:07:04 +02:00
119 lines
3.9 KiB
TypeScript
119 lines
3.9 KiB
TypeScript
import { mock } from 'jest-mock-extended';
|
|
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
import Parser from 'rss-parser';
|
|
|
|
import { parseFeedUrl } from '../GenericFunctions';
|
|
|
|
jest.mock('rss-parser');
|
|
|
|
const ParserMock = Parser as unknown as jest.Mock;
|
|
|
|
const RELAXED_ACCEPT =
|
|
'application/rss+xml, application/rdf+xml;q=0.8, application/atom+xml;q=0.6, application/xml;q=0.4, text/xml;q=0.4';
|
|
|
|
describe('parseFeedUrl', () => {
|
|
const feedUrl = 'https://example.com/feed';
|
|
const xmlBody = '<rss />';
|
|
const parsed = { items: [{ title: 'item-1' }] };
|
|
|
|
let helpers: ReturnType<typeof mock<IExecuteFunctions['helpers']>>;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
helpers = mock<IExecuteFunctions['helpers']>();
|
|
helpers.httpRequest.mockResolvedValue(xmlBody);
|
|
|
|
(Parser.prototype.parseString as jest.Mock).mockResolvedValue(parsed);
|
|
});
|
|
|
|
it('uses GET with default headers, text encoding, and no SSL skip when no options are passed', async () => {
|
|
const result = await parseFeedUrl(helpers, feedUrl);
|
|
|
|
expect(result).toBe(parsed);
|
|
|
|
expect(helpers.httpRequest).toHaveBeenCalledTimes(1);
|
|
expect(helpers.httpRequest).toHaveBeenCalledWith({
|
|
method: 'GET',
|
|
url: feedUrl,
|
|
headers: {
|
|
'User-Agent': 'rss-parser',
|
|
Accept: 'application/rss+xml',
|
|
},
|
|
json: false,
|
|
encoding: 'text',
|
|
skipSslCertificateValidation: undefined,
|
|
});
|
|
|
|
expect(Parser.prototype.parseString).toHaveBeenCalledWith(xmlBody);
|
|
});
|
|
|
|
it('switches to the relaxed Accept header when useRelaxedAcceptHeader is true', async () => {
|
|
await parseFeedUrl(helpers, feedUrl, { useRelaxedAcceptHeader: true });
|
|
|
|
expect(helpers.httpRequest).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
headers: expect.objectContaining({ Accept: RELAXED_ACCEPT }),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('passes skipSslCertificateValidation when ignoreSSL is true', async () => {
|
|
await parseFeedUrl(helpers, feedUrl, { ignoreSSL: true });
|
|
|
|
expect(helpers.httpRequest).toHaveBeenCalledWith(
|
|
expect.objectContaining({ skipSslCertificateValidation: true }),
|
|
);
|
|
});
|
|
|
|
it('splits and trims customFields before passing them to the parser', async () => {
|
|
await parseFeedUrl(helpers, feedUrl, { customFields: 'custom, dc:creator , media:content' });
|
|
|
|
expect(ParserMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
customFields: { item: ['custom', 'dc:creator', 'media:content'] },
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('does not set a customFields parser option when none are provided', async () => {
|
|
await parseFeedUrl(helpers, feedUrl);
|
|
|
|
const parserOptions = ParserMock.mock.calls.at(-1)?.[0] ?? {};
|
|
expect(parserOptions).not.toHaveProperty('customFields');
|
|
});
|
|
|
|
it('always sets the sanitizing xml2js processors on the parser', async () => {
|
|
await parseFeedUrl(helpers, feedUrl);
|
|
|
|
const parserOptions = ParserMock.mock.calls.at(-1)?.[0];
|
|
expect(parserOptions?.xml2js?.tagNameProcessors).toHaveLength(1);
|
|
expect(parserOptions?.xml2js?.attrNameProcessors).toHaveLength(1);
|
|
});
|
|
|
|
it('coerces a non-string response body to a string before parsing', async () => {
|
|
helpers.httpRequest.mockResolvedValue(Buffer.from('<rss>buf</rss>') as unknown as string);
|
|
|
|
await parseFeedUrl(helpers, feedUrl);
|
|
|
|
expect(Parser.prototype.parseString).toHaveBeenCalledWith(
|
|
expect.stringContaining('<rss>buf</rss>'),
|
|
);
|
|
});
|
|
|
|
it('propagates errors thrown by the underlying httpRequest helper', async () => {
|
|
const networkError = Object.assign(new Error('refused'), { code: 'ECONNREFUSED' });
|
|
helpers.httpRequest.mockRejectedValue(networkError);
|
|
|
|
await expect(parseFeedUrl(helpers, feedUrl)).rejects.toBe(networkError);
|
|
expect(Parser.prototype.parseString).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('propagates errors thrown by the parser', async () => {
|
|
const parseError = new Error('bad xml');
|
|
(Parser.prototype.parseString as jest.Mock).mockRejectedValue(parseError);
|
|
|
|
await expect(parseFeedUrl(helpers, feedUrl)).rejects.toBe(parseError);
|
|
});
|
|
});
|