Skip to content

Commit 6bcd29c

Browse files
committed
feat: disableCaseInsensitivity and forceEmailAndUsernameToLowerCase
1 parent 528690b commit 6bcd29c

File tree

5 files changed

+244
-67
lines changed

5 files changed

+244
-67
lines changed

spec/DatabaseController.spec.js

Lines changed: 197 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
const Config = require('../lib/Config');
12
const DatabaseController = require('../lib/Controllers/DatabaseController.js');
23
const validateQuery = DatabaseController._validateQuery;
34

4-
describe('DatabaseController', function () {
5-
describe('validateQuery', function () {
6-
it('should not restructure simple cases of SERVER-13732', done => {
5+
describe('DatabaseController', () => {
6+
describe('validateQuery', () => {
7+
it('should not restructure simple cases of SERVER-13732', () => {
78
const query = {
89
$or: [{ a: 1 }, { a: 2 }],
910
_rperm: { $in: ['a', 'b'] },
@@ -15,10 +16,9 @@ describe('DatabaseController', function () {
1516
_rperm: { $in: ['a', 'b'] },
1617
foo: 3,
1718
});
18-
done();
1919
});
2020

21-
it('should not restructure SERVER-13732 queries with $nears', done => {
21+
it('should not restructure SERVER-13732 queries with $nears', () => {
2222
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
2323
validateQuery(query);
2424
expect(query).toEqual({
@@ -28,10 +28,9 @@ describe('DatabaseController', function () {
2828
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
2929
validateQuery(query);
3030
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
31-
done();
3231
});
3332

34-
it('should not push refactored keys down a tree for SERVER-13732', done => {
33+
it('should not push refactored keys down a tree for SERVER-13732', () => {
3534
const query = {
3635
a: 1,
3736
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
@@ -41,22 +40,18 @@ describe('DatabaseController', function () {
4140
a: 1,
4241
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
4342
});
44-
45-
done();
4643
});
4744

48-
it('should reject invalid queries', done => {
45+
it('should reject invalid queries', () => {
4946
expect(() => validateQuery({ $or: { a: 1 } })).toThrow();
50-
done();
5147
});
5248

53-
it('should accept valid queries', done => {
49+
it('should accept valid queries', () => {
5450
expect(() => validateQuery({ $or: [{ a: 1 }, { b: 2 }] })).not.toThrow();
55-
done();
5651
});
5752
});
5853

59-
describe('addPointerPermissions', function () {
54+
describe('addPointerPermissions', () => {
6055
const CLASS_NAME = 'Foo';
6156
const USER_ID = 'userId';
6257
const ACL_GROUP = [USER_ID];
@@ -69,7 +64,7 @@ describe('DatabaseController', function () {
6964
'getExpectedType',
7065
]);
7166

72-
it('should not decorate query if no pointer CLPs are present', done => {
67+
it('should not decorate query if no pointer CLPs are present', () => {
7368
const clp = buildCLP();
7469
const query = { a: 'b' };
7570

@@ -87,11 +82,9 @@ describe('DatabaseController', function () {
8782
);
8883

8984
expect(output).toEqual({ ...query });
90-
91-
done();
9285
});
9386

94-
it('should decorate query if a pointer CLP entry is present', done => {
87+
it('should decorate query if a pointer CLP entry is present', () => {
9588
const clp = buildCLP(['user']);
9689
const query = { a: 'b' };
9790

@@ -112,11 +105,9 @@ describe('DatabaseController', function () {
112105
);
113106

114107
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
115-
116-
done();
117108
});
118109

119-
it('should decorate query if an array CLP entry is present', done => {
110+
it('should decorate query if an array CLP entry is present', () => {
120111
const clp = buildCLP(['users']);
121112
const query = { a: 'b' };
122113

@@ -140,11 +131,9 @@ describe('DatabaseController', function () {
140131
...query,
141132
users: { $all: [createUserPointer(USER_ID)] },
142133
});
143-
144-
done();
145134
});
146135

147-
it('should decorate query if an object CLP entry is present', done => {
136+
it('should decorate query if an object CLP entry is present', () => {
148137
const clp = buildCLP(['user']);
149138
const query = { a: 'b' };
150139

@@ -168,11 +157,9 @@ describe('DatabaseController', function () {
168157
...query,
169158
user: createUserPointer(USER_ID),
170159
});
171-
172-
done();
173160
});
174161

175-
it('should decorate query if a pointer CLP is present and the same field is part of the query', done => {
162+
it('should decorate query if a pointer CLP is present and the same field is part of the query', () => {
176163
const clp = buildCLP(['user']);
177164
const query = { a: 'b', user: 'a' };
178165

@@ -195,11 +182,9 @@ describe('DatabaseController', function () {
195182
expect(output).toEqual({
196183
$and: [{ user: createUserPointer(USER_ID) }, { ...query }],
197184
});
198-
199-
done();
200185
});
201186

202-
it('should transform the query to an $or query if multiple array/pointer CLPs are present', done => {
187+
it('should transform the query to an $or query if multiple array/pointer CLPs are present', () => {
203188
const clp = buildCLP(['user', 'users', 'userObject']);
204189
const query = { a: 'b' };
205190

@@ -232,11 +217,9 @@ describe('DatabaseController', function () {
232217
{ ...query, userObject: createUserPointer(USER_ID) },
233218
],
234219
});
235-
236-
done();
237220
});
238221

239-
it('should not return a $or operation if the query involves one of the two fields also used as array/pointer permissions', done => {
222+
it('should not return a $or operation if the query involves one of the two fields also used as array/pointer permissions', () => {
240223
const clp = buildCLP(['users', 'user']);
241224
const query = { a: 'b', user: createUserPointer(USER_ID) };
242225
schemaController.testPermissionsForClassName
@@ -257,10 +240,9 @@ describe('DatabaseController', function () {
257240
ACL_GROUP
258241
);
259242
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
260-
done();
261243
});
262244

263-
it('should not return a $or operation if the query involves one of the fields also used as array/pointer permissions', done => {
245+
it('should not return a $or operation if the query involves one of the fields also used as array/pointer permissions', () => {
264246
const clp = buildCLP(['user', 'users', 'userObject']);
265247
const query = { a: 'b', user: createUserPointer(USER_ID) };
266248
schemaController.testPermissionsForClassName
@@ -284,10 +266,9 @@ describe('DatabaseController', function () {
284266
ACL_GROUP
285267
);
286268
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
287-
done();
288269
});
289270

290-
it('should throw an error if for some unexpected reason the property specified in the CLP is neither a pointer nor an array', done => {
271+
it('should throw an error if for some unexpected reason the property specified in the CLP is neither a pointer nor an array', () => {
291272
const clp = buildCLP(['user']);
292273
const query = { a: 'b' };
293274

@@ -312,21 +293,18 @@ describe('DatabaseController', function () {
312293
`An unexpected condition occurred when resolving pointer permissions: ${CLASS_NAME} user`
313294
)
314295
);
315-
316-
done();
317296
});
318297
});
319298

320299
describe('reduceOperations', function () {
321300
const databaseController = new DatabaseController();
322301

323-
it('objectToEntriesStrings', done => {
302+
it('objectToEntriesStrings', () => {
324303
const output = databaseController.objectToEntriesStrings({ a: 1, b: 2, c: 3 });
325304
expect(output).toEqual(['"a":1', '"b":2', '"c":3']);
326-
done();
327305
});
328306

329-
it('reduceOrOperation', done => {
307+
it('reduceOrOperation', () => {
330308
expect(databaseController.reduceOrOperation({ a: 1 })).toEqual({ a: 1 });
331309
expect(databaseController.reduceOrOperation({ $or: [{ a: 1 }, { b: 2 }] })).toEqual({
332310
$or: [{ a: 1 }, { b: 2 }],
@@ -341,10 +319,9 @@ describe('DatabaseController', function () {
341319
expect(
342320
databaseController.reduceOrOperation({ $or: [{ b: 2 }, { a: 1, b: 2, c: 3 }] })
343321
).toEqual({ b: 2 });
344-
done();
345322
});
346323

347-
it('reduceAndOperation', done => {
324+
it('reduceAndOperation', () => {
348325
expect(databaseController.reduceAndOperation({ a: 1 })).toEqual({ a: 1 });
349326
expect(databaseController.reduceAndOperation({ $and: [{ a: 1 }, { b: 2 }] })).toEqual({
350327
$and: [{ a: 1 }, { b: 2 }],
@@ -358,7 +335,182 @@ describe('DatabaseController', function () {
358335
expect(
359336
databaseController.reduceAndOperation({ $and: [{ a: 1, b: 2, c: 3 }, { b: 2 }] })
360337
).toEqual({ a: 1, b: 2, c: 3 });
361-
done();
338+
});
339+
});
340+
341+
describe('disableCaseInsensitivity', () => {
342+
const dummyStorageAdapter = {
343+
find: () => Promise.resolve([]),
344+
watch: () => Promise.resolve(),
345+
getAllClasses: () => Promise.resolve([]),
346+
};
347+
348+
beforeEach(() => {
349+
Config.get(Parse.applicationId).schemaCache.clear();
350+
});
351+
352+
it('should force caseInsensitive to false with disableCaseInsensitivity option', async () => {
353+
const databaseController = new DatabaseController(dummyStorageAdapter, {
354+
disableCaseInsensitivity: true,
355+
});
356+
const spy = spyOn(dummyStorageAdapter, 'find');
357+
spy.and.callThrough();
358+
await databaseController.find('SomeClass', {}, { caseInsensitive: true });
359+
expect(spy.calls.all()[0].args[3].caseInsensitive).toEqual(false);
360+
});
361+
362+
it('should support caseInsensitive without disableCaseInsensitivity option', async () => {
363+
const databaseController = new DatabaseController(dummyStorageAdapter, {});
364+
const spy = spyOn(dummyStorageAdapter, 'find');
365+
spy.and.callThrough();
366+
await databaseController.find('_User', {}, { caseInsensitive: true });
367+
expect(spy.calls.all()[0].args[3].caseInsensitive).toEqual(true);
368+
});
369+
370+
it('should create insensitive indexes without disableCaseInsensitivity', async () => {
371+
await reconfigureServer({
372+
databaseURI: 'mongodb://localhost:27017/disableCaseInsensitivityFalse',
373+
databaseAdapter: undefined,
374+
});
375+
const user = new Parse.User();
376+
await user.save({ username: 'example', password: 'password', email: '[email protected]' });
377+
const schemas = await Parse.Schema.all();
378+
const UserSchema = schemas.find(({ className }) => className === '_User');
379+
expect(UserSchema.indexes).toEqual({
380+
_id_: { _id: 1 },
381+
username_1: { username: 1 },
382+
case_insensitive_username: { username: 1 },
383+
case_insensitive_email: { email: 1 },
384+
email_1: { email: 1 },
385+
});
386+
});
387+
388+
it('should not create insensitive indexes with disableCaseInsensitivity', async () => {
389+
await reconfigureServer({
390+
disableCaseInsensitivity: true,
391+
databaseURI: 'mongodb://localhost:27017/disableCaseInsensitivityTrue',
392+
databaseAdapter: undefined,
393+
});
394+
const user = new Parse.User();
395+
await user.save({ username: 'example', password: 'password', email: '[email protected]' });
396+
const schemas = await Parse.Schema.all();
397+
const UserSchema = schemas.find(({ className }) => className === '_User');
398+
expect(UserSchema.indexes).toEqual({
399+
_id_: { _id: 1 },
400+
username_1: { username: 1 },
401+
email_1: { email: 1 },
402+
});
403+
});
404+
});
405+
406+
describe('forceEmailAndUsernameToLowerCase', () => {
407+
const dummyStorageAdapter = {
408+
createObject: () => Promise.resolve({ ops: [{}] }),
409+
findOneAndUpdate: () => Promise.resolve({}),
410+
watch: () => Promise.resolve(),
411+
getAllClasses: () =>
412+
Promise.resolve([
413+
{
414+
className: '_User',
415+
fields: { username: 'String', email: 'String' },
416+
indexes: {},
417+
classLevelPermissions: { protectedFields: {} },
418+
},
419+
]),
420+
};
421+
const dates = {
422+
createdAt: { iso: undefined, __type: 'Date' },
423+
updatedAt: { iso: undefined, __type: 'Date' },
424+
};
425+
426+
it('should not force email and username to lower case without forceEmailAndUsernameToLowerCase option on create', async () => {
427+
const databaseController = new DatabaseController(dummyStorageAdapter, {});
428+
const spy = spyOn(dummyStorageAdapter, 'createObject');
429+
spy.and.callThrough();
430+
await databaseController.create('_User', {
431+
username: 'EXAMPLE',
432+
433+
});
434+
expect(spy.calls.all()[0].args[2]).toEqual({
435+
username: 'EXAMPLE',
436+
437+
...dates,
438+
});
439+
});
440+
441+
it('should force email and username to lower case with forceEmailAndUsernameToLowerCase option on create', async () => {
442+
const databaseController = new DatabaseController(dummyStorageAdapter, {
443+
forceEmailAndUsernameToLowerCase: true,
444+
});
445+
const spy = spyOn(dummyStorageAdapter, 'createObject');
446+
spy.and.callThrough();
447+
await databaseController.create('_User', {
448+
username: 'EXAMPLE',
449+
450+
});
451+
expect(spy.calls.all()[0].args[2]).toEqual({
452+
username: 'example',
453+
454+
...dates,
455+
});
456+
});
457+
458+
it('should not force email and username to lower case without forceEmailAndUsernameToLowerCase option on update', async () => {
459+
const databaseController = new DatabaseController(dummyStorageAdapter, {});
460+
const spy = spyOn(dummyStorageAdapter, 'findOneAndUpdate');
461+
spy.and.callThrough();
462+
await databaseController.update(
463+
'_User',
464+
{ id: 'example' },
465+
{ username: 'EXAMPLE', email: '[email protected]' }
466+
);
467+
expect(spy.calls.all()[0].args[3]).toEqual({
468+
username: 'EXAMPLE',
469+
470+
});
471+
});
472+
473+
it('should force email and username to lower case with forceEmailAndUsernameToLowerCase option on update', async () => {
474+
const databaseController = new DatabaseController(dummyStorageAdapter, {
475+
forceEmailAndUsernameToLowerCase: true,
476+
});
477+
const spy = spyOn(dummyStorageAdapter, 'findOneAndUpdate');
478+
spy.and.callThrough();
479+
await databaseController.update(
480+
'_User',
481+
{ id: 'example' },
482+
{ username: 'EXAMPLE', email: '[email protected]' }
483+
);
484+
expect(spy.calls.all()[0].args[3]).toEqual({
485+
username: 'example',
486+
487+
});
488+
});
489+
490+
it('should not find a case insensitive user by username or email with forceEmailAndUsernameToLowerCase', async () => {
491+
await reconfigureServer({ forceEmailAndUsernameToLowerCase: true });
492+
const user = new Parse.User();
493+
await user.save({ username: 'EXAMPLE', email: '[email protected]', password: 'password' });
494+
495+
const query = new Parse.Query(Parse.User);
496+
query.equalTo('username', 'EXAMPLE');
497+
const result = await query.find();
498+
expect(result.length).toEqual(0);
499+
500+
const query2 = new Parse.Query(Parse.User);
501+
query2.equalTo('email', '[email protected]');
502+
const result2 = await query2.find();
503+
expect(result2.length).toEqual(0);
504+
505+
const query3 = new Parse.Query(Parse.User);
506+
query3.equalTo('username', 'example');
507+
const result3 = await query3.find();
508+
expect(result3.length).toEqual(1);
509+
510+
const query4 = new Parse.Query(Parse.User);
511+
query4.equalTo('email', '[email protected]');
512+
const result4 = await query4.find();
513+
expect(result4.length).toEqual(1);
362514
});
363515
});
364516
});

0 commit comments

Comments
 (0)