Files
kami-parse-server/spec/DatabaseController.spec.js
Pedro Diaz c46e8a525d Optimize redundant logic used in queries (#7061)
* Optimize redundant logic used in queries

* Added CHANGELOG

* Fixed comments and code style after recommendations.

* Fixed code style after recommendation.

* Improved explanation in comments

* Added tests to for logic optimizations

* Added two test cases more and some comments

* Added extra test cases and fixed issue found with them.

* Removed empty lines as requested.

Co-authored-by: Pedro Diaz <p.diaz@wemersive.com>
2020-12-15 23:41:14 -06:00

393 lines
12 KiB
JavaScript

const DatabaseController = require('../lib/Controllers/DatabaseController.js');
const validateQuery = DatabaseController._validateQuery;
describe('DatabaseController', function () {
describe('validateQuery', function () {
it('should not restructure simple cases of SERVER-13732', done => {
const query = {
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
};
validateQuery(query);
expect(query).toEqual({
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
});
done();
});
it('should not restructure SERVER-13732 queries with $nears', done => {
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
validateQuery(query);
expect(query).toEqual({
$or: [{ a: 1 }, { b: 1 }],
c: { $nearSphere: {} },
});
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
validateQuery(query);
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
done();
});
it('should not push refactored keys down a tree for SERVER-13732', done => {
const query = {
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
};
validateQuery(query);
expect(query).toEqual({
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
});
done();
});
it('should reject invalid queries', done => {
expect(() => validateQuery({ $or: { a: 1 } })).toThrow();
done();
});
it('should accept valid queries', done => {
expect(() => validateQuery({ $or: [{ a: 1 }, { b: 2 }] })).not.toThrow();
done();
});
});
describe('addPointerPermissions', function () {
const CLASS_NAME = 'Foo';
const USER_ID = 'userId';
const ACL_GROUP = [USER_ID];
const OPERATION = 'find';
const databaseController = new DatabaseController();
const schemaController = jasmine.createSpyObj('SchemaController', [
'testPermissionsForClassName',
'getClassLevelPermissions',
'getExpectedType',
]);
it('should not decorate query if no pointer CLPs are present', done => {
const clp = buildCLP();
const query = { a: 'b' };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(true);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({ ...query });
done();
});
it('should decorate query if a pointer CLP entry is present', done => {
const clp = buildCLP(['user']);
const query = { a: 'b' };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'user')
.and.returnValue({ type: 'Pointer' });
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
done();
});
it('should decorate query if an array CLP entry is present', done => {
const clp = buildCLP(['users']);
const query = { a: 'b' };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'users')
.and.returnValue({ type: 'Array' });
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({
...query,
users: { $all: [createUserPointer(USER_ID)] },
});
done();
});
it('should decorate query if an object CLP entry is present', done => {
const clp = buildCLP(['user']);
const query = { a: 'b' };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'user')
.and.returnValue({ type: 'Object' });
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({
...query,
user: createUserPointer(USER_ID),
});
done();
});
it('should decorate query if a pointer CLP is present and the same field is part of the query', done => {
const clp = buildCLP(['user']);
const query = { a: 'b', user: 'a' };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'user')
.and.returnValue({ type: 'Pointer' });
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({
$and: [{ user: createUserPointer(USER_ID) }, { ...query }],
});
done();
});
it('should transform the query to an $or query if multiple array/pointer CLPs are present', done => {
const clp = buildCLP(['user', 'users', 'userObject']);
const query = { a: 'b' };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'user')
.and.returnValue({ type: 'Pointer' });
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'users')
.and.returnValue({ type: 'Array' });
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'userObject')
.and.returnValue({ type: 'Object' });
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({
$or: [
{ ...query, user: createUserPointer(USER_ID) },
{ ...query, users: { $all: [createUserPointer(USER_ID)] } },
{ ...query, userObject: createUserPointer(USER_ID) },
],
});
done();
});
it('should not return a $or operation if the query involves one of the two fields also used as array/pointer permissions', done => {
const clp = buildCLP(['users', 'user']);
const query = { a: 'b', user: createUserPointer(USER_ID) };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'user')
.and.returnValue({ type: 'Pointer' });
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'users')
.and.returnValue({ type: 'Array' });
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
done();
});
it('should not return a $or operation if the query involves one of the fields also used as array/pointer permissions', done => {
const clp = buildCLP(['user', 'users', 'userObject']);
const query = { a: 'b', user: createUserPointer(USER_ID) };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'user')
.and.returnValue({ type: 'Pointer' });
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'users')
.and.returnValue({ type: 'Array' });
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'userObject')
.and.returnValue({ type: 'Object' });
const output = databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
done();
});
it('should throw an error if for some unexpected reason the property specified in the CLP is neither a pointer nor an array', done => {
const clp = buildCLP(['user']);
const query = { a: 'b' };
schemaController.testPermissionsForClassName
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
.and.returnValue(false);
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
schemaController.getExpectedType
.withArgs(CLASS_NAME, 'user')
.and.returnValue({ type: 'Number' });
expect(() => {
databaseController.addPointerPermissions(
schemaController,
CLASS_NAME,
OPERATION,
query,
ACL_GROUP
);
}).toThrow(
Error(
`An unexpected condition occurred when resolving pointer permissions: ${CLASS_NAME} user`
)
);
done();
});
});
describe('reduceOperations', function () {
const databaseController = new DatabaseController();
it('objectToEntriesStrings', done => {
const output = databaseController.objectToEntriesStrings({ a: 1, b: 2, c: 3 });
expect(output).toEqual(['"a":1', '"b":2', '"c":3']);
done();
});
it('reduceOrOperation', done => {
expect(databaseController.reduceOrOperation({ a: 1 })).toEqual({ a: 1 });
expect(databaseController.reduceOrOperation({ $or: [{ a: 1 }, { b: 2 }] })).toEqual({
$or: [{ a: 1 }, { b: 2 }],
});
expect(databaseController.reduceOrOperation({ $or: [{ a: 1 }, { a: 2 }] })).toEqual({
$or: [{ a: 1 }, { a: 2 }],
});
expect(databaseController.reduceOrOperation({ $or: [{ a: 1 }, { a: 1 }] })).toEqual({ a: 1 });
expect(
databaseController.reduceOrOperation({ $or: [{ a: 1, b: 2, c: 3 }, { a: 1 }] })
).toEqual({ a: 1 });
expect(
databaseController.reduceOrOperation({ $or: [{ b: 2 }, { a: 1, b: 2, c: 3 }] })
).toEqual({ b: 2 });
done();
});
it('reduceAndOperation', done => {
expect(databaseController.reduceAndOperation({ a: 1 })).toEqual({ a: 1 });
expect(databaseController.reduceAndOperation({ $and: [{ a: 1 }, { b: 2 }] })).toEqual({
$and: [{ a: 1 }, { b: 2 }],
});
expect(databaseController.reduceAndOperation({ $and: [{ a: 1 }, { a: 2 }] })).toEqual({
$and: [{ a: 1 }, { a: 2 }],
});
expect(databaseController.reduceAndOperation({ $and: [{ a: 1 }, { a: 1 }] })).toEqual({
a: 1,
});
expect(
databaseController.reduceAndOperation({ $and: [{ a: 1, b: 2, c: 3 }, { b: 2 }] })
).toEqual({ a: 1, b: 2, c: 3 });
done();
});
});
});
function buildCLP(pointerNames) {
const OPERATIONS = ['count', 'find', 'get', 'create', 'update', 'delete', 'addField'];
const clp = OPERATIONS.reduce((acc, op) => {
acc[op] = {};
if (pointerNames && pointerNames.length) {
acc[op].pointerFields = pointerNames;
}
return acc;
}, {});
clp.protectedFields = {};
clp.writeUserFields = [];
clp.readUserFields = [];
return clp;
}
function createUserPointer(userId) {
return {
__type: 'Pointer',
className: '_User',
objectId: userId,
};
}