f4c54a997fb26a0adcf944f208dfa1e81cb73fbf
[squeep-api-dingus] / test / lib / router.js
1 /* eslint-disable capitalized-comments */
2 /* eslint-env mocha */
3 'use strict';
4
5 const assert = require('assert');
6 const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
7 const Router = require('../../lib/router');
8 const { DingusError } = require('../../lib/errors');
9
10 const noExpectedException = 'did not get expected exception';
11
12 describe('Router', function () {
13 const router = new Router();
14 let _its; // Save and restore ignoreTrailingSlash
15
16 beforeEach(function () {
17 _its = router.ignoreTrailingSlash;
18 });
19
20 afterEach(function () {
21 sinon.restore();
22 router.ignoreTrailingSlash = _its;
23 });
24
25 describe('_pathDefinitionToPathMatch', function () {
26 it('defines a simple path', function () {
27 const p = '/a/b/c';
28 const expected = ['', 'a', 'b', 'c'];
29 expected[router.METHODS] = {};
30 const r = router._pathDefinitionToPathMatch(p);
31 assert.deepStrictEqual(r, expected);
32 });
33 it('defines a path with parameter', function () {
34 const p = '/a/b/:c';
35 const expected = ['', 'a', 'b', { [router.PARAM]: 'c' }];
36 expected[router.METHODS] = {};
37 const r = router._pathDefinitionToPathMatch(p);
38 assert.deepStrictEqual(r, expected);
39 });
40 it('defines a path with trailing slash', function () {
41 router.ignoreTrailingSlash = true;
42 const p = '/a/b/:c/';
43 const expected = ['', 'a', 'b', { [router.PARAM]: 'c' }];
44 expected[router.METHODS] = {};
45 const r = router._pathDefinitionToPathMatch(p);
46 assert.deepStrictEqual(r, expected);
47 });
48 }); // _pathDefinitionToPathMatch
49
50 describe('_pathCompareExact', function () {
51 let fixedPath, checkPath;
52
53 it('compares static paths which match', function () {
54 fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
55 checkPath = router._pathDefinitionToPathMatch('/a/b/c');
56 const r = Router._pathCompareExact(fixedPath, checkPath);
57 assert.strictEqual(r, true);
58 });
59 it('compares static paths which do not match', function () {
60 fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
61 checkPath = router._pathDefinitionToPathMatch('/a/b/d');
62 const r = Router._pathCompareExact(fixedPath, checkPath);
63 assert.strictEqual(r, false);
64 });
65 it('compares unequal static paths', function () {
66 fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
67 checkPath = router._pathDefinitionToPathMatch('/a/b');
68 const r = Router._pathCompareExact(fixedPath, checkPath);
69 assert.strictEqual(r, false);
70 });
71 it('compares param paths which match', function () {
72 fixedPath = router._pathDefinitionToPathMatch('/a/:b/c');
73 checkPath = router._pathDefinitionToPathMatch('/a/:b/c');
74 const r = Router._pathCompareExact(fixedPath, checkPath);
75 assert.strictEqual(r, true);
76 });
77 it('compares param paths which do not match', function () {
78 fixedPath = router._pathDefinitionToPathMatch('/a/:b/c');
79 checkPath = router._pathDefinitionToPathMatch('/a/:g/c');
80 const r = Router._pathCompareExact(fixedPath, checkPath);
81 assert.strictEqual(r, false);
82 });
83 }); // _pathCompareExact
84
85 describe('_pathCompareParam', function () {
86 let fixedPath, checkPath;
87
88 it('compares static paths which match', function () {
89 const params = {};
90 const expectedParams = {};
91 fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
92 checkPath = router._pathDefinitionToPathMatch('/a/b/c');
93 const r = Router._pathCompareParam(fixedPath, checkPath);
94 assert.strictEqual(r, true);
95 assert.deepStrictEqual(params, expectedParams);
96 });
97 it('compares static paths which do not match', function () {
98 const params = {};
99 const expectedParams = {};
100 fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
101 checkPath = router._pathDefinitionToPathMatch('/a/b/d');
102 const r = Router._pathCompareParam(fixedPath, checkPath, params);
103 assert.strictEqual(r, false);
104 assert.deepStrictEqual(params, expectedParams);
105 });
106 it('compares unequal static paths', function () {
107 const params = {};
108 const expectedParams = {};
109 fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
110 checkPath = router._pathDefinitionToPathMatch('/a/b');
111 const r = Router._pathCompareParam(fixedPath, checkPath, params);
112 assert.strictEqual(r, false);
113 assert.deepStrictEqual(params, expectedParams);
114 });
115 it('compares param paths which match', function () {
116 const params = {};
117 const expectedParams = {
118 b: 'bar',
119 };
120 fixedPath = router._pathDefinitionToPathMatch('/a/:b/c');
121 checkPath = router._pathDefinitionToPathMatch('/a/bar/c');
122 const r = Router._pathCompareParam(fixedPath, checkPath, params);
123 assert.strictEqual(r, true);
124 assert.deepStrictEqual(params, expectedParams);
125 });
126 it('compares multi param path which match', function () {
127 const params = {};
128 const expectedParams = {
129 b: 'gaz',
130 c: '123',
131 };
132 fixedPath = router._pathDefinitionToPathMatch('/a/:b/:c');
133 checkPath = router._pathDefinitionToPathMatch('/a/gaz/123');
134 const r = Router._pathCompareParam(fixedPath, checkPath, params);
135 assert.strictEqual(r, true);
136 assert.deepStrictEqual(params, expectedParams);
137 });
138 }); // _pathCompareParam
139
140 describe('_pathFind / _pathFindExact', function () {
141 let pathsByLengthOrig;
142
143 beforeEach(function () {
144 pathsByLengthOrig = router.pathsByLength;
145 router.pathsByLength = {
146 2: [ router._pathDefinitionToPathMatch('/:id') ],
147 3: [ router._pathDefinitionToPathMatch('/a/b') ],
148 };
149 });
150 afterEach(function () {
151 router.pathsByLength = pathsByLengthOrig;
152 });
153
154 describe('_pathFind', function () {
155 it('finds a path', function () {
156 const pathParts = ['', '46123f1e-bdca-40ee-9f62-93ad38647aa1'];
157 const { matchedPath, pathParams } = router._pathFind(pathParts);
158 assert.strictEqual(matchedPath, router.pathsByLength[2][0]);
159 assert.deepStrictEqual(pathParams, { id: pathParts[1] });
160 });
161 it('does not find a path', function () {
162 const pathParts = ['', 'flarp', 'baz'];
163 const { matchedPath } = router._pathFind(pathParts);
164 assert.strictEqual(matchedPath, undefined);
165 });
166 }); // _pathFind
167
168 describe('_pathFindExact', function () {
169 it('finds a path', function () {
170 const pathParts = ['', { [router.PARAM]: 'id' }];
171 const r = router._pathFindExact(pathParts);
172 assert.strictEqual(r, router.pathsByLength[2][0]);
173 });
174 it('does not find a path', function () {
175 const pathParts = ['', 'flarp', 'baz'];
176 const r = router._pathFindExact(pathParts);
177 assert.strictEqual(r, undefined);
178 });
179 }); // _pathFindExact
180
181 }); // _pathFind / _pathFindExact
182
183 describe('on', function () {
184 let pathsByLengthOrig;
185 const stubHandler = () => {};
186
187 beforeEach(function () {
188 pathsByLengthOrig = router.pathsByLength;
189 router.pathsByLength = {
190 2: [ router._pathDefinitionToPathMatch('/:id') ],
191 };
192 });
193 afterEach(function () {
194 router.pathsByLength = pathsByLengthOrig;
195 });
196
197 it('adds new path', function () {
198 const urlPath = '/a/:id';
199 const expected = router._pathDefinitionToPathMatch(urlPath);
200 expected[router.METHODS]['GET'] = stubHandler;
201 router.on('GET', urlPath, stubHandler);
202 assert.deepStrictEqual(router.pathsByLength[3][0], expected);
203 });
204
205 it('adds new method to path', function () {
206 const urlPath = '/a/:id';
207 const expected = router._pathDefinitionToPathMatch(urlPath);
208 expected[router.METHODS]['GET'] = stubHandler;
209 expected[router.METHODS]['POST'] = stubHandler;
210 router.on('GET', urlPath, stubHandler);
211
212 router.on('POST', urlPath, stubHandler);
213 assert.deepStrictEqual(router.pathsByLength[3][0], expected);
214 });
215
216 it('add some more paths', function () {
217 let urlPath = '/a/b/c/d';
218 const expected = router._pathDefinitionToPathMatch(urlPath);
219 expected[router.METHODS]['GET'] = stubHandler;
220 router.on('GET', urlPath, stubHandler);
221 urlPath = '/a/b/x/y';
222 router.on('GET', urlPath, stubHandler);
223
224 assert.strictEqual(router.pathsByLength[5].length, 2);
225 });
226
227 it('adds multiple methods', function () {
228 const urlPath = '/:id';
229 const expected = router._pathDefinitionToPathMatch(urlPath);
230 expected[router.METHODS]['GET'] = stubHandler;
231 expected[router.METHODS]['HEAD'] = stubHandler;
232
233 router.on(['GET', 'HEAD'], urlPath, stubHandler);
234 assert.deepStrictEqual(router.pathsByLength[2][0], expected);
235 });
236
237 it('adds new wildcard path', function () {
238 const urlPath = '/a/:id';
239 const expected = router._pathDefinitionToPathMatch(urlPath);
240 expected[router.METHODS]['*'] = stubHandler;
241 router.on('*', urlPath, stubHandler);
242 assert.deepStrictEqual(router.pathsByLength[3][0], expected);
243 });
244
245 it('fails to add unknown method path', function () {
246 const urlPath = '/a/:id';
247 try {
248 router.on('FLARP', urlPath, stubHandler);
249 assert.fail('expected an exception');
250 } catch (e) {
251 assert.strictEqual(e.name, 'DingusError');
252 assert.strictEqual(e.message, 'invalid method \'FLARP\'');
253 }
254 });
255 }); // on
256
257 describe('lookup', function () {
258 let pathsByLengthOrig;
259 let ctx;
260 let stubHandler;
261
262 beforeEach(function () {
263 ctx = {};
264 pathsByLengthOrig = router.pathsByLength;
265 stubHandler = sinon.stub();
266 });
267 afterEach(function () {
268 router.pathsByLength = pathsByLengthOrig;
269 });
270
271 it('finds handler', function () {
272 const urlPath = '/:id';
273 const method = 'GET';
274 router.on(method, urlPath, stubHandler);
275 const path = '/abc';
276
277 const handler = router.lookup(method, path, ctx);
278 assert.strictEqual(handler, stubHandler);
279 });
280 it('does not find handler with trailing slash', function () {
281 router.ignoreTrailingSlash = false;
282 const urlPath = '/:id';
283 const method = 'GET';
284 router.on(method, urlPath, stubHandler);
285 const path = '/abc/';
286
287 try {
288 router.lookup(method, path, ctx);
289 assert.fail(noExpectedException);
290 } catch (e) {
291 assert(e instanceof DingusError);
292 assert.strictEqual(e.message, 'NoPath');
293 }
294 });
295 it('finds handler', function () {
296 router.ignoreTrailingSlash = true;
297 const urlPath = '/:id';
298 const method = 'GET';
299 router.on(method, urlPath, stubHandler);
300 const path = '/abc/';
301
302 const handler = router.lookup(method, path, ctx);
303 assert.strictEqual(handler, stubHandler);
304 });
305 it('finds handler without context', async function () {
306 const urlPath = '/:id';
307 const method = 'GET';
308 router.on(method, urlPath, stubHandler);
309 const path = '/abc';
310
311 const handler = router.lookup(method, path);
312 assert.strictEqual(handler, stubHandler);
313 });
314 it('finds fallback handler', async function () {
315 const urlPath = '/abc/:id';
316 const method = 'GET';
317 router.on('*', urlPath, stubHandler);
318 const path = '/abc/def';
319
320 const handler = router.lookup(method, path, ctx);
321 assert.strictEqual(handler, stubHandler);
322 });
323 it('calls unsupported method', async function () {
324 const urlPath = '/:id';
325 const method = 'POST';
326 router.on('GET', urlPath, stubHandler);
327 const path = '/abc';
328
329 try {
330 router.lookup(method, path, ctx);
331 assert.fail(noExpectedException);
332 } catch (e) {
333 assert(e instanceof DingusError);
334 assert.strictEqual(e.message, 'NoMethod');
335 }
336 });
337 it('does not lookup nonexistant path', async function () {
338 const path = '/foo/bar';
339 const method = 'GET';
340
341 try {
342 router.lookup(method, path, ctx);
343 assert.fail(noExpectedException);
344 } catch (e) {
345 assert(e instanceof DingusError);
346 assert.strictEqual(e.message, 'NoPath');
347 }
348 });
349
350 }); // lookup
351
352 });