Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
25 changes: 24 additions & 1 deletion src/parse-mockdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -596,12 +596,28 @@ function sortQueryresults(matches, order) {
return _.orderBy(matches, keys, orders);
}

/**
* Projects out specific fields in the response (the `keys` parameter
* in the REST API).
*/
function projectFields(objects, fields) {
const realFields = [
...fields.split(','),
'objectId',
'createdAt',
'updatedAt',
'ACL',
];
return objects.map(object => _.pick(object, realFields));
}

/**
* Handles a GET request (Parse.Query.find(), get(), first(), Parse.Object.fetch())
*/
function handleGetRequest(request) {
const objId = request.objectId;
const className = request.className;
const data = request.data;
if (objId) {
// Object.fetch() query
const collection = getCollection(className);
Expand All @@ -619,9 +635,12 @@ function handleGetRequest(request) {
match = _.omit(match, toOmit);
}

if (data.keys) {
match = projectFields([match], data.keys)[0];
}

Comment on lines +638 to +641

Choose a reason for hiding this comment

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

Don't you want those four default fields (ACL, objectId, createdAt, and updatedAt) even if data.keys is not provided?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If data.keys isn't provided, we want to return all the fields, so we skip this projection operation entirely 😃

Choose a reason for hiding this comment

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

Oh, wait, were they always there? And you're just including them up above so that _.pick() doesn't remove them?

Choose a reason for hiding this comment

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

Oh, got it.

return Promise.resolve(respond(200, match));
}
const data = request.data;
indirect = data.redirectClassNameForKey;
let matches = recursivelyMatch(className, data.where);
let matchesClassName = '';
Expand Down Expand Up @@ -656,6 +675,10 @@ function handleGetRequest(request) {
matches = sortQueryresults(matches, data.order);
}

if (data.keys) {
matches = projectFields(matches, data.keys);
}

const limit = data.limit || DEFAULT_LIMIT;
const startIndex = data.skip || 0;
const endIndex = startIndex + limit;
Expand Down
64 changes: 63 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ Parse.Object.registerSubclass('Store', Store);

class CustomUserSubclass extends Parse.User { }

function createBrandP(name) {
function createBrandP(name, extra) {
const brand = new Brand();
brand.set('name', name);
if (extra) {
brand.set(extra);
}
return brand.save();
}

Expand Down Expand Up @@ -1553,6 +1556,65 @@ describe('ParseMock', () => {
})
);

describe('projecting fields', () => {
let savedBrand;
const brandExtra = { type: 'utility' };
beforeEach(() => createBrandP('ACME', brandExtra).then((brand) => { savedBrand = brand; }));

let savedItem;
const itemExtra = { name: 'Dynamite', description: 'Blows up.' };
beforeEach(() => createItemP(30, savedBrand, itemExtra).then((item) => { savedItem = item; }));

it('can project when fetching a single item', () => (
new Parse.Query(Item).select('name').get(savedItem.id).then((item) => {
assert(item.id); // "id" is always projected
assert(item.createdAt); // "createdAt" is always projected
assert(item.updatedAt); // "updatedAt" is always projected
assert.equal(item.get('price'), undefined); // this attribute was set but not projected
assert.equal(item.get('name'), 'Dynamite'); // this attribute was projected
})
));

it('can project a single field passed as a string', () => (
new Parse.Query(Item).select('name').find().then((items) => {
assert.equal(items.length, 1);
const item = items[0];
assert(item.id); // "id" is always projected
assert(item.createdAt); // "createdAt" is always projected
assert(item.updatedAt); // "updatedAt" is always projected
assert.equal(item.get('price'), undefined); // this attribute was set but not projected
assert.equal(item.get('name'), 'Dynamite'); // this attribute was projected
})
));

it('can project multiple fields passed as an array', () => (
new Parse.Query(Item).select(['name', 'price']).find().then((items) => {
assert.equal(items.length, 1);
const item = items[0];
assert(item.id); // "id" is always projected
assert(item.createdAt); // "createdAt" is always projected
assert(item.updatedAt); // "updatedAt" is always projected
assert.equal(item.get('price'), 30); // this attribute was projected
assert.equal(item.get('name'), 'Dynamite'); // this attribute was projected
assert.equal(item.get('description'), undefined); // this attribute was not projected
})
));

it('can project fields from nested objects', () => {
new Parse.Query(Item)
.select(['name', 'brand.type'])
.find()
.then((items) => {
assert.equal(items.length, 1);
const item = items[0];
const brand = item.get('brand');
assert(brand);
assert.equal(brand.get('type'), 'utility'); // this attribute was projected
assert.equal(brand.get('name'), undefined); // this attribute was not projected
});
});
});

it('should correctly handle matchesQuery', () =>
createBrandP('Acme').then((brand) =>
createItemP(30, brand).then((item) =>
Expand Down