initial release
[squeep-web-linking] / test / lib / rfc8288-web-linking.js
1 /* eslint-env mocha */
2 'use strict';
3
4 const assert = require('assert');
5
6 describe('RFC 8288 Header parser', function () {
7 const { parse, SyntaxError: PEGSyntaxError } = require('../../lib/rfc8288-web-linking');
8
9 function checkParser(linkHeader, expectedResult) {
10 const result = parse(linkHeader.replace(/\r?\n|\r/gm, ''));
11 assert.deepStrictEqual(result, expectedResult);
12 }
13
14 describe('§3.3 Relation Type', function () {
15 it('Must ignore occurrences after the first', function () {
16 const linkHeader = [
17 '<http://example.org/>;',
18 'rel="start"; rel="ignore"; foo',
19 ].join('\n');
20 const expectedResult = [
21 {
22 target: 'http://example.org/',
23 attributes: [
24 {
25 extended: false,
26 name: 'rel',
27 value: 'start',
28 },
29 {
30 extended: false,
31 name: 'foo',
32 value: null,
33 },
34 ],
35 },
36 ];
37 checkParser(linkHeader, expectedResult);
38 });
39 });
40
41 describe('§3.5 Link Header Field Examples', function () {
42 it('a', function () {
43 const linkHeader = [
44 '<http://example.com/TheBook/chapter2>; rel="previous";',
45 'title="previous chapter"',
46 ].join('\n');
47 const expectedResult = [
48 {
49 target: 'http://example.com/TheBook/chapter2',
50 attributes: [
51 {
52 extended: false,
53 name: 'rel',
54 value: 'previous',
55 },
56 {
57 extended: false,
58 name: 'title',
59 value: 'previous chapter',
60 },
61 ],
62 },
63 ];
64 checkParser(linkHeader, expectedResult);
65 });
66 it('b', function () {
67 const linkHeader = '</>; rel="http://example.net/foo"';
68 const expectedResult = [
69 {
70 target: '/',
71 attributes: [
72 {
73 extended: false,
74 name: 'rel',
75 value: 'http://example.net/foo',
76 },
77 ],
78 },
79 ];
80 checkParser(linkHeader, expectedResult);
81 });
82 it('c', function () {
83 const linkHeader = '</terms>; rel="copyright"; anchor="#foo"';
84 const expectedResult = [
85 {
86 target: '/terms',
87 attributes: [
88 {
89 extended: false,
90 name: 'rel',
91 value: 'copyright',
92 },
93 {
94 extended: false,
95 name: 'anchor',
96 value: '#foo',
97 },
98 ],
99 },
100 ];
101 checkParser(linkHeader, expectedResult);
102 });
103 it('d', function () {
104 const linkHeader = [
105 '</TheBook/chapter2>;',
106 'rel="previous"; title*=UTF-8\'de\'letztes%20Kapitel,',
107 '</TheBook/chapter4>;',
108 'rel="next"; title*=UTF-8\'de\'n%c3%a4chstes%20Kapitel',
109 ].join('\n');
110 const expectedResult = [
111 {
112 target: '/TheBook/chapter2',
113 attributes: [
114 {
115 extended: false,
116 name: 'rel',
117 value: 'previous',
118 },
119 {
120 extended: true,
121 name: 'title*',
122 value: 'UTF-8\'de\'letztes%20Kapitel',
123 },
124 ],
125 },
126 {
127 target: '/TheBook/chapter4',
128 attributes: [
129 {
130 extended: false,
131 name: 'rel',
132 value: 'next',
133 },
134 {
135 extended: true,
136 name: 'title*',
137 value: 'UTF-8\'de\'n%c3%a4chstes%20Kapitel',
138 },
139 ],
140 },
141 ];
142 checkParser(linkHeader, expectedResult);
143 });
144 it('e', function () {
145 const linkHeader = [
146 '<http://example.org/>;',
147 'rel="start http://example.net/relation/other"',
148 ].join('\n');
149 const expectedResult = [
150 {
151 target: 'http://example.org/',
152 attributes: [
153 {
154 extended: false,
155 name: 'rel',
156 value: 'start http://example.net/relation/other',
157 },
158 ],
159 },
160 ];
161 checkParser(linkHeader, expectedResult);
162 });
163 it('f', function () {
164 const linkHeader = [
165 '<https://example.org/>; rel="start",',
166 ' <https://example.org/index>; rel="index"',
167 ].join('\n');
168 const expectedResult = [
169 {
170 target: 'https://example.org/',
171 attributes: [
172 {
173 extended: false,
174 name: 'rel',
175 value: 'start',
176 },
177 ],
178 },
179 {
180 target: 'https://example.org/index',
181 attributes: [
182 {
183 extended: false,
184 name: 'rel',
185 value: 'index',
186 },
187 ],
188 },
189 ];
190 checkParser(linkHeader, expectedResult);
191 });
192 }); // §3.5 Link Header Field Examples
193
194 describe('Unusual Cases', function () {
195 it('handles uris with semicolons', function () {
196 const linkHeader = [
197 '<http://example.com/strange;url;indeed>; rel="self";',
198 ].join('\n');
199 const expectedResult = [
200 {
201 target: 'http://example.com/strange;url;indeed',
202 attributes: [
203 {
204 extended: false,
205 name: 'rel',
206 value: 'self',
207 },
208 ],
209 },
210 ];
211 checkParser(linkHeader, expectedResult);
212 });
213 it('handles value-less attributes', function () {
214 const linkHeader = [
215 '</>; special; rel="special";',
216 ].join('\n');
217 const expectedResult = [
218 {
219 target: '/',
220 attributes: [
221 {
222 extended: false,
223 name: 'special',
224 value: null,
225 },
226 {
227 extended: false,
228 name: 'rel',
229 value: 'special',
230 },
231 ],
232 },
233 ];
234 checkParser(linkHeader, expectedResult);
235 });
236 }); // Unusual Cases
237
238 describe('Extended Values', function () {
239 it('decodes extended value', function () {
240 const extendedValue = 'UTF-8\'\'%c2%a3%20and%20%e2%82%ac%20rates';
241 const expectedResult = {
242 encoding: 'UTF-8',
243 language: null,
244 value: '£ and € rates',
245 };
246 const result = parse(extendedValue, { startRule: 'extendedValue' });
247 assert.deepStrictEqual(result, expectedResult);
248 });
249 }); // Extended Values
250
251 describe('Failures', function () {
252 it('does not parse invalid header', function () {
253 const linkHeader = 'not a link header';
254 const expectedResult = [];
255 try {
256 checkParser(linkHeader, expectedResult);
257 assert.fail('unexpected success');
258 } catch (e) {
259 assert.strictEqual(e.name, 'SyntaxError');
260 }
261 });
262 it('does not parse partial invalid header', function () {
263 const linkHeader = '<https://example.com/foo>; rel="valid", not a link header';
264 const expectedResult = [];
265 try {
266 checkParser(linkHeader, expectedResult);
267 assert.fail('unexpected success');
268 } catch (e) {
269 assert(e instanceof PEGSyntaxError);
270 assert.strictEqual(e.name, 'SyntaxError');
271 }
272 });
273 }); // Failures
274
275 });