Merge branch 'v1.3-dev'
[websub-hub] / test / src / link-helper.js
1 'use strict';
2
3 const assert = require('node:assert');
4 const sinon = require('sinon');
5 const LinkHelper = require('../../src/link-helper');
6 const stubLogger = require('../stub-logger');
7 const testData = require('../test-data/link-helper');
8
9 describe('LinkHelper', function () {
10 let lh, options;
11 beforeEach(function () {
12 options = {
13 dingus: {
14 selfBaseUrl: 'https://example.com/hub/',
15 },
16 };
17 lh = new LinkHelper(stubLogger, options);
18 });
19 afterEach(function () {
20 sinon.restore();
21 });
22 describe('validHub', function () {
23 let url, headers, body;
24 beforeEach(function () {
25 url = 'https://example.com/feed/';
26 headers = {};
27 body = '';
28 });
29 it('covers success', async function () {
30 headers = {
31 link: '<https://example.com/hub/>; rel="hub"',
32 };
33 const expected = true;
34 const result = await lh.validHub(url, headers, body);
35 assert.strictEqual(result, expected);
36 });
37 it('covers wrong hub', async function () {
38 headers = {
39 link: '<https://example.com/other/hub/>; rel="hub"',
40 };
41 const expected = false;
42 const result = await lh.validHub(url, headers, body);
43 assert.strictEqual(result, expected);
44 });
45 it('covers link in Atom body', async function () {
46 headers = {
47 'content-type': 'application/xml',
48 };
49 body = testData.atomFeedBody;
50 url = testData.atomFeedUrl;
51 lh.selfUrl = 'https://hub.squeep.com/';
52 const expected = true;
53 const result = await lh.validHub(url, headers, body);
54 assert.strictEqual(result, expected);
55 });
56 it('covers link in HTML body', async function () {
57 headers = {
58 'content-type': 'text/html',
59 };
60 body = '<html><head><link rel="hub" href="https://example.com/hub/"></head></html>';
61 const expected = true;
62 const result = await lh.validHub(url, headers, body);
63 assert.strictEqual(result, expected);
64 });
65 it('covers link in HTML body with charset translation', async function () {
66 headers = {
67 'content-type': 'text/html; charset=ASCII',
68 };
69 body = '<html><head><link rel="hub" href="https://example.com/hub/"></head></html>';
70 const expected = true;
71 const result = await lh.validHub(url, headers, body);
72 assert.strictEqual(result, expected);
73 });
74 it('covers parser failure', async function () {
75 headers = {
76 link: 'Invalid Link Header',
77 };
78 const expected = false;
79 const result = await lh.validHub(url, headers, body);
80 assert.strictEqual(result, expected);
81 });
82 it('covers other failure', async function () {
83 const expected = false;
84 const result = await lh.validHub(url, headers, body);
85 assert.strictEqual(result, expected);
86 });
87 }); // validHub
88
89 describe('parseContentType', function () {
90 it('handles no data', function () {
91 const expected = {
92 mediaType: 'application/octet-stream',
93 params: {},
94 };
95 const result = LinkHelper.parseContentType();
96 assert.deepStrictEqual(result, expected);
97 });
98 it('handles only media type', function () {
99 const expected = {
100 mediaType: 'application/json',
101 params: {},
102 };
103 const result = LinkHelper.parseContentType('application/json');
104 assert.deepStrictEqual(result, expected);
105 });
106 it('handles parameters', function () {
107 const expected = {
108 mediaType: 'text/html',
109 params: {
110 charset: 'ISO-8859-4',
111 },
112 };
113 const result = LinkHelper.parseContentType('text/html; charset=ISO-8859-4');
114 assert.deepStrictEqual(result, expected);
115 });
116 it('handles more parameters', function () {
117 const expected = {
118 mediaType: 'multipart/form-data',
119 params: {
120 boundary: '--123--',
121 other: 'foo',
122 },
123 };
124 const result = LinkHelper.parseContentType('multipart/form-data; boundary="--123--"; other=foo');
125 assert.deepStrictEqual(result, expected);
126 });
127 }); // parseContentType
128
129 describe('absoluteURI', function () {
130 it('success', function () {
131 const uri = '../rel';
132 const context = 'https://example.com/base/';
133 const expected = 'https://example.com/rel';
134 const result = lh.absoluteURI(uri, context);
135 assert.strictEqual(result, expected);
136 });
137 it('failure', function () {
138 const uri = '../rel';
139 const context = '/not/valid';
140 const expected = '../rel';
141 const result = lh.absoluteURI(uri, context);
142 assert.strictEqual(result, expected);
143 });
144 }); // absoluteURI
145
146 describe('locateHubTargets', function () {
147 it('covers', function () {
148 const links = [
149 {
150 target: 'https://example.com/hub1/',
151 attributes: [
152 {
153 name: 'rel',
154 value: 'hub',
155 },
156 ],
157 },
158 {
159 target: 'https://example.com/index',
160 attributes: [
161 {
162 name: 'rel',
163 value: 'index',
164 },
165 ],
166 },
167 {
168 target: 'https://example.com/hub2/',
169 attributes: [
170 {
171 name: 'rel',
172 value: 'hub other',
173 },
174 ],
175 },
176 ];
177 const expected = ['https://example.com/hub1/', 'https://example.com/hub2/'];
178 const result = LinkHelper.locateHubTargets(links);
179 assert.deepStrictEqual(result, expected);
180 });
181 }); // locateHubTargets
182
183 describe('linksFromFeedBody', function () {
184 it('parses rss', async function () {
185 const feedData = testData.rssFeedBody;
186 const feedUrl = testData.rssFeedUrl;
187 const expected = [
188 {
189 attributes: [
190 {
191 name: 'rel',
192 value: 'hub',
193 },
194 ],
195 target: 'https://hub.squeep.com/',
196 },
197 ];
198 const result = await lh.linksFromFeedBody(feedUrl, feedData);
199 assert.deepStrictEqual(result, expected);
200 });
201 it('parses more rss', async function () {
202 const feedData = testData.rssFeedBody2;
203 const feedUrl = testData.rssFeedUrl2;
204 const expected = [
205 {
206 attributes: [
207 {
208 name: 'rel',
209 value: 'self',
210 },
211 {
212 name: 'type',
213 value: 'application/rss+xml',
214 },
215 ],
216 target: 'https://puppetcircuits.wordpress.com/feed/',
217 },
218 {
219 attributes: [
220 {
221 name: 'rel',
222 value: 'search',
223 },
224 {
225 name: 'type',
226 value: 'application/opensearchdescription+xml',
227 },
228 {
229 name: 'title',
230 value: 'Puppet Circuits',
231 },
232 ],
233 target: 'https://puppetcircuits.wordpress.com/osd.xml',
234 },
235 {
236 attributes: [
237 {
238 name: 'rel',
239 value: 'hub',
240 },
241 ],
242 target: 'https://puppetcircuits.wordpress.com/?pushpress=hub',
243 },
244 ];
245 const result = await lh.linksFromFeedBody(feedUrl, feedData);
246 assert.deepStrictEqual(result, expected);
247 });
248 it('parses atom', async function () {
249 const feedData = testData.atomFeedBody;
250 const feedUrl = testData.atomFeedUrl;
251 const expected = [
252 {
253 attributes: [
254 {
255 name: 'rel',
256 value: 'alternate',
257 },
258 {
259 name: 'type',
260 value: 'text/xhtml',
261 },
262 ],
263 target: 'https://squeep.com/eats/',
264 },
265 {
266 attributes: [
267 {
268 name: 'rel',
269 value: 'self',
270 },
271 {
272 name: 'type',
273 value: 'application/atom+xml',
274 },
275 ],
276 target: 'https://squeep.com/eats/atom/',
277 },
278 {
279 attributes: [
280 {
281 name: 'rel',
282 value: 'hub',
283 },
284 ],
285 target: 'https://hub.squeep.com/',
286 },
287 ];
288 const result = await lh.linksFromFeedBody(feedUrl, feedData);
289 assert.deepStrictEqual(result, expected);
290 });
291 it('does not parse HTML', async function () {
292 const feedData = testData.htmlBody;
293 const feedUrl = testData.htmlUrl;
294 const expected = [];
295 const result = await lh.linksFromFeedBody(feedUrl, feedData);
296 assert.deepStrictEqual(result, expected);
297 });
298 }); // hubLinksFromFeedBody
299
300 describe('linksFromHTMLBody', function () {
301 it('parses HTML', function () {
302 const htmlData = testData.htmlBody;
303 const expected = [
304 {
305 attributes: [
306 {
307 name: 'rel',
308 value: 'preload',
309 },
310 {
311 name: 'as',
312 value: 'font',
313 },
314 {
315 name: 'type',
316 value: 'font/opentype',
317 },
318 {
319 name: 'crossorigin',
320 value: 'anonymous',
321 },
322 ],
323 target: 'oldstyle.otf',
324 },
325 {
326 attributes: [
327 {
328 name: 'rel',
329 value: 'stylesheet',
330 },
331 {
332 name: 'type',
333 value: 'text/css',
334 },
335 ],
336 target: 'eats.css',
337 },
338 {
339 attributes: [
340 {
341 name: 'rel',
342 value: 'hub',
343 },
344 ],
345 target: 'https://hub.squeep.com/',
346 },
347 {
348 attributes: [
349 {
350 name: 'rel',
351 value: 'alternate',
352 },
353 {
354 name: 'type',
355 value: 'application/atom+xml',
356 },
357 {
358 name: 'title',
359 value: 'Atom 1.0',
360 },
361 ],
362 target: 'https://squeep.com/eats/atom/',
363 },
364 ];
365 const result = lh.linksFromHTMLBody(htmlData);
366 assert.deepStrictEqual(result, expected);
367 });
368 }); // linksFromHTMLBody
369
370 }); // LinkHelper