1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 | const should = require('./init.js');
|
8 | const utils = require('../lib/utils');
|
9 | const ObjectID = require('bson').ObjectID;
|
10 | const fieldsToArray = utils.fieldsToArray;
|
11 | const sanitizeQuery = utils.sanitizeQuery;
|
12 | const deepMerge = utils.deepMerge;
|
13 | const rankArrayElements = utils.rankArrayElements;
|
14 | const mergeIncludes = utils.mergeIncludes;
|
15 | const sortObjectsByIds = utils.sortObjectsByIds;
|
16 | const uniq = utils.uniq;
|
17 |
|
18 | describe('util.fieldsToArray', function() {
|
19 | function sample(fields, excludeUnknown) {
|
20 | const properties = ['foo', 'bar', 'bat', 'baz'];
|
21 | return {
|
22 | expect: function(arr) {
|
23 | should.deepEqual(fieldsToArray(fields, properties, excludeUnknown), arr);
|
24 | },
|
25 | };
|
26 | }
|
27 |
|
28 | it('Turn objects and strings into an array of fields' +
|
29 | ' to include when finding models', function() {
|
30 | sample(false).expect(undefined);
|
31 | sample(null).expect(undefined);
|
32 | sample({}).expect(undefined);
|
33 | sample('foo').expect(['foo']);
|
34 | sample(['foo']).expect(['foo']);
|
35 | sample({'foo': 1}).expect(['foo']);
|
36 | sample({'bat': true}).expect(['bat']);
|
37 | sample({'bat': 0}).expect(['foo', 'bar', 'baz']);
|
38 | sample({'bat': false}).expect(['foo', 'bar', 'baz']);
|
39 | });
|
40 |
|
41 | it('should exclude unknown properties', function() {
|
42 | sample(false, true).expect(undefined);
|
43 | sample(null, true).expect(undefined);
|
44 | sample({}, true).expect(undefined);
|
45 | sample('foo', true).expect(['foo']);
|
46 | sample(['foo', 'unknown'], true).expect(['foo']);
|
47 | sample({'foo': 1, unknown: 1}, true).expect(['foo']);
|
48 | sample({'bat': true, unknown: true}, true).expect(['bat']);
|
49 | sample({'bat': 0}, true).expect(['foo', 'bar', 'baz']);
|
50 | sample({'bat': false}, true).expect(['foo', 'bar', 'baz']);
|
51 | sample({'other': false}, true).expect(['foo', 'bar', 'bat', 'baz']);
|
52 | });
|
53 | });
|
54 |
|
55 | describe('util.sanitizeQuery', function() {
|
56 | it('Remove undefined values from the query object', function() {
|
57 | const q1 = {where: {x: 1, y: undefined}};
|
58 | should.deepEqual(sanitizeQuery(q1), {where: {x: 1}});
|
59 |
|
60 | const q2 = {where: {x: 1, y: 2}};
|
61 | should.deepEqual(sanitizeQuery(q2), {where: {x: 1, y: 2}});
|
62 |
|
63 | const q3 = {where: {x: 1, y: {in: [2, undefined]}}};
|
64 | should.deepEqual(sanitizeQuery(q3), {where: {x: 1, y: {in: [2]}}});
|
65 |
|
66 | should.equal(sanitizeQuery(null), null);
|
67 |
|
68 | should.equal(sanitizeQuery(undefined), undefined);
|
69 |
|
70 | should.equal(sanitizeQuery('x'), 'x');
|
71 |
|
72 | const date = new Date();
|
73 | const q4 = {where: {x: 1, y: date}};
|
74 | should.deepEqual(sanitizeQuery(q4), {where: {x: 1, y: date}});
|
75 |
|
76 |
|
77 | let q5 = {where: {x: 1, y: undefined}};
|
78 | should.deepEqual(sanitizeQuery(q5, 'nullify'), {where: {x: 1, y: null}});
|
79 |
|
80 | q5 = {where: {x: 1, y: undefined}};
|
81 | should.deepEqual(sanitizeQuery(q5, {normalizeUndefinedInQuery: 'nullify'}), {where: {x: 1, y: null}});
|
82 |
|
83 | const q6 = {where: {x: 1, y: undefined}};
|
84 | (function() { sanitizeQuery(q6, 'throw'); }).should.throw(/`undefined` in query/);
|
85 | });
|
86 |
|
87 | it('Report errors for circular or deep query objects', function() {
|
88 | const q7 = {where: {x: 1}};
|
89 | q7.where.y = q7;
|
90 | (function() { sanitizeQuery(q7); }).should.throw(
|
91 | /The query object is circular/,
|
92 | );
|
93 |
|
94 | const q8 = {where: {and: [{and: [{and: [{and: [{and: [{and:
|
95 | [{and: [{and: [{and: [{x: 1}]}]}]}]}]}]}]}]}]}};
|
96 | (function() { sanitizeQuery(q8, {maxDepth: 12}); }).should.throw(
|
97 | /The query object exceeds maximum depth 12/,
|
98 | );
|
99 |
|
100 |
|
101 | sanitizeQuery(q8).should.eql(q8);
|
102 |
|
103 | const q9 = {where: {and: [{and: [{and: [{and: [{x: 1}]}]}]}]}};
|
104 | (function() { sanitizeQuery(q8, {maxDepth: 4}); }).should.throw(
|
105 | /The query object exceeds maximum depth 4/,
|
106 | );
|
107 | });
|
108 |
|
109 | it('Removed prohibited properties in query objects', function() {
|
110 | const q1 = {where: {secret: 'guess'}};
|
111 | sanitizeQuery(q1, {prohibitedKeys: ['secret']});
|
112 | q1.where.should.eql({});
|
113 |
|
114 | const q2 = {and: [{secret: 'guess'}, {x: 1}]};
|
115 | sanitizeQuery(q2, {prohibitedKeys: ['secret']});
|
116 | q2.should.eql({and: [{}, {x: 1}]});
|
117 | });
|
118 |
|
119 | it('should allow proper structured regexp string', () => {
|
120 | const q1 = {where: {name: {like: '^J'}}};
|
121 | sanitizeQuery(q1).should.eql({where: {name: {like: '^J'}}});
|
122 | });
|
123 |
|
124 | it('should properly sanitize regexp string operators', () => {
|
125 | const q1 = {where: {name: {like: '['}}};
|
126 | sanitizeQuery(q1).should.eql({where: {name: {like: '\\['}}});
|
127 |
|
128 | const q2 = {where: {name: {nlike: '['}}};
|
129 | sanitizeQuery(q2).should.eql({where: {name: {nlike: '\\['}}});
|
130 |
|
131 | const q3 = {where: {name: {ilike: '['}}};
|
132 | sanitizeQuery(q3).should.eql({where: {name: {ilike: '\\['}}});
|
133 |
|
134 | const q4 = {where: {name: {nilike: '['}}};
|
135 | sanitizeQuery(q4).should.eql({where: {name: {nilike: '\\['}}});
|
136 |
|
137 | const q5 = {where: {name: {regexp: '['}}};
|
138 | sanitizeQuery(q5).should.eql({where: {name: {regexp: '\\['}}});
|
139 | });
|
140 | });
|
141 |
|
142 | describe('util.parseSettings', function() {
|
143 | it('Parse a full url into a settings object', function() {
|
144 | const url = 'mongodb://x:y@localhost:27017/mydb?w=2';
|
145 | const settings = utils.parseSettings(url);
|
146 | should.equal(settings.hostname, 'localhost');
|
147 | should.equal(settings.port, 27017);
|
148 | should.equal(settings.host, 'localhost');
|
149 | should.equal(settings.user, 'x');
|
150 | should.equal(settings.password, 'y');
|
151 | should.equal(settings.database, 'mydb');
|
152 | should.equal(settings.connector, 'mongodb');
|
153 | should.equal(settings.w, '2');
|
154 | should.equal(settings.url, 'mongodb://x:y@localhost:27017/mydb?w=2');
|
155 | });
|
156 |
|
157 | it('Parse a url without auth into a settings object', function() {
|
158 | const url = 'mongodb://localhost:27017/mydb/abc?w=2';
|
159 | const settings = utils.parseSettings(url);
|
160 | should.equal(settings.hostname, 'localhost');
|
161 | should.equal(settings.port, 27017);
|
162 | should.equal(settings.host, 'localhost');
|
163 | should.equal(settings.user, undefined);
|
164 | should.equal(settings.password, undefined);
|
165 | should.equal(settings.database, 'mydb');
|
166 | should.equal(settings.connector, 'mongodb');
|
167 | should.equal(settings.w, '2');
|
168 | should.equal(settings.url, 'mongodb://localhost:27017/mydb/abc?w=2');
|
169 | });
|
170 |
|
171 | it('Parse a url with complex query into a settings object', function() {
|
172 | const url = 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB';
|
173 | const settings = utils.parseSettings(url);
|
174 | should.equal(settings.hostname, '127.0.0.1');
|
175 | should.equal(settings.port, 3306);
|
176 | should.equal(settings.host, '127.0.0.1');
|
177 | should.equal(settings.user, undefined);
|
178 | should.equal(settings.password, undefined);
|
179 | should.equal(settings.database, 'mydb');
|
180 | should.equal(settings.connector, 'mysql');
|
181 | should.equal(settings.x.a, '1');
|
182 | should.equal(settings.x.b, '2');
|
183 | should.equal(settings.engine, 'InnoDB');
|
184 | should.equal(settings.url, 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB');
|
185 | });
|
186 |
|
187 | it('Parse a Memory url without auth into a settings object', function() {
|
188 | const url = 'memory://?x=1';
|
189 | const settings = utils.parseSettings(url);
|
190 | should.equal(settings.hostname, '');
|
191 | should.equal(settings.user, undefined);
|
192 | should.equal(settings.password, undefined);
|
193 | should.equal(settings.database, undefined);
|
194 | should.equal(settings.connector, 'memory');
|
195 | should.equal(settings.x, '1');
|
196 | should.equal(settings.url, 'memory://?x=1');
|
197 | });
|
198 | });
|
199 |
|
200 | describe('util.deepMerge', function() {
|
201 | it('should deep merge objects', function() {
|
202 | const extras = {base: 'User',
|
203 | relations: {accessTokens: {model: 'accessToken', type: 'hasMany',
|
204 | foreignKey: 'userId'},
|
205 | account: {model: 'account', type: 'belongsTo'}},
|
206 | acls: [
|
207 | {accessType: '*',
|
208 | permission: 'DENY',
|
209 | principalType: 'ROLE',
|
210 | principalId: '$everyone'},
|
211 | {accessType: '*',
|
212 | permission: 'ALLOW',
|
213 | principalType: 'ROLE',
|
214 | property: 'login',
|
215 | principalId: '$everyone'},
|
216 | {permission: 'ALLOW',
|
217 | property: 'findById',
|
218 | principalType: 'ROLE',
|
219 | principalId: '$owner'},
|
220 | ]};
|
221 | const base = {strict: false,
|
222 | acls: [
|
223 | {principalType: 'ROLE',
|
224 | principalId: '$everyone',
|
225 | permission: 'ALLOW',
|
226 | property: 'create'},
|
227 | {principalType: 'ROLE',
|
228 | principalId: '$owner',
|
229 | permission: 'ALLOW',
|
230 | property: 'removeById'},
|
231 | ],
|
232 | maxTTL: 31556926,
|
233 | ttl: 1209600};
|
234 |
|
235 | const merged = deepMerge(base, extras);
|
236 |
|
237 | const expected = {strict: false,
|
238 | acls: [
|
239 | {principalType: 'ROLE',
|
240 | principalId: '$everyone',
|
241 | permission: 'ALLOW',
|
242 | property: 'create'},
|
243 | {principalType: 'ROLE',
|
244 | principalId: '$owner',
|
245 | permission: 'ALLOW',
|
246 | property: 'removeById'},
|
247 | {accessType: '*',
|
248 | permission: 'DENY',
|
249 | principalType: 'ROLE',
|
250 | principalId: '$everyone'},
|
251 | {accessType: '*',
|
252 | permission: 'ALLOW',
|
253 | principalType: 'ROLE',
|
254 | property: 'login',
|
255 | principalId: '$everyone'},
|
256 | {permission: 'ALLOW',
|
257 | property: 'findById',
|
258 | principalType: 'ROLE',
|
259 | principalId: '$owner'},
|
260 | ],
|
261 | maxTTL: 31556926,
|
262 | ttl: 1209600,
|
263 | base: 'User',
|
264 | relations: {accessTokens: {model: 'accessToken', type: 'hasMany',
|
265 | foreignKey: 'userId'},
|
266 | account: {model: 'account', type: 'belongsTo'}}};
|
267 |
|
268 | should.deepEqual(merged, expected, 'Merged objects should match the expectation');
|
269 | });
|
270 | });
|
271 |
|
272 | describe('util.rankArrayElements', function() {
|
273 | it('should add property \'__rank\' to array elements of type object {}', function() {
|
274 | const acls = [
|
275 | {accessType: '*',
|
276 | permission: 'DENY',
|
277 | principalType: 'ROLE',
|
278 | principalId: '$everyone'},
|
279 | ];
|
280 |
|
281 | const rankedAcls = rankArrayElements(acls, 2);
|
282 |
|
283 | should.equal(rankedAcls[0].__rank, 2);
|
284 | });
|
285 |
|
286 | it('should not replace existing \'__rank\' property of array elements', function() {
|
287 | const acls = [
|
288 | {accessType: '*',
|
289 | permission: 'DENY',
|
290 | principalType: 'ROLE',
|
291 | principalId: '$everyone',
|
292 | __rank: 1,
|
293 | },
|
294 | ];
|
295 |
|
296 | const rankedAcls = rankArrayElements(acls, 2);
|
297 |
|
298 | should.equal(rankedAcls[0].__rank, 1);
|
299 | });
|
300 | });
|
301 |
|
302 | describe('util.sortObjectsByIds', function() {
|
303 | const items = [
|
304 | {id: 1, name: 'a'},
|
305 | {id: 2, name: 'b'},
|
306 | {id: 3, name: 'c'},
|
307 | {id: 4, name: 'd'},
|
308 | {id: 5, name: 'e'},
|
309 | {id: 6, name: 'f'},
|
310 | ];
|
311 |
|
312 | it('should sort', function() {
|
313 | const sorted = sortObjectsByIds('id', [6, 5, 4, 3, 2, 1], items);
|
314 | const names = sorted.map(function(u) { return u.name; });
|
315 | should.deepEqual(names, ['f', 'e', 'd', 'c', 'b', 'a']);
|
316 | });
|
317 |
|
318 | it('should sort - partial ids', function() {
|
319 | const sorted = sortObjectsByIds('id', [5, 3, 2], items);
|
320 | const names = sorted.map(function(u) { return u.name; });
|
321 | should.deepEqual(names, ['e', 'c', 'b', 'a', 'd', 'f']);
|
322 | });
|
323 |
|
324 | it('should sort - strict', function() {
|
325 | const sorted = sortObjectsByIds('id', [5, 3, 2], items, true);
|
326 | const names = sorted.map(function(u) { return u.name; });
|
327 | should.deepEqual(names, ['e', 'c', 'b']);
|
328 | });
|
329 | });
|
330 |
|
331 | describe('util.mergeIncludes', function() {
|
332 | function checkInputOutput(baseInclude, updateInclude, expectedInclude) {
|
333 | const mergedInclude = mergeIncludes(baseInclude, updateInclude);
|
334 | should.deepEqual(mergedInclude, expectedInclude,
|
335 | 'Merged include should match the expectation');
|
336 | }
|
337 |
|
338 | it('Merge string values to object', function() {
|
339 | const baseInclude = 'relation1';
|
340 | const updateInclude = 'relation2';
|
341 | const expectedInclude = [
|
342 | {relation2: true},
|
343 | {relation1: true},
|
344 | ];
|
345 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
346 | });
|
347 |
|
348 | it('Merge string & array values to object', function() {
|
349 | const baseInclude = 'relation1';
|
350 | const updateInclude = ['relation2'];
|
351 | const expectedInclude = [
|
352 | {relation2: true},
|
353 | {relation1: true},
|
354 | ];
|
355 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
356 | });
|
357 |
|
358 | it('Merge string & object values to object', function() {
|
359 | const baseInclude = ['relation1'];
|
360 | const updateInclude = {relation2: 'relation2Include'};
|
361 | const expectedInclude = [
|
362 | {relation2: 'relation2Include'},
|
363 | {relation1: true},
|
364 | ];
|
365 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
366 | });
|
367 |
|
368 | it('Merge array & array values to object', function() {
|
369 | const baseInclude = ['relation1'];
|
370 | const updateInclude = ['relation2'];
|
371 | const expectedInclude = [
|
372 | {relation2: true},
|
373 | {relation1: true},
|
374 | ];
|
375 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
376 | });
|
377 |
|
378 | it('Merge array & object values to object', function() {
|
379 | const baseInclude = ['relation1'];
|
380 | const updateInclude = {relation2: 'relation2Include'};
|
381 | const expectedInclude = [
|
382 | {relation2: 'relation2Include'},
|
383 | {relation1: true},
|
384 | ];
|
385 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
386 | });
|
387 |
|
388 | it('Merge object & object values to object', function() {
|
389 | const baseInclude = {relation1: 'relation1Include'};
|
390 | const updateInclude = {relation2: 'relation2Include'};
|
391 | const expectedInclude = [
|
392 | {relation2: 'relation2Include'},
|
393 | {relation1: 'relation1Include'},
|
394 | ];
|
395 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
396 | });
|
397 |
|
398 | it('Override property collision with update value', function() {
|
399 | const baseInclude = {relation1: 'baseValue'};
|
400 | const updateInclude = {relation1: 'updateValue'};
|
401 | const expectedInclude = [
|
402 | {relation1: 'updateValue'},
|
403 | ];
|
404 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
405 | });
|
406 |
|
407 | it('Merge string includes & include with relation syntax properly',
|
408 | function() {
|
409 | const baseInclude = 'relation1';
|
410 | const updateInclude = {relation: 'relation1'};
|
411 | const expectedInclude = [
|
412 | {relation: 'relation1'},
|
413 | ];
|
414 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
415 | });
|
416 |
|
417 | it('Merge string includes & include with scope properly', function() {
|
418 | const baseInclude = 'relation1';
|
419 | const updateInclude = {
|
420 | relation: 'relation1',
|
421 | scope: {include: 'relation2'},
|
422 | };
|
423 | const expectedInclude = [
|
424 | {relation: 'relation1', scope: {include: 'relation2'}},
|
425 | ];
|
426 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
427 | });
|
428 |
|
429 | it('Merge includes with and without relation syntax properly',
|
430 | function() {
|
431 |
|
432 | let baseInclude = ['relation2'];
|
433 | let updateInclude = {
|
434 | relation: 'relation1',
|
435 | scope: {include: 'relation2'},
|
436 | };
|
437 | let expectedInclude = [{
|
438 | relation: 'relation1',
|
439 | scope: {include: 'relation2'},
|
440 | }, {relation2: true}];
|
441 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
442 |
|
443 |
|
444 | baseInclude = ['relation1'];
|
445 | updateInclude = {relation: 'relation1', scope: {include: 'relation2'}};
|
446 | expectedInclude =
|
447 | [{relation: 'relation1', scope: {include: 'relation2'}}];
|
448 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
449 |
|
450 |
|
451 | baseInclude = {relation: 'relation1', scope: {include: 'relation2'}};
|
452 | updateInclude = ['relation1'];
|
453 | expectedInclude = [{relation1: true}];
|
454 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
455 | });
|
456 |
|
457 | it('Merge includes with mixture of strings, arrays & objects properly', function() {
|
458 | const baseInclude = ['relation1', {relation2: true},
|
459 | {relation: 'relation3', scope: {where: {id: 'some id'}}},
|
460 | {relation: 'relation5', scope: {where: {id: 'some id'}}},
|
461 | ];
|
462 | const updateInclude = ['relation4', {relation3: true},
|
463 | {relation: 'relation2', scope: {where: {id: 'some id'}}}];
|
464 | const expectedInclude = [{relation4: true}, {relation3: true},
|
465 | {relation: 'relation2', scope: {where: {id: 'some id'}}},
|
466 | {relation1: true},
|
467 | {relation: 'relation5', scope: {where: {id: 'some id'}}}];
|
468 | checkInputOutput(baseInclude, updateInclude, expectedInclude);
|
469 | });
|
470 | });
|
471 |
|
472 | describe('util.uniq', function() {
|
473 | it('should dedupe an array with duplicate number entries', function() {
|
474 | const a = [1, 2, 1, 3];
|
475 | const b = uniq(a);
|
476 | b.should.eql([1, 2, 3]);
|
477 | });
|
478 |
|
479 | it('should dedupe an array with duplicate string entries', function() {
|
480 | const a = ['a', 'a', 'b', 'a'];
|
481 | const b = uniq(a);
|
482 | b.should.eql(['a', 'b']);
|
483 | });
|
484 |
|
485 | it('should dedupe an array with duplicate bson entries', function() {
|
486 | const idOne = new ObjectID('59f9ec5dc7d59a00042f7c62');
|
487 | const idTwo = new ObjectID('59f9ec5dc7d59a00042f7c63');
|
488 | const a = [idOne, idTwo, new ObjectID('59f9ec5dc7d59a00042f7c62'),
|
489 | new ObjectID('59f9ec5dc7d59a00042f7c62')];
|
490 | const b = uniq(a);
|
491 | b.should.eql([idOne, idTwo]);
|
492 | });
|
493 |
|
494 | it('should dedupe an array without duplicate number entries', function() {
|
495 | const a = [1, 3, 2];
|
496 | const b = uniq(a);
|
497 | b.should.eql([1, 3, 2]);
|
498 | });
|
499 |
|
500 | it('should dedupe an array without duplicate string entries', function() {
|
501 | const a = ['a', 'c', 'b'];
|
502 | const b = uniq(a);
|
503 | b.should.eql(['a', 'c', 'b']);
|
504 | });
|
505 |
|
506 | it('should dedupe an array without duplicate bson entries', function() {
|
507 | const idOne = new ObjectID('59f9ec5dc7d59a00042f7c62');
|
508 | const idTwo = new ObjectID('59f9ec5dc7d59a00042f7c63');
|
509 | const idThree = new ObjectID('59f9ec5dc7d59a00042f7c64');
|
510 | const a = [idOne, idTwo, idThree];
|
511 | const b = uniq(a);
|
512 | b.should.eql([idOne, idTwo, idThree]);
|
513 | });
|
514 |
|
515 | it('should allow null/undefined array', function() {
|
516 | const a = null;
|
517 | const b = uniq(a);
|
518 | b.should.eql([]);
|
519 | });
|
520 |
|
521 | it('should report error for non-array arg', function() {
|
522 | const a = '1';
|
523 | try {
|
524 | const b = uniq(a);
|
525 | throw new Error('The test should have thrown an error');
|
526 | } catch (err) {
|
527 | err.should.be.instanceof(Error);
|
528 | }
|
529 | });
|
530 | });
|
531 |
|
532 | describe('util.toRegExp', function() {
|
533 | let invalidDataTypes;
|
534 | let validDataTypes;
|
535 |
|
536 | before(function() {
|
537 | invalidDataTypes = [0, true, {}, [], Function, null];
|
538 | validDataTypes = ['string', /^regex/, new RegExp(/^regex/)];
|
539 | });
|
540 |
|
541 | it('should not accept invalid data types', function() {
|
542 | invalidDataTypes.forEach(function(invalid) {
|
543 | utils.toRegExp(invalid).should.be.an.Error;
|
544 | });
|
545 | });
|
546 |
|
547 | it('should accept valid data types', function() {
|
548 | validDataTypes.forEach(function(valid) {
|
549 | utils.toRegExp(valid).should.not.be.an.Error;
|
550 | });
|
551 | });
|
552 |
|
553 | context('with a regex string', function() {
|
554 | it('should return a RegExp object when no regex flags are provided',
|
555 | function() {
|
556 | utils.toRegExp('^regex$').should.be.an.instanceOf(RegExp);
|
557 | });
|
558 |
|
559 | it('should throw an error when invalid regex flags are provided',
|
560 | function() {
|
561 | utils.toRegExp('^regex$/abc').should.be.an.Error;
|
562 | });
|
563 |
|
564 | it('should return a RegExp object when valid flags are provided',
|
565 | function() {
|
566 | utils.toRegExp('regex/igm').should.be.an.instanceOf(RegExp);
|
567 | });
|
568 | });
|
569 |
|
570 | context('with a regex literal', function() {
|
571 | it('should return a RegExp object', function() {
|
572 | utils.toRegExp(/^regex$/igm).should.be.an.instanceOf(RegExp);
|
573 | });
|
574 | });
|
575 |
|
576 | context('with a regex object', function() {
|
577 | it('should return a RegExp object', function() {
|
578 | utils.toRegExp(new RegExp('^regex$', 'igm')).should.be.an.instanceOf(RegExp);
|
579 | });
|
580 | });
|
581 | });
|
582 |
|
583 | describe('util.hasRegExpFlags', function() {
|
584 | context('with a regex string', function() {
|
585 | it('should be true when the regex has invalid flags', function() {
|
586 | utils.hasRegExpFlags('^regex$/abc').should.be.ok;
|
587 | });
|
588 |
|
589 | it('should be true when the regex has valid flags', function() {
|
590 | utils.hasRegExpFlags('^regex$/igm').should.be.ok;
|
591 | });
|
592 |
|
593 | it('should be false when the regex has no flags', function() {
|
594 | utils.hasRegExpFlags('^regex$').should.not.be.ok;
|
595 | utils.hasRegExpFlags('^regex$/').should.not.be.ok;
|
596 | });
|
597 | });
|
598 |
|
599 | context('with a regex literal', function() {
|
600 | it('should be true when the regex has valid flags', function() {
|
601 | utils.hasRegExpFlags(/^regex$/igm).should.be.ok;
|
602 | });
|
603 |
|
604 | it('should be false when the regex has no flags', function() {
|
605 | utils.hasRegExpFlags(/^regex$/).should.not.be.ok;
|
606 | });
|
607 | });
|
608 |
|
609 | context('with a regex object', function() {
|
610 | it('should be true when the regex has valid flags', function() {
|
611 | utils.hasRegExpFlags(new RegExp(/^regex$/igm)).should.be.ok;
|
612 | });
|
613 |
|
614 | it('should be false when the regex has no flags', function() {
|
615 | utils.hasRegExpFlags(new RegExp(/^regex$/)).should.not.be.ok;
|
616 | });
|
617 | });
|
618 | });
|
619 |
|
620 | describe('util.idsHaveDuplicates', function() {
|
621 | context('with string IDs', function() {
|
622 | it('should be true with a duplicate present', function() {
|
623 | utils.idsHaveDuplicates(['a', 'b', 'a']).should.be.ok;
|
624 | });
|
625 |
|
626 | it('should be false when no duplicates are present', function() {
|
627 | utils.idsHaveDuplicates(['a', 'b', 'c']).should.not.be.ok;
|
628 | });
|
629 | });
|
630 |
|
631 | context('with numeric IDs', function() {
|
632 | it('should be true with a duplicate present', function() {
|
633 | utils.idsHaveDuplicates([1, 2, 1]).should.be.ok;
|
634 | });
|
635 |
|
636 | it('should be false when no duplicates are present', function() {
|
637 | utils.idsHaveDuplicates([1, 2, 3]).should.not.be.ok;
|
638 | });
|
639 | });
|
640 |
|
641 | context('with complex IDs', function() {
|
642 | it('should be true with a duplicate present', function() {
|
643 | utils.idsHaveDuplicates(['a', 'b', 'a'].map(id => ({id}))).should.be.ok;
|
644 | });
|
645 |
|
646 | it('should be false when no duplicates are present', function() {
|
647 | utils.idsHaveDuplicates(['a', 'b', 'c'].map(id => ({id}))).should.not.be.ok;
|
648 | });
|
649 | });
|
650 | });
|