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