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