68f2599791c12faf5d106656200fd23cb7c55947
[squeep-api-dingus] / test / lib / common.js
1 'use strict';
2
3 const assert = require('node:assert');
4 const sinon = require('sinon');
5 const common = require('../../lib/common');
6
7
8 describe('common', function () {
9
10 describe('generateETag', function () {
11 it('generates a tag from data', function () {
12 const expected = '"RHUvNyculE/SyROjU0LqzN0arxibrlBnazAashP8UGE"';
13 const data = 'example data';
14 const result = common.generateETag(undefined, undefined, data);
15 assert.strictEqual(result, expected);
16 });
17 it('generates a tag from data and stats', function () {
18 const expected = '"mtI0qCyqXsZVfX0Pgi+G6mQM10y9yVyi1NZejXSYttk"';
19 const stat = {
20 mtimeMs: 1600113038270.2375,
21 };
22 const data = 'example data';
23 const result = common.generateETag(undefined, stat, data);
24 assert.strictEqual(result, expected);
25 });
26 }); // generateETag
27
28 describe('get', function () {
29 const def = 'default';
30 it('covers missing obj', function () {
31 const result = common.get(undefined, undefined, def);
32 assert.strictEqual(result, def);
33 });
34 it('covers missing prop', function () {
35 const result = common.get({}, undefined, def);
36 assert.strictEqual(result, def);
37 });
38 it('covers default', function () {
39 const result = common.get({ k: 'v' }, 'x', def);
40 assert.strictEqual(result, def);
41 });
42 it('covers prop', function () {
43 const result = common.get({ k: 'v' }, 'k', def);
44 assert.strictEqual(result, 'v');
45 });
46 }); // get
47
48 describe('isClientCached', function () {
49 let req, modifiedTimeMs, eTag;
50 const blueMoon = 'Sat, 31 Oct 2020 07:51:00 GMT';
51 const blueMoonMs = 1604130660000;
52 const oneDayMs = 86400000;
53 const anotherETag = '"RHUvNyculE/SyROjU0LqzN0arxibrlBnazAashP8UGE"';
54 beforeEach(function () {
55 req = {
56 getHeader: sinon.stub(),
57 };
58 modifiedTimeMs = 0;
59 eTag = '"mtI0qCyqXsZVfX0Pgi+G6mQM10y9yVyi1NZejXSYttk"';
60 });
61 it('no headers, not cached', function () {
62 const result = common.isClientCached(req, modifiedTimeMs, eTag);
63 assert.strictEqual(result, false);
64 });
65 it('modified header, not cached', function () {
66 req.getHeader.onCall(0).returns(blueMoon);
67 modifiedTimeMs = blueMoonMs + oneDayMs;
68 const result = common.isClientCached(req, modifiedTimeMs, eTag);
69 assert.strictEqual(result, false);
70 });
71 it('modified header, cached', function () {
72 req.getHeader.onCall(0).returns(blueMoon);
73 modifiedTimeMs = blueMoonMs - oneDayMs;
74 const result = common.isClientCached(req, modifiedTimeMs, eTag);
75 assert.strictEqual(result, true);
76 });
77 it('match header, not matched', function () {
78 req.getHeader.onCall(1).returns(anotherETag);
79 const result = common.isClientCached(req, modifiedTimeMs, eTag);
80 assert.strictEqual(result, false);
81 });
82 it('match header, matched', function () {
83 req.getHeader.onCall(1).returns(`${anotherETag}, ${eTag}, ${anotherETag}`);
84 const result = common.isClientCached(req, modifiedTimeMs, eTag);
85 assert.strictEqual(result, true);
86 });
87 it('match header any, matched', function () {
88 req.getHeader.onCall(1).returns('*');
89 const result = common.isClientCached(req, modifiedTimeMs, eTag);
90 assert.strictEqual(result, true);
91 });
92 it('modified header cached, match header not matched, not cached', function () {
93 req.getHeader.onCall(0).returns(blueMoon);
94 modifiedTimeMs = blueMoonMs - oneDayMs;
95 req.getHeader.onCall(1).returns(`${anotherETag}, ${anotherETag}`);
96 const result = common.isClientCached(req, modifiedTimeMs, eTag);
97 assert.strictEqual(result, false);
98 });
99 }); // iscClientCached
100
101 describe('pick', function () {
102 it('picks', function () {
103 const srcObj = {
104 a: 1,
105 b: 2,
106 c: 3,
107 };
108 const result = common.pick(srcObj, ['a', 'c', 'd']);
109 assert.deepStrictEqual(result, {
110 'a': 1,
111 'c': 3,
112 });
113 });
114 }); // pick
115
116 describe('setOptions', function () {
117 it('sets options', function () {
118 const expected = {
119 keyOne: 'default',
120 keyTwo: 'option',
121 keyThree: 'default',
122 };
123 const target = {};
124 const defaultOptions = {
125 keyOne: 'default',
126 keyTwo: 'default',
127 keyThree: 'default',
128 };
129 const options = {
130 keyTwo: 'option',
131 keyFour: 'option',
132 };
133 common.setOptions(target, defaultOptions, options);
134 assert.deepStrictEqual(target, expected);
135 });
136 }); // setOptions
137
138 describe('splitFirst', function () {
139 it('splits', function () {
140 const expected = ['foo', 'awoo'];
141 const src = 'foo?awoo';
142 const delim = '?';
143 const fill = 'fill';
144 const result = common.splitFirst(src, delim, fill);
145 assert.deepStrictEqual(result, expected);
146 });
147 it('fills', function () {
148 const expected = ['foo', 'fill'];
149 const src = 'foo';
150 const delim = '?';
151 const fill = 'fill';
152 const result = common.splitFirst(src, delim, fill);
153 assert.deepStrictEqual(result, expected);
154 });
155 }); // splitFirst
156
157 describe('mergeEnum', function () {
158 it('merges enums', function () {
159 const origEnum = {
160 ContentType: {
161 TextHTML: 'text/html',
162 },
163 ErrorResponse: {
164 BadRequest: {
165 statusCode: 400,
166 errorMessage: 'Bad Request',
167 },
168 },
169 };
170 const newEnum = {
171 ContentType: {
172 TextPlain: 'text/plain',
173 },
174 ErrorResponse: {
175 BadResponse: {
176 statusCode: 401,
177 errorMessage: 'Unauthorized',
178 },
179 },
180 Header: {
181 Accept: 'accept',
182 },
183 Value: 'value',
184 };
185 const mergedEnum = {
186 ContentType: {
187 TextHTML: 'text/html',
188 TextPlain: 'text/plain',
189 },
190 ErrorResponse: {
191 BadRequest: {
192 statusCode: 400,
193 errorMessage: 'Bad Request',
194 },
195 BadResponse: {
196 statusCode: 401,
197 errorMessage: 'Unauthorized',
198 },
199 },
200 Header: {
201 Accept: 'accept',
202 },
203 Value: 'value',
204 };
205 const result = common.mergeEnum(origEnum, newEnum);
206 assert.deepStrictEqual(result, mergedEnum);
207 });
208 }); // mergeEnum
209
210 describe('requestId', function () {
211 it('returns a string', function () {
212 const id = common.requestId();
213 assert.strictEqual(typeof id, 'string');
214 assert(id.length > 0);
215 });
216 }); // requestId
217
218 describe('httpStatusCodeClass', function () {
219 it('works', function () {
220 for (const [statusCode, statusClassExpected] of Object.entries({
221 200: 2,
222 304: 3,
223 404: 4,
224 500: 5,
225 })) {
226 const statusClass = common.httpStatusCodeClass(statusCode);
227 assert.strictEqual(statusClass, statusClassExpected);
228 }
229 });
230 }); // ensureLoggerLevels
231
232 describe('mergeDeep', function () {
233 it('simple merge', function () {
234 const o1 = {
235 foo1: 'one',
236 };
237 const o2 = {
238 foo2: 'two',
239 };
240 const o3 = {
241 foo3: 'three',
242 };
243 const expected = {
244 foo1: 'one',
245 foo2: 'two',
246 foo3: 'three',
247 };
248 const result = common.mergeDeep(o1, o2, o3);
249 assert.deepStrictEqual(result, expected);
250 });
251 it('deep merge', function () {
252 const o1 = {
253 foo: {
254 quux: 1,
255 },
256 bar: 'baz',
257 };
258 const o2 = {
259 foo: {
260 myList: ['fancy'],
261 gleep: {
262 fraz: 2,
263 },
264 },
265 blah: 'frop',
266 };
267 const o3 = {
268 foo: {
269 myList: ['pants'],
270 gleep: {
271 troz: 'poit',
272 },
273 narf: 3,
274 },
275 bar: 'yup',
276 };
277 const expected = {
278 foo: {
279 myList: ['fancy', 'pants'],
280 gleep: {
281 fraz: 2,
282 troz: 'poit',
283 },
284 narf: 3,
285 quux: 1,
286 },
287 bar: 'yup',
288 blah: 'frop',
289 };
290 const result = common.mergeDeep(o1, o2, o3);
291 assert.deepStrictEqual(result, expected);
292 });
293 it('merged object is distinct', function () {
294 const o1 = {
295 foo: {
296 bar: 3,
297 },
298 };
299 const o2 = {
300 baz: {
301 quux: 4,
302 },
303 };
304 const expected = {
305 foo: {
306 bar: 3,
307 },
308 baz: {
309 quux: 4,
310 },
311 };
312 const result = common.mergeDeep(o1, o2);
313 assert.deepStrictEqual(result, expected);
314 const expected2 = {
315 foo: {
316 bar: 5,
317 },
318 baz: {
319 quux: 5,
320 },
321 };
322 result.foo.bar = 5;
323 result.baz.quux = 5;
324 assert.deepStrictEqual(result, expected2);
325 assert.strictEqual(o1.foo.bar, 3);
326 assert.strictEqual(o2.baz.quux, 4);
327 });
328 }); // mergeDeep
329
330 describe('unfoldHeaderLines', function () {
331 it('folds', function () {
332 const lines = [
333 'Normal-Header: some header data',
334 'Folded-Header: more data',
335 ' second line of data',
336 ' third line of data',
337 ];
338 const expected = [
339 'Normal-Header: some header data',
340 'Folded-Header: more data second line of data third line of data',
341 ];
342 const result = common.unfoldHeaderLines(lines);
343 assert.deepStrictEqual(result, expected);
344 });
345 it('covers no input', function () {
346 const lines = undefined;
347 const result = common.unfoldHeaderLines();
348 assert.deepStrictEqual(result, lines);
349 });
350 }); // unfoldHeaderLines
351
352 describe('addCookie', function () {
353 let res, name, value;
354 beforeEach(function () {
355 res = {
356 appendHeader: sinon.stub(),
357 };
358 name = 'someCookieName';
359 value = 'someCookieValue';
360 });
361 it('covers no options', function () {
362 common.addCookie(res, name, value, {});
363 assert(res.appendHeader.called);
364 });
365 it('covers all options', function () {
366 common.addCookie(res, name, value, {
367 domain: 'example.com',
368 expires: new Date(),
369 httpOnly: true,
370 maxAge: 9999999,
371 path: '/foo',
372 sameSite: 'Lax',
373 secure: true,
374 });
375 assert(res.appendHeader.called);
376 });
377 it('covers invalid expires', function () {
378 assert.throws(() => common.addCookie(res, name, value, { expires: 'never' }), TypeError);
379 });
380 it('covers invalid sameSite', function () {
381 assert.throws(() => common.addCookie(res, name, value, { sameSite: 'Whatever' }));
382 });
383 it('covers no options', function () {
384 common.addCookie(res, name, value);
385 assert(res.appendHeader.called);
386 });
387 }); // addCookie
388
389 });