Files
kami-parse-server/spec/ParseQuery.Aggregate.spec.js
Benjamin Wilson Friedman 7944e2bd2d Remove hidden properties from aggregate responses (#4351)
* Remove hidden properties from aggregrate responses

* transform results from mongo & postgres

* Adjust ordering to comply with tests
2017-11-22 23:07:45 -08:00

458 lines
14 KiB
JavaScript

'use strict';
const Parse = require('parse/node');
const rp = require('request-promise');
const masterKeyHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'test',
'X-Parse-Master-Key': 'test'
}
const masterKeyOptions = {
headers: masterKeyHeaders,
json: true
}
const loadTestData = () => {
const data1 = {score: 10, name: 'foo', sender: {group: 'A'}, size: ['S', 'M']};
const data2 = {score: 10, name: 'foo', sender: {group: 'A'}, size: ['M', 'L']};
const data3 = {score: 10, name: 'bar', sender: {group: 'B'}, size: ['S']};
const data4 = {score: 20, name: 'dpl', sender: {group: 'B'}, size: ['S']};
const obj1 = new TestObject(data1);
const obj2 = new TestObject(data2);
const obj3 = new TestObject(data3);
const obj4 = new TestObject(data4);
return Parse.Object.saveAll([obj1, obj2, obj3, obj4]);
}
describe('Parse.Query Aggregate testing', () => {
beforeEach((done) => {
loadTestData().then(done, done);
});
it('should only query aggregate with master key', (done) => {
Parse._request('GET', `aggregate/someClass`, {})
.then(() => {}, (error) => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
});
});
it('invalid query invalid key', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
unknown: {},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.catch((error) => {
expect(error.error.code).toEqual(Parse.Error.INVALID_QUERY);
done();
});
});
it('invalid query group _id', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { _id: null },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.catch((error) => {
expect(error.error.code).toEqual(Parse.Error.INVALID_QUERY);
done();
});
});
it('invalid query group objectId required', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: {},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.catch((error) => {
expect(error.error.code).toEqual(Parse.Error.INVALID_QUERY);
done();
});
});
it('group by field', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: '$name' },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(3);
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[1].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[2].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).not.toBe(undefined);
expect(resp.results[1].objectId).not.toBe(undefined);
expect(resp.results[2].objectId).not.toBe(undefined);
done();
}).catch(done.fail);
});
it('group sum query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].total).toBe(50);
done();
}).catch(done.fail);
});
it('group count query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: 1 } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].total).toBe(4);
done();
}).catch(done.fail);
});
it('group min query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, minScore: { $min: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].minScore).toBe(10);
done();
}).catch(done.fail);
});
it('group max query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, maxScore: { $max: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].maxScore).toBe(20);
done();
}).catch(done.fail);
});
it('group avg query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, avgScore: { $avg: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].avgScore).toBe(12.5);
done();
}).catch(done.fail);
});
it('limit query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
limit: 2,
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
done();
}).catch(done.fail);
});
it('sort ascending query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
sort: { name: 1 },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(4);
expect(resp.results[0].name).toBe('bar');
expect(resp.results[1].name).toBe('dpl');
expect(resp.results[2].name).toBe('foo');
expect(resp.results[3].name).toBe('foo');
done();
}).catch(done.fail);
});
it('sort decending query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
sort: { name: -1 },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(4);
expect(resp.results[0].name).toBe('foo');
expect(resp.results[1].name).toBe('foo');
expect(resp.results[2].name).toBe('dpl');
expect(resp.results[3].name).toBe('bar');
done();
}).catch(done.fail);
});
it('skip query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
skip: 2,
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
done();
}).catch(done.fail);
});
it('match query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: { score: { $gt: 15 }},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(1);
expect(resp.results[0].score).toBe(20);
done();
}).catch(done.fail);
});
it('project query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
project: { name: 1 },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
resp.results.forEach((result) => {
expect(result.name !== undefined).toBe(true);
expect(result.sender).toBe(undefined);
expect(result.size).toBe(undefined);
expect(result.score).toBe(undefined);
});
done();
}).catch(done.fail);
});
it('project with group query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
project: { score: 1 },
group: { objectId: '$score', score: { $sum: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
resp.results.forEach((result) => {
expect(result.hasOwnProperty('objectId')).toBe(true);
expect(result.name).toBe(undefined);
expect(result.sender).toBe(undefined);
expect(result.size).toBe(undefined);
expect(result.score).not.toBe(undefined);
if (result.objectId === 10) {
expect(result.score).toBe(30);
}
if (result.objectId === 20) {
expect(result.score).toBe(20);
}
});
done();
}).catch(done.fail);
});
it('class does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/UnknownClass', options)
.then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('field does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: '$unknownfield' } },
}
});
rp.get(Parse.serverURL + '/aggregate/UnknownClass', options)
.then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('distinct query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'score' }
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
expect(resp.results.includes(10)).toBe(true);
expect(resp.results.includes(20)).toBe(true);
done();
}).catch(done.fail);
});
it('distinct query with where', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
distinct: 'score',
where: {
name: 'bar'
}
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0]).toBe(10);
done();
}).catch(done.fail);
});
it('distinct query with where string', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
distinct: 'score',
where: JSON.stringify({name:'bar'}),
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0]).toBe(10);
done();
}).catch(done.fail);
});
it('distinct nested', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'sender.group' }
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
expect(resp.results.includes('A')).toBe(true);
expect(resp.results.includes('B')).toBe(true);
done();
}).catch(done.fail);
});
it('distinct class does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'unknown' }
});
rp.get(Parse.serverURL + '/aggregate/UnknownClass', options)
.then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('distinct field does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'unknown' }
});
const obj = new TestObject();
obj.save().then(() => {
return rp.get(Parse.serverURL + '/aggregate/TestObject', options);
}).then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('distinct array', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'size' }
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(3);
expect(resp.results.includes('S')).toBe(true);
expect(resp.results.includes('M')).toBe(true);
expect(resp.results.includes('L')).toBe(true);
done();
}).catch(done.fail);
});
it('does not return sensitive hidden properties', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: {
score: {
$gt: 5
}
},
}
});
const username = 'leaky_user';
const score = 10;
const user = new Parse.User();
user.setUsername(username);
user.setPassword('password');
user.set('score', score);
user.signUp().then(function() {
return rp.get(Parse.serverURL + '/aggregate/_User', options);
}).then(function(resp) {
expect(resp.results.length).toBe(1);
const result = resp.results[0];
// verify server-side keys are not present...
expect(result._hashed_password).toBe(undefined);
expect(result._wperm).toBe(undefined);
expect(result._rperm).toBe(undefined);
expect(result._acl).toBe(undefined);
expect(result._created_at).toBe(undefined);
expect(result._updated_at).toBe(undefined);
// verify createdAt, updatedAt and others are present
expect(result.createdAt).not.toBe(undefined);
expect(result.updatedAt).not.toBe(undefined);
expect(result.objectId).not.toBe(undefined);
expect(result.username).toBe(username);
expect(result.score).toBe(score);
done();
}).catch(function(err) {
fail(err);
});
});
});