Skip to content

Commit e6b8ef7

Browse files
authored
fix: Check property existence for exclude from indexes with wildcard (#1114)
* Add a test for testing without mocks Testing without mocks allows us to look at the code how it is functioning right now so that we can evaluate the intended behavior. * Do a refactor on the test Objects shared by both tests should be moved into the outer block so that they can be shared. * improve the test so that the error bubbles up Now the test fails properly and passes properly by introducing callbacks * isolate the latter test right now The failure should be visible to the user and in the current state it is so now by isolating the test it becomes easier to explore solutions that work. * test fixes indent a describe block, modify tests to make sure they pass and refactor a function out that easily allows for testing modified parameters. * Add two more tests for excludeFromIndexes on array Add two more tests that address the array case for excludeIndexesFromPath. Also ensure that the conditions which check for these test cases properly ignore exclude from indexes when an array is used * Move the tests over to the index file We should not create a nomocks file because we can just group the tests under a unique describe block of test/index.ts. * PR update Remove typeof and remove check for string ‘undefined’ * Create a function to refactor the tests Refactor the tests so that they are more readable. There is a lot of repeated code in each test which makes comparisons hard. * All tests for this feature now use same script Replace all tests exercising the feature so that they all use the same test script. This will make the tests way easier to read. * Add parameterized testing Add parameterized testing to eliminate repeated code. These tests need to be easier to read for the sake of the reviewer. * Eliminate function and inline code This function is only used once now. We should inline this code in the test. * Add JSON stringify to test title In the test title include the properties passed in so that it is easy to track what passed/failed. * Organize code so that similar variables are used Similar variables should be used together. Organize the code so that there is less confusion about which variables are related. * inline getExpectedConfig function The getExpectedConfig does not need to be used anymore because its code can just be inlined in the one place it is used. * Separate tests into blocks and inline code Separate the test into blocks and inline some parameters for better test readability. * Add types to parameters passed into async Stronger typing makes the code easier to read. * Eliminate unnecessary variable assignment Don’t assign to the Datastore variable. This is not necessary. Just use the OriginalDatastore variable directly. * Ran linter and simplified source changes The diff will be easier to read if we do not add a nested if statement and instead apply a condition on the if and change the else to else if. * Add blank line back for easier diff reading * Try again, eliminate the blank line * Additional adjustment to entity first path part Apply the first suggestion in the PR to all examples * Define isFirstPathPartUndefined There is a repeated code fragment for checking if the first path part is undefined that we should refactor. * Rename the variable to align with what it does * run linter * Revert "run linter" This reverts commit a101f27. * Revert "Rename the variable to align with what it does" This reverts commit 4f54d94. * Revert "Define isFirstPathPartUndefined" This reverts commit df3f376. * Refactor check out for seeing if defined We do four checks to see if the first path part is defined. We should refactor these checks out. * Move comment to more appropriate place The comment should reflect the next line of code. * Replace comments with description Replace comments with description property so that the tests will report the description when running the tests instead of the properties in the description. * Eliminate prefix. Only use description * fix typo * lowercase convention
1 parent a41741b commit e6b8ef7

File tree

2 files changed

+144
-6
lines changed

2 files changed

+144
-6
lines changed

src/entity.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -852,8 +852,11 @@ export namespace entity {
852852
return;
853853
}
854854

855+
const isFirstPathPartDefined =
856+
entity.properties![firstPathPart] !== undefined;
855857
if (
856858
firstPathPartIsArray &&
859+
isFirstPathPartDefined &&
857860
// check also if the property in question is actually an array value.
858861
entity.properties![firstPathPart].arrayValue &&
859862
// check if wildcard is not applied
@@ -879,7 +882,12 @@ export namespace entity {
879882
);
880883
}
881884
});
882-
} else if (firstPathPartIsArray && hasWildCard && remainderPath === '*') {
885+
} else if (
886+
firstPathPartIsArray &&
887+
hasWildCard &&
888+
remainderPath === '*' &&
889+
isFirstPathPartDefined
890+
) {
883891
const array = entity.properties![firstPathPart].arrayValue;
884892
// eslint-disable-next-line @typescript-eslint/no-explicit-any
885893
array.values.forEach((value: any) => {
@@ -898,7 +906,7 @@ export namespace entity {
898906
excludePathFromEntity(entity, newPath);
899907
});
900908
} else {
901-
if (hasWildCard && remainderPath === '*') {
909+
if (hasWildCard && remainderPath === '*' && isFirstPathPartDefined) {
902910
const parentEntity = entity.properties![firstPathPart].entityValue;
903911

904912
if (parentEntity) {
@@ -911,7 +919,7 @@ export namespace entity {
911919
} else {
912920
excludePathFromEntity(entity, firstPathPart);
913921
}
914-
} else {
922+
} else if (isFirstPathPartDefined) {
915923
const parentEntity = entity.properties![firstPathPart].entityValue;
916924
excludePathFromEntity(parentEntity, remainderPath);
917925
}

test/index.ts

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,23 @@ import {PassThrough, Readable} from 'stream';
2020

2121
import * as ds from '../src';
2222
import {Datastore, DatastoreOptions} from '../src';
23-
import {entity, Entity, EntityProto, EntityObject} from '../src/entity';
24-
import {RequestConfig} from '../src/request';
23+
import {Datastore as OriginalDatastore} from '../src';
24+
import {
25+
entity,
26+
Entity,
27+
EntityProto,
28+
EntityObject,
29+
Entities,
30+
} from '../src/entity';
31+
import {RequestCallback, RequestConfig} from '../src/request';
2532
import * as is from 'is';
2633
import * as sinon from 'sinon';
2734
import * as extend from 'extend';
28-
const async = require('async');
35+
import {google} from '../protos/protos';
2936

3037
// eslint-disable-next-line @typescript-eslint/no-var-requires
3138
const v1 = require('../src/v1/index.js');
39+
const async = require('async');
3240

3341
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3442
const fakeEntityInit: any = {
@@ -2319,6 +2327,128 @@ async.each(
23192327
});
23202328
});
23212329

2330+
describe('without using mocks', () => {
2331+
describe('on save tests', () => {
2332+
const onSaveTests = [
2333+
{
2334+
description:
2335+
'should encode a save request without excludeFromIndexes',
2336+
properties: {k: {stringValue: 'v'}},
2337+
entitiesWithoutKey: {data: {k: 'v'}},
2338+
},
2339+
{
2340+
description:
2341+
'should add exclude from indexes to property k and ignore excludeFromIndexes with wildcard',
2342+
properties: {k: {stringValue: 'v', excludeFromIndexes: true}},
2343+
entitiesWithoutKey: {
2344+
data: {k: 'v'},
2345+
excludeFromIndexes: ['k', 'k.*'],
2346+
},
2347+
},
2348+
{
2349+
description:
2350+
'should encode a save request without properties and without excludeFromIndexes',
2351+
properties: {},
2352+
entitiesWithoutKey: {data: {}},
2353+
},
2354+
{
2355+
description:
2356+
'should encode a save request with no properties ignoring excludeFromIndexes for a property not on save data',
2357+
properties: {},
2358+
entitiesWithoutKey: {
2359+
data: {},
2360+
excludeFromIndexes: [
2361+
'non_exist_property', // this just ignored
2362+
'non_exist_property.*', // should also be ignored
2363+
],
2364+
},
2365+
},
2366+
{
2367+
description:
2368+
'should encode a save request with one property ignoring excludeFromIndexes for a property not on save data',
2369+
properties: {k: {stringValue: 'v'}},
2370+
entitiesWithoutKey: {
2371+
data: {k: 'v'},
2372+
excludeFromIndexes: [
2373+
'non_exist_property[]', // this just ignored
2374+
],
2375+
},
2376+
},
2377+
{
2378+
description:
2379+
'should encode a save request with one property ignoring excludeFromIndexes for a property with a wildcard not on save data',
2380+
properties: {k: {stringValue: 'v'}},
2381+
entitiesWithoutKey: {
2382+
data: {k: 'v'},
2383+
excludeFromIndexes: [
2384+
'non_exist_property[].*', // this just ignored
2385+
],
2386+
},
2387+
},
2388+
];
2389+
2390+
async.each(
2391+
onSaveTests,
2392+
(onSaveTest: {
2393+
description: string;
2394+
properties: google.datastore.v1.IValue;
2395+
entitiesWithoutKey: Entities;
2396+
}) => {
2397+
it(`${onSaveTest.description}`, async () => {
2398+
const datastore = new OriginalDatastore({
2399+
namespace: `${Date.now()}`,
2400+
});
2401+
{
2402+
// This block of code mocks out request_ to check values passed into it.
2403+
const expectedConfig = {
2404+
client: 'DatastoreClient',
2405+
method: 'commit',
2406+
gaxOpts: {},
2407+
reqOpts: {
2408+
mutations: [
2409+
{
2410+
upsert: {
2411+
key: {
2412+
path: [{kind: 'Post', name: 'Post1'}],
2413+
partitionId: {
2414+
namespaceId: datastore.namespace,
2415+
},
2416+
},
2417+
properties: onSaveTest.properties,
2418+
},
2419+
},
2420+
],
2421+
},
2422+
};
2423+
// Mock out the request function to compare config passed into it.
2424+
datastore.request_ = (
2425+
config: RequestConfig,
2426+
callback: RequestCallback
2427+
) => {
2428+
try {
2429+
assert.deepStrictEqual(config, expectedConfig);
2430+
callback(null, 'some-data');
2431+
} catch (e: any) {
2432+
callback(e);
2433+
}
2434+
};
2435+
}
2436+
{
2437+
// Attach key to entities parameter passed in and run save with those parameters.
2438+
const key = datastore.key(['Post', 'Post1']);
2439+
const entities = Object.assign(
2440+
{key},
2441+
onSaveTest.entitiesWithoutKey
2442+
);
2443+
const results = await datastore.save(entities);
2444+
assert.deepStrictEqual(results, ['some-data']);
2445+
}
2446+
});
2447+
}
2448+
);
2449+
});
2450+
});
2451+
23222452
describe('multi-db support', () => {
23232453
it('should get the database id from the client', async () => {
23242454
const otherDatastore = new Datastore({

0 commit comments

Comments
 (0)