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