From 4248665d8ee11cf835ab78a9ffa7b54c984129fe Mon Sep 17 00:00:00 2001 From: Noah Silas Date: Wed, 15 Apr 2020 14:11:09 -0700 Subject: [PATCH] Support projecting fields in queries --- src/parse-mockdb.js | 25 +++++++++++++++++- test/test.js | 64 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/parse-mockdb.js b/src/parse-mockdb.js index 1b87fab..8422a31 100644 --- a/src/parse-mockdb.js +++ b/src/parse-mockdb.js @@ -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); @@ -619,9 +635,12 @@ function handleGetRequest(request) { match = _.omit(match, toOmit); } + if (data.keys) { + match = projectFields([match], data.keys)[0]; + } + return Promise.resolve(respond(200, match)); } - const data = request.data; indirect = data.redirectClassNameForKey; let matches = recursivelyMatch(className, data.where); let matchesClassName = ''; @@ -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; diff --git a/test/test.js b/test/test.js index 58ad718..e71acf0 100644 --- a/test/test.js +++ b/test/test.js @@ -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(); } @@ -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) =>