Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
385 changes: 385 additions & 0 deletions spec/ParseQuery.Aggregate.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,385 @@
'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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious what happens if one of the score values happens to be unset/null? I'm assuming it just omits it but would be nice to know for sure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried. If unset/null it gets ignored

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) => {
Copy link
Contributor

@montymxb montymxb Nov 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it duplicates functionality we already have in standard queries, is this needed for something in particular where the original won't do? Read through the rest, nvm this comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the user builds their aggregate queries they have the option to limit, sort, skip. This test is also there for Postgres support

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I realized that right after I noted this, you're good here.

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('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('distint query with where', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo on distint

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('distint query with where string', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo on distint again

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);
});
});
Loading