From 8b0b31fdde5bd565aa527719003ef283a45f55cc Mon Sep 17 00:00:00 2001 From: Will Schurman Date: Thu, 11 Apr 2024 09:09:18 -0700 Subject: [PATCH] feat: require explicit query context specification (#219) --- .../GenericLocalMemoryCacher-full-test.ts | 42 +- ...tchedRedisCacheAdapter-integration-test.ts | 48 +- ...enericRedisCacher-full-integration-test.ts | 65 ++- .../GenericRedisCacher-integration-test.ts | 10 +- .../src/__integration-tests__/errors-test.ts | 7 +- .../PostgresEntityIntegration-test.ts | 444 ++++++++++++++---- ...PostgresEntityQueryContextProvider-test.ts | 35 +- .../PostgresInvalidSetup-test.ts | 68 ++- .../src/__integration-tests__/errors-test.ts | 71 ++- .../src/__tests__/NoteEntity-test.ts | 10 +- .../entity-example/src/routers/notesRouter.ts | 68 +-- packages/entity-example/src/schema.ts | 36 +- packages/entity-example/src/viewerContexts.ts | 17 +- .../EntityCacheInconsistency-test.ts | 46 +- .../EntityEdgesIntegration-test.ts | 27 +- ...itySelfReferentialEdgesIntegration-test.ts | 34 +- .../entities/ChildEntity.ts | 22 +- .../entities/ParentEntity.ts | 22 +- .../entities/TestViewerContext.ts | 13 + .../LocalMemorySecondaryEntityCache-test.ts | 15 +- ...isSecondaryEntityCache-integration-test.ts | 15 +- packages/entity/src/Entity.ts | 35 +- .../entity/src/EntityAssociationLoader.ts | 34 +- .../entity/src/EntityQueryContextProvider.ts | 2 +- packages/entity/src/ReadonlyEntity.ts | 5 +- packages/entity/src/ViewerContext.ts | 10 +- packages/entity/src/__tests__/Entity-test.ts | 70 +-- .../__tests__/EntityAssociationLoader-test.ts | 254 ++++++---- .../__tests__/EntityCommonUseCases-test.ts | 54 ++- .../entity/src/__tests__/EntityEdges-test.ts | 142 ++++-- .../EntityLoader-constructor-test.ts | 2 +- .../entity/src/__tests__/EntityLoader-test.ts | 38 +- ...tyMutator-MutationCacheConsistency-test.ts | 35 +- .../src/__tests__/EntityMutator-test.ts | 128 ++--- .../EntitySecondaryCacheLoader-test.ts | 31 +- .../EntitySelfReferentialEdges-test.ts | 162 +++++-- .../src/__tests__/ReadonlyEntity-test.ts | 28 +- .../src/__tests__/ViewerContext-test.ts | 5 +- .../TwoEntitySameTableDisjointRows-test.ts | 55 ++- .../TwoEntitySameTableOverlappingRows-test.ts | 52 +- .../entity/src/internal/EntityDataManager.ts | 2 +- .../__tests__/EntityDataManager-test.ts | 22 +- .../src/testfixtures/SimpleTestEntity.ts | 16 +- .../entity/src/testfixtures/TestEntity.ts | 27 +- .../entity/src/testfixtures/TestEntity2.ts | 16 +- .../src/testfixtures/TestViewerContext.ts | 13 +- 46 files changed, 1598 insertions(+), 755 deletions(-) create mode 100644 packages/entity-full-integration-tests/src/__integration-tests__/entities/TestViewerContext.ts diff --git a/packages/entity-cache-adapter-local-memory/src/__tests__/GenericLocalMemoryCacher-full-test.ts b/packages/entity-cache-adapter-local-memory/src/__tests__/GenericLocalMemoryCacher-full-test.ts index dbf552b1..9778dc8c 100644 --- a/packages/entity-cache-adapter-local-memory/src/__tests__/GenericLocalMemoryCacher-full-test.ts +++ b/packages/entity-cache-adapter-local-memory/src/__tests__/GenericLocalMemoryCacher-full-test.ts @@ -25,13 +25,19 @@ describe(GenericLocalMemoryCacher, () => { const cacheKeyMaker = genericCacher['makeCacheKey'].bind(genericCacher); const date = new Date(); - const entity1Created = await LocalMemoryTestEntity.creator(viewerContext) + const entity1Created = await LocalMemoryTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .setField('dateField', date) .enforceCreateAsync(); // loading an entity should put it in cache - const entity1 = await LocalMemoryTestEntity.loader(viewerContext) + const entity1 = await LocalMemoryTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByIDAsync(entity1Created.getID()); @@ -63,9 +69,10 @@ describe(GenericLocalMemoryCacher, () => { // simulate non existent db fetch, should write negative result ('') to cache const nonExistentId = uuidv4(); - const entityNonExistentResult = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync( - nonExistentId - ); + const entityNonExistentResult = await LocalMemoryTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadByIDAsync(nonExistentId); expect(entityNonExistentResult.ok).toBe(false); const nonExistentCachedResult = await entitySpecificGenericCacher.loadManyAsync([ @@ -77,12 +84,16 @@ describe(GenericLocalMemoryCacher, () => { // load again through entities framework to ensure it reads negative result const entityNonExistentResult2 = await LocalMemoryTestEntity.loader( - viewerContext + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') ).loadByIDAsync(nonExistentId); expect(entityNonExistentResult2.ok).toBe(false); // invalidate from cache to ensure it invalidates correctly - await LocalMemoryTestEntity.loader(viewerContext).invalidateFieldsAsync(entity1.getAllFields()); + await LocalMemoryTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).invalidateFieldsAsync(entity1.getAllFields()); const cachedResultMiss = await entitySpecificGenericCacher.loadManyAsync([ cacheKeyMaker('id', entity1.getID()), ]); @@ -100,13 +111,19 @@ describe(GenericLocalMemoryCacher, () => { const cacheKeyMaker = genericCacher['makeCacheKey'].bind(genericCacher); const date = new Date(); - const entity1Created = await LocalMemoryTestEntity.creator(viewerContext) + const entity1Created = await LocalMemoryTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .setField('dateField', date) .enforceCreateAsync(); // loading an entity will try to put it in cache but it's a noop cache, so it should be a miss - const entity1 = await LocalMemoryTestEntity.loader(viewerContext) + const entity1 = await LocalMemoryTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByIDAsync(entity1Created.getID()); @@ -133,9 +150,10 @@ describe(GenericLocalMemoryCacher, () => { // a non existent db fetch should try to write negative result ('') but it's a noop cache, so it should be a miss const nonExistentId = uuidv4(); - const entityNonExistentResult = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync( - nonExistentId - ); + const entityNonExistentResult = await LocalMemoryTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadByIDAsync(nonExistentId); expect(entityNonExistentResult.ok).toBe(false); const nonExistentCachedResult = await entitySpecificGenericCacher.loadManyAsync([ diff --git a/packages/entity-cache-adapter-redis/src/__integration-tests__/BatchedRedisCacheAdapter-integration-test.ts b/packages/entity-cache-adapter-redis/src/__integration-tests__/BatchedRedisCacheAdapter-integration-test.ts index fc11e4c4..b8152e53 100644 --- a/packages/entity-cache-adapter-redis/src/__integration-tests__/BatchedRedisCacheAdapter-integration-test.ts +++ b/packages/entity-cache-adapter-redis/src/__integration-tests__/BatchedRedisCacheAdapter-integration-test.ts @@ -101,7 +101,10 @@ describe(GenericRedisCacher, () => { ]['cacheAdapter']['genericCacher']; const cacheKeyMaker = genericCacher['makeCacheKey'].bind(genericCacher); - const entity1Created = await RedisTestEntity.creator(viewerContext) + const entity1Created = await RedisTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .enforceCreateAsync(); @@ -117,9 +120,22 @@ describe(GenericRedisCacher, () => { createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext) ); const [entity1, entity2, entity3] = await Promise.all([ - RedisTestEntity.loader(viewerContext1).enforcing().loadByIDAsync(entity1Created.getID()), - RedisTestEntity.loader(viewerContext2).enforcing().loadByIDAsync(entity1Created.getID()), - RedisTestEntity.loader(viewerContext3) + RedisTestEntity.loader( + viewerContext1, + viewerContext1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .enforcing() + .loadByIDAsync(entity1Created.getID()), + RedisTestEntity.loader( + viewerContext2, + viewerContext2.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .enforcing() + .loadByIDAsync(entity1Created.getID()), + RedisTestEntity.loader( + viewerContext3, + viewerContext3.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', entity1Created.getField('name')), ]); @@ -138,28 +154,36 @@ describe(GenericRedisCacher, () => { }); const cacheKeyEntity1NameField = cacheKeyMaker('name', entity1Created.getField('name')); - await RedisTestEntity.loader(viewerContext) + await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', entity1Created.getField('name')); await expect(redis.get(cacheKeyEntity1NameField)).resolves.toEqual(cachedJSON); // simulate non existent db fetch, should write negative result ('') to cache const nonExistentId = uuidv4(); - const entityNonExistentResult = await RedisTestEntity.loader(viewerContext).loadByIDAsync( - nonExistentId - ); + const entityNonExistentResult = await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadByIDAsync(nonExistentId); expect(entityNonExistentResult.ok).toBe(false); const cacheKeyNonExistent = cacheKeyMaker('id', nonExistentId); const nonExistentCachedValue = await redis.get(cacheKeyNonExistent); expect(nonExistentCachedValue).toEqual(''); // load again through entities framework to ensure it reads negative result - const entityNonExistentResult2 = await RedisTestEntity.loader(viewerContext).loadByIDAsync( - nonExistentId - ); + const entityNonExistentResult2 = await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadByIDAsync(nonExistentId); expect(entityNonExistentResult2.ok).toBe(false); // invalidate from cache to ensure it invalidates correctly in both caches - await RedisTestEntity.loader(viewerContext).invalidateFieldsAsync(entity1.getAllFields()); + await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).invalidateFieldsAsync(entity1.getAllFields()); await expect(redis.get(cacheKeyEntity1)).resolves.toBeNull(); await expect(redis.get(cacheKeyEntity1NameField)).resolves.toBeNull(); }); diff --git a/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-full-integration-test.ts b/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-full-integration-test.ts index 8e1d2936..7f2b6856 100644 --- a/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-full-integration-test.ts +++ b/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-full-integration-test.ts @@ -46,12 +46,18 @@ describe(GenericRedisCacher, () => { ]['cacheAdapter']['genericCacher']; const cacheKeyMaker = genericCacher['makeCacheKey'].bind(genericCacher); - const entity1Created = await RedisTestEntity.creator(viewerContext) + const entity1Created = await RedisTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .enforceCreateAsync(); // loading an entity should put it in cache - const entity1 = await RedisTestEntity.loader(viewerContext) + const entity1 = await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByIDAsync(entity1Created.getID()); @@ -67,9 +73,10 @@ describe(GenericRedisCacher, () => { // simulate non existent db fetch, should write negative result ('') to cache const nonExistentId = uuidv4(); - const entityNonExistentResult = await RedisTestEntity.loader(viewerContext).loadByIDAsync( - nonExistentId - ); + const entityNonExistentResult = await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadByIDAsync(nonExistentId); expect(entityNonExistentResult.ok).toBe(false); const nonExistentCachedValue = await (genericRedisCacheContext.redisClient as Redis).get( @@ -78,13 +85,17 @@ describe(GenericRedisCacher, () => { expect(nonExistentCachedValue).toEqual(''); // load again through entities framework to ensure it reads negative result - const entityNonExistentResult2 = await RedisTestEntity.loader(viewerContext).loadByIDAsync( - nonExistentId - ); + const entityNonExistentResult2 = await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadByIDAsync(nonExistentId); expect(entityNonExistentResult2.ok).toBe(false); // invalidate from cache to ensure it invalidates correctly - await RedisTestEntity.loader(viewerContext).invalidateFieldsAsync(entity1.getAllFields()); + await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).invalidateFieldsAsync(entity1.getAllFields()); const cachedValueNull = await (genericRedisCacheContext.redisClient as Redis).get( cacheKeyMaker('id', entity1.getID()) ); @@ -97,11 +108,19 @@ describe(GenericRedisCacher, () => { ); const date = new Date(); const entity1 = await enforceAsyncResult( - RedisTestEntity.creator(viewerContext).setField('dateField', date).createAsync() + RedisTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('dateField', date) + .createAsync() ); expect(entity1.getField('dateField')).toEqual(date); - const entity2 = await RedisTestEntity.loader(viewerContext) + const entity2 = await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByIDAsync(entity1.getID()); expect(entity2.getField('dateField')).toEqual(date); @@ -110,7 +129,12 @@ describe(GenericRedisCacher, () => { const vc2 = new TestViewerContext( createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext) ); - const entity3 = await RedisTestEntity.loader(vc2).enforcing().loadByIDAsync(entity1.getID()); + const entity3 = await RedisTestEntity.loader( + vc2, + vc2.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .enforcing() + .loadByIDAsync(entity1.getID()); expect(entity3.getField('dateField')).toEqual(date); }); @@ -119,9 +143,17 @@ describe(GenericRedisCacher, () => { createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext) ); const entity1 = await enforceAsyncResult( - RedisTestEntity.creator(viewerContext).setField('name', '').createAsync() + RedisTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', '') + .createAsync() ); - const entity2 = await RedisTestEntity.loader(viewerContext) + const entity2 = await RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', ''); expect(entity2?.getID()).toEqual(entity1.getID()); @@ -130,7 +162,10 @@ describe(GenericRedisCacher, () => { const vc2 = new TestViewerContext( createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext) ); - const entity3 = await RedisTestEntity.loader(vc2) + const entity3 = await RedisTestEntity.loader( + vc2, + vc2.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', ''); expect(entity3?.getID()).toEqual(entity1.getID()); diff --git a/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-integration-test.ts b/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-integration-test.ts index 13ce64f9..4354002e 100644 --- a/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-integration-test.ts +++ b/packages/entity-cache-adapter-redis/src/__integration-tests__/GenericRedisCacher-integration-test.ts @@ -46,7 +46,10 @@ describe(GenericRedisCacher, () => { redisTestEntityConfiguration ); const date = new Date(); - const entity1Created = await RedisTestEntity.creator(viewerContext) + const entity1Created = await RedisTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .setField('dateField', date) .enforceCreateAsync(); @@ -92,7 +95,10 @@ describe(GenericRedisCacher, () => { redisTestEntityConfiguration ); const date = new Date(); - const entity1Created = await RedisTestEntity.creator(viewerContext) + const entity1Created = await RedisTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .setField('dateField', date) .enforceCreateAsync(); diff --git a/packages/entity-cache-adapter-redis/src/__integration-tests__/errors-test.ts b/packages/entity-cache-adapter-redis/src/__integration-tests__/errors-test.ts index aeae8155..d972abd6 100644 --- a/packages/entity-cache-adapter-redis/src/__integration-tests__/errors-test.ts +++ b/packages/entity-cache-adapter-redis/src/__integration-tests__/errors-test.ts @@ -40,7 +40,12 @@ describe(GenericRedisCacher, () => { ); await expect( - RedisTestEntity.creator(vc1).setField('name', 'blah').enforceCreateAsync() + RedisTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'blah') + .enforceCreateAsync() ).rejects.toThrow(EntityCacheAdapterTransientError); }); }); diff --git a/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityIntegration-test.ts b/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityIntegration-test.ts index 3af8f407..6b5c6d2b 100644 --- a/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityIntegration-test.ts +++ b/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityIntegration-test.ts @@ -42,16 +42,34 @@ describe('postgres entity integration', () => { it('supports parallel partial updates', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity = await enforceAsyncResult( - PostgresTestEntity.creator(vc).setField('name', 'hello').createAsync() + PostgresTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'hello') + .createAsync() ); // update two different fields at the same time (from the same entity) await Promise.all([ - PostgresTestEntity.updater(entity).setField('hasACat', true).updateAsync(), - PostgresTestEntity.updater(entity).setField('hasADog', false).updateAsync(), + PostgresTestEntity.updater( + entity, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('hasACat', true) + .updateAsync(), + PostgresTestEntity.updater( + entity, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('hasADog', false) + .updateAsync(), ]); - const loadedEntity = await PostgresTestEntity.loader(vc) + const loadedEntity = await PostgresTestEntity.loader( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByIDAsync(entity.getID()); @@ -62,24 +80,49 @@ describe('postgres entity integration', () => { describe('empty creates and updates', () => { it('allows empty create', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); - const entity = await enforceAsyncResult(PostgresTestEntity.creator(vc).createAsync()); + const entity = await enforceAsyncResult( + PostgresTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).createAsync() + ); expect(entity.getID()).toBeTruthy(); }); it('throws knex error upon empty update', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity = await enforceAsyncResult( - PostgresTestEntity.creator(vc).setField('name', 'hello').createAsync() + PostgresTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'hello') + .createAsync() ); - await expect(PostgresTestEntity.updater(entity).updateAsync()).rejects.toThrow(); + await expect( + PostgresTestEntity.updater( + entity, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).updateAsync() + ).rejects.toThrow(); }); it('throws error upon empty update for stub database adapter to match behavior', async () => { const vc = new ViewerContext(createUnitTestEntityCompanionProvider()); const entity = await enforceAsyncResult( - PostgresTestEntity.creator(vc).setField('name', 'hello').createAsync() + PostgresTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'hello') + .createAsync() ); - await expect(PostgresTestEntity.updater(entity).updateAsync()).rejects.toThrow(); + await expect( + PostgresTestEntity.updater( + entity, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).updateAsync() + ).rejects.toThrow(); }); }); @@ -88,10 +131,20 @@ describe('postgres entity integration', () => { // put one in the DB const firstEntity = await enforceAsyncResult( - PostgresTestEntity.creator(vc1).setField('name', 'hello').createAsync() + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'hello') + .createAsync() ); - await enforceAsyncResult(PostgresTestEntity.loader(vc1).loadByIDAsync(firstEntity.getID())); + await enforceAsyncResult( + PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadByIDAsync(firstEntity.getID()) + ); const errorToThrow = new Error('Intentional error'); @@ -107,7 +160,10 @@ describe('postgres entity integration', () => { ).rejects.toEqual(errorToThrow); const entities = await enforceResultsAsync( - PostgresTestEntity.loader(vc1).loadManyByFieldEqualingAsync('name', 'hello') + PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ).loadManyByFieldEqualingAsync('name', 'hello') ); expect(entities).toHaveLength(1); }); @@ -116,7 +172,12 @@ describe('postgres entity integration', () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const firstEntity = await enforceAsyncResult( - PostgresTestEntity.creator(vc1).setField('name', 'hello').createAsync() + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'hello') + .createAsync() ); const loadAndUpdateAsync = async (newName: string): Promise<{ error?: Error }> => { @@ -155,7 +216,10 @@ describe('postgres entity integration', () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity = await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('stringArray', ['hello', 'world']) .setField('jsonArrayField', ['hello', 'world']) .createAsync() @@ -169,7 +233,10 @@ describe('postgres entity integration', () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity = await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('jsonObjectField', { hello: 'world' }) .createAsync() ); @@ -181,12 +248,18 @@ describe('postgres entity integration', () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity1 = await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('maybeJsonArrayField', ['hello', 'world']) .createAsync() ); const entity2 = await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('maybeJsonArrayField', { hello: 'world' }) .createAsync() ); @@ -201,17 +274,32 @@ describe('postgres entity integration', () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); let entity = await enforceAsyncResult( - PostgresTestEntity.creator(vc1).setField('bigintField', '72057594037928038').createAsync() + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('bigintField', '72057594037928038') + .createAsync() ); expect(entity.getField('bigintField')).toEqual('72057594037928038'); entity = await enforceAsyncResult( - PostgresTestEntity.updater(entity).setField('bigintField', '10').updateAsync() + PostgresTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('bigintField', '10') + .updateAsync() ); expect(entity.getField('bigintField')).toEqual('10'); entity = await enforceAsyncResult( - PostgresTestEntity.updater(entity).setField('bigintField', '-10').updateAsync() + PostgresTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('bigintField', '-10') + .updateAsync() ); expect(entity.getField('bigintField')).toEqual('-10'); }); @@ -222,7 +310,10 @@ describe('postgres entity integration', () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'hello') .setField('hasACat', false) .setField('hasADog', true) @@ -230,7 +321,10 @@ describe('postgres entity integration', () => { ); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'world') .setField('hasACat', false) .setField('hasADog', true) @@ -238,14 +332,20 @@ describe('postgres entity integration', () => { ); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'wat') .setField('hasACat', false) .setField('hasADog', false) .createAsync() ); - const results = await PostgresTestEntity.loader(vc1) + const results = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByFieldEqualityConjunctionAsync([ { @@ -260,7 +360,10 @@ describe('postgres entity integration', () => { expect(results).toHaveLength(2); - const results2 = await PostgresTestEntity.loader(vc1) + const results2 = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByFieldEqualityConjunctionAsync([ { fieldName: 'hasADog', fieldValues: [true, false] }, @@ -271,13 +374,37 @@ describe('postgres entity integration', () => { it('supports query modifiers', async () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); - await enforceAsyncResult(PostgresTestEntity.creator(vc1).setField('name', 'a').createAsync()); + await enforceAsyncResult( + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'a') + .createAsync() + ); - await enforceAsyncResult(PostgresTestEntity.creator(vc1).setField('name', 'b').createAsync()); + await enforceAsyncResult( + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'b') + .createAsync() + ); - await enforceAsyncResult(PostgresTestEntity.creator(vc1).setField('name', 'c').createAsync()); + await enforceAsyncResult( + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'c') + .createAsync() + ); - const results = await PostgresTestEntity.loader(vc1) + const results = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByFieldEqualityConjunctionAsync([], { limit: 2, @@ -296,37 +423,55 @@ describe('postgres entity integration', () => { it('supports null field values', async () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'a') .setField('hasADog', true) .createAsync() ); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'b') .setField('hasADog', true) .createAsync() ); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', null) .setField('hasADog', true) .createAsync() ); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', null) .setField('hasADog', false) .createAsync() ); - const results = await PostgresTestEntity.loader(vc1) + const results = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByFieldEqualityConjunctionAsync([{ fieldName: 'name', fieldValue: null }]); expect(results).toHaveLength(2); expect(results[0]!.getField('name')).toBeNull(); - const results2 = await PostgresTestEntity.loader(vc1) + const results2 = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByFieldEqualityConjunctionAsync( [ @@ -351,14 +496,20 @@ describe('postgres entity integration', () => { it('loads by raw where clause', async () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'hello') .setField('hasACat', false) .setField('hasADog', true) .createAsync() ); - const results = await PostgresTestEntity.loader(vc1) + const results = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByRawWhereClauseAsync('name = ?', ['hello']); @@ -368,7 +519,10 @@ describe('postgres entity integration', () => { it('throws with invalid where clause', async () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'hello') .setField('hasACat', false) .setField('hasADog', true) @@ -376,7 +530,10 @@ describe('postgres entity integration', () => { ); await expect( - PostgresTestEntity.loader(vc1) + PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByRawWhereClauseAsync('invalid_column = ?', ['hello']) ).rejects.toThrow(); @@ -386,27 +543,39 @@ describe('postgres entity integration', () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'a') .setField('hasADog', true) .createAsync() ); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'b') .setField('hasADog', true) .createAsync() ); await enforceAsyncResult( - PostgresTestEntity.creator(vc1) + PostgresTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'c') .setField('hasADog', true) .createAsync() ); - const results = await PostgresTestEntity.loader(vc1) + const results = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByRawWhereClauseAsync('has_a_dog = ?', [true], { limit: 2, @@ -422,7 +591,10 @@ describe('postgres entity integration', () => { expect(results).toHaveLength(2); expect(results.map((e) => e.getField('name'))).toEqual(['b', 'c']); - const resultsMultipleOrderBy = await PostgresTestEntity.loader(vc1) + const resultsMultipleOrderBy = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByRawWhereClauseAsync('has_a_dog = ?', [true], { orderBy: [ @@ -440,7 +612,10 @@ describe('postgres entity integration', () => { expect(resultsMultipleOrderBy).toHaveLength(3); expect(resultsMultipleOrderBy.map((e) => e.getField('name'))).toEqual(['c', 'b', 'a']); - const resultsOrderByRaw = await PostgresTestEntity.loader(vc1) + const resultsOrderByRaw = await PostgresTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadManyByRawWhereClauseAsync('has_a_dog = ?', [true], { orderByRaw: 'has_a_dog ASC, name DESC', @@ -459,52 +634,86 @@ describe('postgres entity integration', () => { ); await expect( - PostgresTriggerTestEntity.creator(vc1) + PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'beforeCreate') .enforceCreateAsync() ).rejects.toThrowError('name cannot have value beforeCreate'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'beforeCreate') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.creator(vc1) + PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'afterCreate') .enforceCreateAsync() ).rejects.toThrowError('name cannot have value afterCreate'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'afterCreate') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.creator(vc1).setField('name', 'beforeAll').enforceCreateAsync() + PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'beforeAll') + .enforceCreateAsync() ).rejects.toThrowError('name cannot have value beforeAll'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'beforeAll') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.creator(vc1).setField('name', 'afterAll').enforceCreateAsync() + PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'afterAll') + .enforceCreateAsync() ).rejects.toThrowError('name cannot have value afterAll'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'afterAll') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.creator(vc1) + PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'afterCommit') .enforceCreateAsync() ).rejects.toThrowError('name cannot have value afterCommit'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'afterCommit') ).resolves.not.toBeNull(); @@ -517,61 +726,94 @@ describe('postgres entity integration', () => { createKnexIntegrationTestEntityCompanionProvider(knexInstance) ); - const entity = await PostgresTriggerTestEntity.creator(vc1) + const entity = await PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .enforceCreateAsync(); await expect( - PostgresTriggerTestEntity.updater(entity) + PostgresTriggerTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'beforeUpdate') .enforceUpdateAsync() ).rejects.toThrowError('name cannot have value beforeUpdate'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'beforeUpdate') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.updater(entity) + PostgresTriggerTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'afterUpdate') .enforceUpdateAsync() ).rejects.toThrowError('name cannot have value afterUpdate'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'afterUpdate') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.updater(entity) + PostgresTriggerTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'beforeAll') .enforceUpdateAsync() ).rejects.toThrowError('name cannot have value beforeAll'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'beforeAll') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.updater(entity) + PostgresTriggerTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'afterAll') .enforceUpdateAsync() ).rejects.toThrowError('name cannot have value afterAll'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'afterAll') ).resolves.toBeNull(); await expect( - PostgresTriggerTestEntity.updater(entity) + PostgresTriggerTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'afterCommit') .enforceUpdateAsync() ).rejects.toThrowError('name cannot have value afterCommit'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'afterCommit') ).resolves.not.toBeNull(); @@ -584,26 +826,44 @@ describe('postgres entity integration', () => { createKnexIntegrationTestEntityCompanionProvider(knexInstance) ); - const entityBeforeDelete = await PostgresTriggerTestEntity.creator(vc1) + const entityBeforeDelete = await PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'beforeDelete') .enforceCreateAsync(); await expect( - PostgresTriggerTestEntity.enforceDeleteAsync(entityBeforeDelete) + PostgresTriggerTestEntity.enforceDeleteAsync( + entityBeforeDelete, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) ).rejects.toThrowError('name cannot have value beforeDelete'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'beforeDelete') ).resolves.not.toBeNull(); - const entityAfterDelete = await PostgresTriggerTestEntity.creator(vc1) + const entityAfterDelete = await PostgresTriggerTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'afterDelete') .enforceCreateAsync(); await expect( - PostgresTriggerTestEntity.enforceDeleteAsync(entityAfterDelete) + PostgresTriggerTestEntity.enforceDeleteAsync( + entityAfterDelete, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) ).rejects.toThrowError('name cannot have value afterDelete'); await expect( - PostgresTriggerTestEntity.loader(vc1) + PostgresTriggerTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'afterDelete') ).resolves.not.toBeNull(); @@ -617,12 +877,18 @@ describe('postgres entity integration', () => { ); await expect( - PostgresValidatorTestEntity.creator(vc1) + PostgresValidatorTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'beforeCreateAndBeforeUpdate') .enforceCreateAsync() ).rejects.toThrowError('name cannot have value beforeCreateAndBeforeUpdate'); await expect( - PostgresValidatorTestEntity.loader(vc1) + PostgresValidatorTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'beforeCreateAndBeforeUpdate') ).resolves.toBeNull(); @@ -634,17 +900,26 @@ describe('postgres entity integration', () => { createKnexIntegrationTestEntityCompanionProvider(knexInstance) ); - const entity = await PostgresValidatorTestEntity.creator(vc1) + const entity = await PostgresValidatorTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'blah') .enforceCreateAsync(); await expect( - PostgresValidatorTestEntity.updater(entity) + PostgresValidatorTestEntity.updater( + entity, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'beforeCreateAndBeforeUpdate') .enforceUpdateAsync() ).rejects.toThrowError('name cannot have value beforeCreateAndBeforeUpdate'); await expect( - PostgresValidatorTestEntity.loader(vc1) + PostgresValidatorTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'beforeCreateAndBeforeUpdate') ).resolves.toBeNull(); @@ -656,12 +931,21 @@ describe('postgres entity integration', () => { createKnexIntegrationTestEntityCompanionProvider(knexInstance) ); - const entityToDelete = await PostgresValidatorTestEntity.creator(vc1) + const entityToDelete = await PostgresValidatorTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'shouldBeDeleted') .enforceCreateAsync(); - await PostgresValidatorTestEntity.enforceDeleteAsync(entityToDelete); + await PostgresValidatorTestEntity.enforceDeleteAsync( + entityToDelete, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ); await expect( - PostgresValidatorTestEntity.loader(vc1) + PostgresValidatorTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .enforcing() .loadByFieldEqualingAsync('name', 'shouldBeDeleted') ).resolves.toBeNull(); diff --git a/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts b/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts index ca064ba2..ceb1e4d5 100644 --- a/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts +++ b/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts @@ -34,10 +34,20 @@ describe(PostgresEntityQueryContextProvider, () => { it('supports nested transactions', async () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); - await PostgresUniqueTestEntity.creator(vc1).setField('name', 'unique').enforceCreateAsync(); + await PostgresUniqueTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'unique') + .enforceCreateAsync(); const id = ( - await PostgresUniqueTestEntity.creator(vc1).setField('name', 'wat').enforceCreateAsync() + await PostgresUniqueTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'wat') + .enforceCreateAsync() ).getID(); await vc1.runInTransactionForDatabaseAdaptorFlavorAsync('postgres', async (queryContext) => { @@ -69,7 +79,12 @@ describe(PostgresEntityQueryContextProvider, () => { .enforceUpdateAsync(); }); - const entityLoaded = await PostgresUniqueTestEntity.loader(vc1).enforcing().loadByIDAsync(id); + const entityLoaded = await PostgresUniqueTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .enforcing() + .loadByIDAsync(id); expect(entityLoaded.getField('name')).toEqual('wat3'); }); @@ -77,7 +92,12 @@ describe(PostgresEntityQueryContextProvider, () => { const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const id = ( - await PostgresUniqueTestEntity.creator(vc1).setField('name', 'wat').enforceCreateAsync() + await PostgresUniqueTestEntity.creator( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'wat') + .enforceCreateAsync() ).getID(); await vc1.runInTransactionForDatabaseAdaptorFlavorAsync('postgres', async (queryContext) => { @@ -95,7 +115,12 @@ describe(PostgresEntityQueryContextProvider, () => { }); }); - const entityLoaded = await PostgresUniqueTestEntity.loader(vc1).enforcing().loadByIDAsync(id); + const entityLoaded = await PostgresUniqueTestEntity.loader( + vc1, + vc1.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .enforcing() + .loadByIDAsync(id); expect(entityLoaded.getField('name')).toEqual('wat3'); }); }); diff --git a/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresInvalidSetup-test.ts b/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresInvalidSetup-test.ts index 7edcf546..a97b5f58 100644 --- a/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresInvalidSetup-test.ts +++ b/packages/entity-database-adapter-knex/src/__integration-tests__/PostgresInvalidSetup-test.ts @@ -35,40 +35,86 @@ describe('postgres entity integration', () => { it('throws after deletion of multiple rows or no rows', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity1 = await enforceAsyncResult( - InvalidTestEntity.creator(vc).setField('id', 1).setField('name', 'hello').createAsync() + InvalidTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('id', 1) + .setField('name', 'hello') + .createAsync() ); await enforceAsyncResult( - InvalidTestEntity.creator(vc).setField('id', 1).setField('name', 'world').createAsync() + InvalidTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('id', 1) + .setField('name', 'world') + .createAsync() ); - await expect(InvalidTestEntity.deleteAsync(entity1)).rejects.toThrowError( - 'Excessive deletions from database adapter delete' - ); + await expect( + InvalidTestEntity.deleteAsync( + entity1, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + ).rejects.toThrowError('Excessive deletions from database adapter delete'); }); it('throws after update of multiple rows', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity1 = await enforceAsyncResult( - InvalidTestEntity.creator(vc).setField('id', 1).setField('name', 'hello').createAsync() + InvalidTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('id', 1) + .setField('name', 'hello') + .createAsync() ); await enforceAsyncResult( - InvalidTestEntity.creator(vc).setField('id', 1).setField('name', 'world').createAsync() + InvalidTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('id', 1) + .setField('name', 'world') + .createAsync() ); await expect( - InvalidTestEntity.updater(entity1).setField('name', 'blah').enforceUpdateAsync() + InvalidTestEntity.updater( + entity1, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'blah') + .enforceUpdateAsync() ).rejects.toThrowError('Excessive results from database adapter update'); }); it('throws after update of no rows', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); const entity1 = await enforceAsyncResult( - InvalidTestEntity.creator(vc).setField('id', 1).setField('name', 'hello').createAsync() + InvalidTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('id', 1) + .setField('name', 'hello') + .createAsync() + ); + await InvalidTestEntity.deleteAsync( + entity1, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') ); - await InvalidTestEntity.deleteAsync(entity1); await expect( - InvalidTestEntity.updater(entity1).setField('name', 'blah').enforceUpdateAsync() + InvalidTestEntity.updater( + entity1, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .setField('name', 'blah') + .enforceUpdateAsync() ).rejects.toThrowError('Empty results from database adapter update'); }); }); diff --git a/packages/entity-database-adapter-knex/src/__integration-tests__/errors-test.ts b/packages/entity-database-adapter-knex/src/__integration-tests__/errors-test.ts index 66205281..0bd6dd91 100644 --- a/packages/entity-database-adapter-knex/src/__integration-tests__/errors-test.ts +++ b/packages/entity-database-adapter-knex/src/__integration-tests__/errors-test.ts @@ -41,7 +41,10 @@ describe('postgres errors', () => { it('throws EntityDatabaseAdapterTransientError on Knex timeout', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); - await ErrorsTestEntity.creator(vc) + await ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .enforceCreateAsync(); @@ -60,16 +63,24 @@ describe('postgres errors', () => { const vc2 = new ViewerContext( createKnexIntegrationTestEntityCompanionProvider(shortTimeoutKnexInstance) ); - await expect(ErrorsTestEntity.loader(vc2).enforcing().loadByIDAsync(1)).rejects.toThrow( - EntityDatabaseAdapterTransientError - ); + await expect( + ErrorsTestEntity.loader( + vc2, + vc2.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) + .enforcing() + .loadByIDAsync(1) + ).rejects.toThrow(EntityDatabaseAdapterTransientError); await shortTimeoutKnexInstance.destroy(); }); it('throws EntityDatabaseAdapterNotNullConstraintError when not null is violated', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', null as any) .enforceCreateAsync() @@ -79,7 +90,10 @@ describe('postgres errors', () => { it('throws EntityDatabaseAdapterForeignKeyConstraintError when foreign key is violated', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .setField('fieldForeignKey', 2) @@ -90,13 +104,19 @@ describe('postgres errors', () => { it('throws EntityDatabaseAdapterUniqueConstraintError when primary key unique constraint is violated', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); - await ErrorsTestEntity.creator(vc) + await ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .enforceCreateAsync(); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .enforceCreateAsync() @@ -105,14 +125,20 @@ describe('postgres errors', () => { it('throws EntityDatabaseAdapterUniqueConstraintError when unique constraint is violated', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); - await ErrorsTestEntity.creator(vc) + await ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 2) .setField('fieldNonNull', 'hello') .setField('fieldUnique', 'hello') .enforceCreateAsync(); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .setField('fieldUnique', 'hello') @@ -123,7 +149,10 @@ describe('postgres errors', () => { it('throws EntityDatabaseAdapterCheckConstraintError when check constraint is violated', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .setField('checkLessThan5', 2) @@ -131,7 +160,10 @@ describe('postgres errors', () => { ).resolves.toBeTruthy(); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 2) .setField('fieldNonNull', 'hello') .setField('checkLessThan5', 10) @@ -142,7 +174,10 @@ describe('postgres errors', () => { it('throws EntityDatabaseAdapterExclusionConstraintError when exclusion constraint is violated', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .setField('fieldExclusion', 'what') @@ -150,7 +185,10 @@ describe('postgres errors', () => { ).resolves.toBeTruthy(); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 2) .setField('fieldNonNull', 'hello') .setField('fieldExclusion', 'what') @@ -161,7 +199,10 @@ describe('postgres errors', () => { it('throws EntityDatabaseAdapterUnknownError otherwise', async () => { const vc = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); await expect( - ErrorsTestEntity.creator(vc) + ErrorsTestEntity.creator( + vc, + vc.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('id', 1) .setField('fieldNonNull', 'hello') .setField('nonExistentColumn', 'what') diff --git a/packages/entity-example/src/__tests__/NoteEntity-test.ts b/packages/entity-example/src/__tests__/NoteEntity-test.ts index 8c073dee..dd5d6745 100644 --- a/packages/entity-example/src/__tests__/NoteEntity-test.ts +++ b/packages/entity-example/src/__tests__/NoteEntity-test.ts @@ -10,14 +10,20 @@ describe(NoteEntity, () => { const userId = uuidv4(); const viewerContext = new UserViewerContext(companionProvider, userId); - const createdEntityResult = await NoteEntity.creator(viewerContext) + const createdEntityResult = await NoteEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('userID', userId) .setField('body', 'image') .setField('title', 'page') .createAsync(); expect(createdEntityResult.ok).toBe(true); - const createdEntityResultImpersonate = await NoteEntity.creator(viewerContext) + const createdEntityResultImpersonate = await NoteEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('userID', uuidv4()) // a different user .setField('body', 'image') .setField('title', 'page') diff --git a/packages/entity-example/src/routers/notesRouter.ts b/packages/entity-example/src/routers/notesRouter.ts index a5653791..0199c157 100644 --- a/packages/entity-example/src/routers/notesRouter.ts +++ b/packages/entity-example/src/routers/notesRouter.ts @@ -19,7 +19,7 @@ router.get('/', async (ctx) => { const viewerContext = ctx.state.viewerContext; let notes: readonly NoteEntity[] = []; if (viewerContext.isUserViewerContext()) { - notes = await NoteEntity.loader(viewerContext) + notes = await NoteEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadManyByFieldEqualingAsync('userID', viewerContext.userID); } @@ -30,7 +30,10 @@ router.get('/', async (ctx) => { router.get('/:id', async (ctx) => { const viewerContext = ctx.state.viewerContext; - const noteResult = await NoteEntity.loader(viewerContext).loadByIDAsync(ctx.params['id']!); + const noteResult = await NoteEntity.loader( + viewerContext, + viewerContext.getQueryContext() + ).loadByIDAsync(ctx.params['id']!); if (!noteResult.ok) { ctx.throw(403, noteResult.reason); return; @@ -53,7 +56,7 @@ router.post('/', async (ctx) => { const { title, body } = ctx.request.body as any; - const createResult = await NoteEntity.creator(viewerContext) + const createResult = await NoteEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('userID', viewerContext.userID) .setField('title', title) .setField('body', body) @@ -72,44 +75,41 @@ router.put('/:id', async (ctx) => { const viewerContext = ctx.state.viewerContext; const { title, body } = ctx.request.body as any; - const noteLoadResult = await NoteEntity.loader(viewerContext).loadByIDAsync(ctx.params['id']!); - if (!noteLoadResult.ok) { - ctx.throw(403, noteLoadResult.reason); - return; - } - - const noteUpdateResult = await NoteEntity.updater(noteLoadResult.value) - .setField('title', title) - .setField('body', body) - .updateAsync(); - if (!noteUpdateResult.ok) { - ctx.throw(403, noteUpdateResult.reason); - return; + try { + const updatedNote = await viewerContext.runInTransactionAsync(async (queryContext) => { + const note = await NoteEntity.loader(viewerContext, queryContext) + .enforcing() + .loadByIDAsync(ctx.params['id']!); + + return await NoteEntity.updater(note, queryContext) + .setField('title', title) + .setField('body', body) + .enforceUpdateAsync(); + }); + ctx.body = { + note: updatedNote.getAllFields(), + }; + } catch (e: any) { + ctx.throw(403, e.message); } - - ctx.body = { - note: noteUpdateResult.value.getAllFields(), - }; }); router.delete('/:id', async (ctx) => { const viewerContext = ctx.state.viewerContext; - const noteLoadResult = await NoteEntity.loader(viewerContext).loadByIDAsync(ctx.params['id']!); - if (!noteLoadResult.ok) { - ctx.throw(403, noteLoadResult.reason); - return; - } - - const noteDeleteResult = await NoteEntity.deleteAsync(noteLoadResult.value); - if (!noteDeleteResult.ok) { - ctx.throw(403, noteDeleteResult.reason); - return; + try { + await viewerContext.runInTransactionAsync(async (queryContext) => { + const note = await NoteEntity.loader(viewerContext, queryContext) + .enforcing() + .loadByIDAsync(ctx.params['id']!); + await NoteEntity.enforceDeleteAsync(note, queryContext); + }); + ctx.body = { + status: 'ok', + }; + } catch (e: any) { + ctx.throw(403, e.message); } - - ctx.body = { - status: 'ok', - }; }); export default router; diff --git a/packages/entity-example/src/schema.ts b/packages/entity-example/src/schema.ts index c050b210..a4a30b95 100644 --- a/packages/entity-example/src/schema.ts +++ b/packages/entity-example/src/schema.ts @@ -46,14 +46,16 @@ export const resolvers: IResolvers = { return viewerContext.userID; }, async noteByID(_root, args, { viewerContext }) { - return await NoteEntity.loader(viewerContext).enforcing().loadByIDAsync(args.id); + return await NoteEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDAsync(args.id); }, } as IObjectTypeResolver, User: { id: (root) => root, async notes(root, _args, { viewerContext }) { - return await NoteEntity.loader(viewerContext) + return await NoteEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadManyByFieldEqualingAsync('userID', root); }, @@ -72,27 +74,31 @@ export const resolvers: IResolvers = { throw new Error('not logged in'); } - return await NoteEntity.creator(viewerContext) + return await NoteEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('userID', viewerContext.userID) .setField('title', args.note.title) .setField('body', args.note.body) .enforceCreateAsync(); }, async updateNote(_root, args, { viewerContext }) { - const existingNote = await NoteEntity.loader(viewerContext) - .enforcing() - .loadByIDAsync(args.id); - return await NoteEntity.updater(existingNote) - .setField('title', args.note.title) - .setField('body', args.note.body) - .enforceUpdateAsync(); + return await viewerContext.runInTransactionAsync(async (queryContext) => { + const existingNote = await NoteEntity.loader(viewerContext, queryContext) + .enforcing() + .loadByIDAsync(args.id); + return await NoteEntity.updater(existingNote, queryContext) + .setField('title', args.note.title) + .setField('body', args.note.body) + .enforceUpdateAsync(); + }); }, async deleteNote(_root, args, { viewerContext }) { - const existingNote = await NoteEntity.loader(viewerContext) - .enforcing() - .loadByIDAsync(args.id); - await NoteEntity.enforceDeleteAsync(existingNote); - return existingNote; + return await viewerContext.runInTransactionAsync(async (queryContext) => { + const existingNote = await NoteEntity.loader(viewerContext, queryContext) + .enforcing() + .loadByIDAsync(args.id); + await NoteEntity.enforceDeleteAsync(existingNote, queryContext); + return existingNote; + }); }, } as IObjectTypeResolver, }; diff --git a/packages/entity-example/src/viewerContexts.ts b/packages/entity-example/src/viewerContexts.ts index f9bb35c9..36af7cc7 100644 --- a/packages/entity-example/src/viewerContexts.ts +++ b/packages/entity-example/src/viewerContexts.ts @@ -1,4 +1,9 @@ -import { ViewerContext, EntityCompanionProvider } from '@expo/entity'; +import { + ViewerContext, + EntityCompanionProvider, + EntityQueryContext, + EntityTransactionalQueryContext, +} from '@expo/entity'; /** * A base class for better typing Entities and Privacy Policies specific to this application. @@ -11,6 +16,16 @@ export abstract class ExampleViewerContext extends ViewerContext { isAnonymousViewerContext(): this is AnonymousViewerContext { return this instanceof AnonymousViewerContext; } + + public getQueryContext(): EntityQueryContext { + return super.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres'); + } + + public async runInTransactionAsync( + transactionScope: (queryContext: EntityTransactionalQueryContext) => Promise + ): Promise { + return await super.runInTransactionForDatabaseAdaptorFlavorAsync('postgres', transactionScope); + } } /** diff --git a/packages/entity-full-integration-tests/src/__integration-tests__/EntityCacheInconsistency-test.ts b/packages/entity-full-integration-tests/src/__integration-tests__/EntityCacheInconsistency-test.ts index 064f06d4..6966a9fa 100644 --- a/packages/entity-full-integration-tests/src/__integration-tests__/EntityCacheInconsistency-test.ts +++ b/packages/entity-full-integration-tests/src/__integration-tests__/EntityCacheInconsistency-test.ts @@ -1,6 +1,5 @@ import { EntityPrivacyPolicy, - ViewerContext, AlwaysAllowPrivacyPolicyRule, Entity, EntityCompanionDefinition, @@ -14,6 +13,7 @@ import { knex, Knex } from 'knex'; import nullthrows from 'nullthrows'; import { URL } from 'url'; +import TestViewerContext from './entities/TestViewerContext'; import { createFullIntegrationTestEntityCompanionProvider } from '../testfixtures/createFullIntegrationTestEntityCompanionProvider'; interface TestFields { @@ -25,28 +25,28 @@ interface TestFields { class TestEntityPrivacyPolicy extends EntityPrivacyPolicy< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity > { protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -class TestEntity extends Entity { +class TestEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, TestEntityPrivacyPolicy > { @@ -136,18 +136,20 @@ describe('Entity cache inconsistency', () => { }); test('lots of updates in long-ish running transactions', async () => { - const viewerContext = new ViewerContext( + const viewerContext = new TestViewerContext( createFullIntegrationTestEntityCompanionProvider(knexInstance, genericRedisCacheContext) ); - const entity1 = await TestEntity.creator(viewerContext) + const entity1 = await TestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('other_string', 'hello') .setField('third_string', 'initial') .enforceCreateAsync(); // put entities in cache and dataloader - await TestEntity.loader(viewerContext).enforcing().loadByIDAsync(entity1.getID()); - await TestEntity.loader(viewerContext) + await TestEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDAsync(entity1.getID()); + await TestEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('other_string', 'hello'); @@ -164,15 +166,17 @@ describe('Entity cache inconsistency', () => { await Promise.all([ // do a load after the transaction below updates the entity but before transaction commits to ensure // that the cache is cleared after the transaction commits rather than in the middle where the changes - // may not be visible by other requests (ViewerContexts) which would cache the incorrect + // may not be visible by other requests (TestViewerContexts) which would cache the incorrect // value during the read-through cache. (async () => { await barrier1; - const viewerContextInternal = new ViewerContext( + const viewerContextInternal = new TestViewerContext( createFullIntegrationTestEntityCompanionProvider(knexInstance, genericRedisCacheContext) ); - await TestEntity.loader(viewerContextInternal).enforcing().loadByIDAsync(entity1.getID()); + await TestEntity.loader(viewerContextInternal, viewerContextInternal.getQueryContext()) + .enforcing() + .loadByIDAsync(entity1.getID()); openBarrier2!(); })(), @@ -192,14 +196,20 @@ describe('Entity cache inconsistency', () => { ]); // ensure cache consistency - const viewerContextLast = new ViewerContext( + const viewerContextLast = new TestViewerContext( createFullIntegrationTestEntityCompanionProvider(knexInstance, genericRedisCacheContext) ); - const loadedById = await TestEntity.loader(viewerContextLast) + const loadedById = await TestEntity.loader( + viewerContextLast, + viewerContextLast.getQueryContext() + ) .enforcing() .loadByIDAsync(entity1.getID()); - const loadedByField = await TestEntity.loader(viewerContextLast) + const loadedByField = await TestEntity.loader( + viewerContextLast, + viewerContextLast.getQueryContext() + ) .enforcing() .loadByFieldEqualingAsync('other_string', 'hello'); diff --git a/packages/entity-full-integration-tests/src/__integration-tests__/EntityEdgesIntegration-test.ts b/packages/entity-full-integration-tests/src/__integration-tests__/EntityEdgesIntegration-test.ts index 6eada1a5..771c7714 100644 --- a/packages/entity-full-integration-tests/src/__integration-tests__/EntityEdgesIntegration-test.ts +++ b/packages/entity-full-integration-tests/src/__integration-tests__/EntityEdgesIntegration-test.ts @@ -1,4 +1,3 @@ -import { ViewerContext } from '@expo/entity'; import { GenericRedisCacheContext } from '@expo/entity-cache-adapter-redis'; import Redis from 'ioredis'; import { knex, Knex } from 'knex'; @@ -7,6 +6,7 @@ import { URL } from 'url'; import ChildEntity from './entities/ChildEntity'; import ParentEntity from './entities/ParentEntity'; +import TestViewerContext from './entities/TestViewerContext'; import { createFullIntegrationTestEntityCompanionProvider } from '../testfixtures/createFullIntegrationTestEntityCompanionProvider'; async function createOrTruncatePostgresTables(knex: Knex): Promise { @@ -75,32 +75,41 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { describe('EntityEdgeDeletionBehavior.INVALIDATE_CACHE', () => { it('invalidates the cache', async () => { - const viewerContext = new ViewerContext( + const viewerContext = new TestViewerContext( createFullIntegrationTestEntityCompanionProvider(knexInstance, genericRedisCacheContext) ); - const parent = await ParentEntity.creator(viewerContext).enforceCreateAsync(); - const child = await ChildEntity.creator(viewerContext) + const parent = await ParentEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const child = await ChildEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_id', parent.getID()) .enforceCreateAsync(); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.not.toBeNull(); await expect( - ChildEntity.loader(viewerContext) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_id', parent.getID()) ).resolves.not.toBeNull(); - await ParentEntity.enforceDeleteAsync(parent); + await ParentEntity.enforceDeleteAsync(parent, viewerContext.getQueryContext()); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.toBeNull(); await expect( - ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID()) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(child.getID()) ).resolves.toBeNull(); }); }); diff --git a/packages/entity-full-integration-tests/src/__integration-tests__/EntitySelfReferentialEdgesIntegration-test.ts b/packages/entity-full-integration-tests/src/__integration-tests__/EntitySelfReferentialEdgesIntegration-test.ts index d0dff765..864bd92a 100644 --- a/packages/entity-full-integration-tests/src/__integration-tests__/EntitySelfReferentialEdgesIntegration-test.ts +++ b/packages/entity-full-integration-tests/src/__integration-tests__/EntitySelfReferentialEdgesIntegration-test.ts @@ -15,6 +15,7 @@ import nullthrows from 'nullthrows'; import { URL } from 'url'; import { v4 as uuidv4 } from 'uuid'; +import TestViewerContext from './entities/TestViewerContext'; import { createFullIntegrationTestEntityCompanionProvider } from '../testfixtures/createFullIntegrationTestEntityCompanionProvider'; interface CategoryFields { @@ -217,42 +218,53 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { edgeDeletionBehavior ); - const viewerContext = new ViewerContext( + const viewerContext = new TestViewerContext( createFullIntegrationTestEntityCompanionProvider(knexInstance, genericRedisCacheContext) ); - const category1 = await CategoryEntity.creator(viewerContext).enforceCreateAsync(); - const other1 = await OtherEntity.creator(viewerContext) + const category1 = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const other1 = await OtherEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_category_id', category1.getID()) .enforceCreateAsync(); - await CategoryEntity.updater(category1) + await CategoryEntity.updater(category1, viewerContext.getQueryContext()) .setField('parent_other_id', other1.getID()) .enforceUpdateAsync(); - await CategoryEntity.enforceDeleteAsync(category1); + await CategoryEntity.enforceDeleteAsync(category1, viewerContext.getQueryContext()); if (edgeDeletionBehavior === EntityEdgeDeletionBehavior.SET_NULL) { await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(category1.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(category1.getID()) ).resolves.toBeNull(); - const otherLoaded = await OtherEntity.loader(viewerContext) + const otherLoaded = await OtherEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDNullableAsync(other1.getID()); expect(otherLoaded?.getField('parent_category_id')).toBeNull(); } else if (edgeDeletionBehavior === EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY) { await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(category1.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(category1.getID()) ).resolves.toBeNull(); - const otherLoaded = await OtherEntity.loader(viewerContext) + const otherLoaded = await OtherEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDNullableAsync(other1.getID()); expect(otherLoaded?.getField('parent_category_id')).toBeNull(); } else { await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(category1.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(category1.getID()) ).resolves.toBeNull(); await expect( - OtherEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(other1.getID()) + OtherEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(other1.getID()) ).resolves.toBeNull(); } }); diff --git a/packages/entity-full-integration-tests/src/__integration-tests__/entities/ChildEntity.ts b/packages/entity-full-integration-tests/src/__integration-tests__/entities/ChildEntity.ts index 4da93213..53d8a11b 100644 --- a/packages/entity-full-integration-tests/src/__integration-tests__/entities/ChildEntity.ts +++ b/packages/entity-full-integration-tests/src/__integration-tests__/entities/ChildEntity.ts @@ -6,36 +6,42 @@ import { EntityEdgeDeletionBehavior, EntityPrivacyPolicy, UUIDField, - ViewerContext, } from '@expo/entity'; import ParentEntity from './ParentEntity'; +import TestViewerContext from './TestViewerContext'; interface ChildFields { id: string; parent_id: string; } -class TestEntityPrivacyPolicy extends EntityPrivacyPolicy { +class TestEntityPrivacyPolicy extends EntityPrivacyPolicy< + any, + string, + TestViewerContext, + any, + any +> { protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -export default class ChildEntity extends Entity { +export default class ChildEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< ChildFields, string, - ViewerContext, + TestViewerContext, ChildEntity, TestEntityPrivacyPolicy > { diff --git a/packages/entity-full-integration-tests/src/__integration-tests__/entities/ParentEntity.ts b/packages/entity-full-integration-tests/src/__integration-tests__/entities/ParentEntity.ts index 4c6952d7..2630ea4a 100644 --- a/packages/entity-full-integration-tests/src/__integration-tests__/entities/ParentEntity.ts +++ b/packages/entity-full-integration-tests/src/__integration-tests__/entities/ParentEntity.ts @@ -5,35 +5,41 @@ import { EntityConfiguration, EntityPrivacyPolicy, UUIDField, - ViewerContext, } from '@expo/entity'; import ChildEntity from './ChildEntity'; +import TestViewerContext from './TestViewerContext'; interface ParentFields { id: string; } -class TestEntityPrivacyPolicy extends EntityPrivacyPolicy { +class TestEntityPrivacyPolicy extends EntityPrivacyPolicy< + any, + string, + TestViewerContext, + any, + any +> { protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -export default class ParentEntity extends Entity { +export default class ParentEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< ParentFields, string, - ViewerContext, + TestViewerContext, ParentEntity, TestEntityPrivacyPolicy > { diff --git a/packages/entity-full-integration-tests/src/__integration-tests__/entities/TestViewerContext.ts b/packages/entity-full-integration-tests/src/__integration-tests__/entities/TestViewerContext.ts new file mode 100644 index 00000000..9482fdf9 --- /dev/null +++ b/packages/entity-full-integration-tests/src/__integration-tests__/entities/TestViewerContext.ts @@ -0,0 +1,13 @@ +import { EntityQueryContext, EntityTransactionalQueryContext, ViewerContext } from '@expo/entity'; + +export default class TestViewerContext extends ViewerContext { + public getQueryContext(): EntityQueryContext { + return super.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres'); + } + + public async runInTransactionAsync( + transactionScope: (queryContext: EntityTransactionalQueryContext) => Promise + ): Promise { + return await super.runInTransactionForDatabaseAdaptorFlavorAsync('postgres', transactionScope); + } +} diff --git a/packages/entity-secondary-cache-local-memory/src/__tests__/LocalMemorySecondaryEntityCache-test.ts b/packages/entity-secondary-cache-local-memory/src/__tests__/LocalMemorySecondaryEntityCache-test.ts index 8637f6ce..f63a8546 100644 --- a/packages/entity-secondary-cache-local-memory/src/__tests__/LocalMemorySecondaryEntityCache-test.ts +++ b/packages/entity-secondary-cache-local-memory/src/__tests__/LocalMemorySecondaryEntityCache-test.ts @@ -172,7 +172,10 @@ describe(LocalMemorySecondaryEntityCache, () => { it('Loads through secondary loader, caches, and invalidates', async () => { const viewerContext = new TestViewerContext(createTestEntityCompanionProvider()); - const createdEntity = await LocalMemoryTestEntity.creator(viewerContext) + const createdEntity = await LocalMemoryTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'wat') .enforceCreateAsync(); @@ -181,7 +184,10 @@ describe(LocalMemorySecondaryEntityCache, () => { localMemoryTestEntityConfiguration, GenericLocalMemoryCacher.createLRUCache({}) ), - LocalMemoryTestEntity.loader(viewerContext) + LocalMemoryTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) ); const loadParams = { id: createdEntity.getID() }; @@ -217,7 +223,10 @@ describe(LocalMemorySecondaryEntityCache, () => { localMemoryTestEntityConfiguration, GenericLocalMemoryCacher.createLRUCache({}) ), - LocalMemoryTestEntity.loader(viewerContext) + LocalMemoryTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) ); const loadParams = { id: FAKE_ID }; diff --git a/packages/entity-secondary-cache-redis/src/__integration-tests__/RedisSecondaryEntityCache-integration-test.ts b/packages/entity-secondary-cache-redis/src/__integration-tests__/RedisSecondaryEntityCache-integration-test.ts index bcf2ce99..24cdd877 100644 --- a/packages/entity-secondary-cache-redis/src/__integration-tests__/RedisSecondaryEntityCache-integration-test.ts +++ b/packages/entity-secondary-cache-redis/src/__integration-tests__/RedisSecondaryEntityCache-integration-test.ts @@ -79,7 +79,10 @@ describe(RedisSecondaryEntityCache, () => { createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext) ); - const createdEntity = await RedisTestEntity.creator(viewerContext) + const createdEntity = await RedisTestEntity.creator( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) .setField('name', 'wat') .enforceCreateAsync(); @@ -89,7 +92,10 @@ describe(RedisSecondaryEntityCache, () => { genericRedisCacheContext, (loadParams) => `test-key-${loadParams.id}` ), - RedisTestEntity.loader(viewerContext) + RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) ); const loadParams = { id: createdEntity.getID() }; @@ -128,7 +134,10 @@ describe(RedisSecondaryEntityCache, () => { genericRedisCacheContext, (loadParams) => `test-key-${loadParams.id}` ), - RedisTestEntity.loader(viewerContext) + RedisTestEntity.loader( + viewerContext, + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres') + ) ); const loadParams = { id: FAKE_ID }; diff --git a/packages/entity/src/Entity.ts b/packages/entity/src/Entity.ts index 280d37fe..17d47344 100644 --- a/packages/entity/src/Entity.ts +++ b/packages/entity/src/Entity.ts @@ -61,10 +61,7 @@ export default abstract class Entity< TMSelectedFields >, viewerContext: TMViewerContext2, - queryContext: EntityQueryContext = viewerContext - .getViewerScopedEntityCompanionForClass(this) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): CreateMutator { return viewerContext .getViewerScopedEntityCompanionForClass(this) @@ -101,11 +98,7 @@ export default abstract class Entity< TMSelectedFields >, existingEntity: TMEntity, - queryContext: EntityQueryContext = existingEntity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(this) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): UpdateMutator { return existingEntity .getViewerContext() @@ -142,11 +135,7 @@ export default abstract class Entity< TMSelectedFields >, existingEntity: TMEntity, - queryContext: EntityQueryContext = existingEntity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(this) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise> { return existingEntity .getViewerContext() @@ -184,11 +173,7 @@ export default abstract class Entity< TMSelectedFields >, existingEntity: TMEntity, - queryContext: EntityQueryContext = existingEntity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(this) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise { return existingEntity .getViewerContext() @@ -237,11 +222,7 @@ export default abstract class Entity< TMSelectedFields >, existingEntity: TMEntity, - queryContext: EntityQueryContext = existingEntity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(this) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise { const companion = existingEntity .getViewerContext() @@ -291,11 +272,7 @@ export default abstract class Entity< TMSelectedFields >, existingEntity: TMEntity, - queryContext: EntityQueryContext = existingEntity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(this) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise { const companion = existingEntity .getViewerContext() diff --git a/packages/entity/src/EntityAssociationLoader.ts b/packages/entity/src/EntityAssociationLoader.ts index ee42215e..092266d6 100644 --- a/packages/entity/src/EntityAssociationLoader.ts +++ b/packages/entity/src/EntityAssociationLoader.ts @@ -55,11 +55,7 @@ export default class EntityAssociationLoader< TAssociatedPrivacyPolicy, TAssociatedSelectedFields >, - queryContext: EntityQueryContext = this.entity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(associatedEntityClass) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise< Result > { @@ -117,11 +113,7 @@ export default class EntityAssociationLoader< TAssociatedSelectedFields >, associatedEntityFieldContainingThisID: keyof Pick, - queryContext: EntityQueryContext = this.entity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(associatedEntityClass) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise[]> { const thisID = this.entity.getID(); const loader = this.entity @@ -171,11 +163,7 @@ export default class EntityAssociationLoader< TAssociatedSelectedFields >, associatedEntityLookupByField: keyof Pick, - queryContext: EntityQueryContext = this.entity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(associatedEntityClass) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise | null> { const associatedFieldValue = this.entity.getField(fieldIdentifyingAssociatedEntity); if (!associatedFieldValue) { @@ -228,11 +216,7 @@ export default class EntityAssociationLoader< TAssociatedSelectedFields >, associatedEntityLookupByField: keyof Pick, - queryContext: EntityQueryContext = this.entity - .getViewerContext() - .getViewerScopedEntityCompanionForClass(associatedEntityClass) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): Promise[]> { const associatedFieldValue = this.entity.getField(fieldIdentifyingAssociatedEntity); if (!associatedFieldValue) { @@ -281,7 +265,7 @@ export default class EntityAssociationLoader< TSelectedFields2 > ], - queryContext?: EntityQueryContext + queryContext: EntityQueryContext ): Promise | null>; /** @@ -336,7 +320,7 @@ export default class EntityAssociationLoader< TSelectedFields3 > ], - queryContext?: EntityQueryContext + queryContext: EntityQueryContext ): Promise | null>; /** @@ -412,7 +396,7 @@ export default class EntityAssociationLoader< TSelectedFields4 > ], - queryContext?: EntityQueryContext + queryContext: EntityQueryContext ): Promise | null>; /** @@ -423,12 +407,12 @@ export default class EntityAssociationLoader< */ async loadAssociatedEntityThroughAsync( loadDirectives: EntityLoadThroughDirective[], - queryContext?: EntityQueryContext + queryContext: EntityQueryContext ): Promise> | null>; async loadAssociatedEntityThroughAsync( loadDirectives: EntityLoadThroughDirective[], - queryContext?: EntityQueryContext + queryContext: EntityQueryContext ): Promise> | null> { let currentEntity: ReadonlyEntity = this.entity; for (const loadDirective of loadDirectives) { diff --git a/packages/entity/src/EntityQueryContextProvider.ts b/packages/entity/src/EntityQueryContextProvider.ts index 42740fe9..59779d1c 100644 --- a/packages/entity/src/EntityQueryContextProvider.ts +++ b/packages/entity/src/EntityQueryContextProvider.ts @@ -12,7 +12,7 @@ export default abstract class EntityQueryContextProvider { /** * Vend a regular (non-transactional) entity query context. */ - public getQueryContext(): EntityNonTransactionalQueryContext { + public getNonTransactionalQueryContext(): EntityNonTransactionalQueryContext { return new EntityNonTransactionalQueryContext(this.getQueryInterface(), this); } diff --git a/packages/entity/src/ReadonlyEntity.ts b/packages/entity/src/ReadonlyEntity.ts index dec07d73..61b184bb 100644 --- a/packages/entity/src/ReadonlyEntity.ts +++ b/packages/entity/src/ReadonlyEntity.ts @@ -148,10 +148,7 @@ export default abstract class ReadonlyEntity< TMSelectedFields >, viewerContext: TMViewerContext2, - queryContext: EntityQueryContext = viewerContext - .getViewerScopedEntityCompanionForClass(this) - .getQueryContextProvider() - .getQueryContext() + queryContext: EntityQueryContext ): EntityLoader { return viewerContext .getViewerScopedEntityCompanionForClass(this) diff --git a/packages/entity/src/ViewerContext.ts b/packages/entity/src/ViewerContext.ts index 3986fd03..2cbe6406 100644 --- a/packages/entity/src/ViewerContext.ts +++ b/packages/entity/src/ViewerContext.ts @@ -2,7 +2,7 @@ import { IEntityClass } from './Entity'; import EntityCompanionProvider, { DatabaseAdapterFlavor } from './EntityCompanionProvider'; import EntityPrivacyPolicy from './EntityPrivacyPolicy'; import { - EntityQueryContext, + EntityNonTransactionalQueryContext, EntityTransactionalQueryContext, TransactionConfig, } from './EntityQueryContext'; @@ -67,12 +67,12 @@ export default class ViewerContext { * Get the regular (non-transactional) query context for a database adaptor flavor. * @param databaseAdaptorFlavor - database adaptor flavor */ - getQueryContextForDatabaseAdaptorFlavor( + getNonTransactionalQueryContextForDatabaseAdaptorFlavor( databaseAdaptorFlavor: DatabaseAdapterFlavor - ): EntityQueryContext { + ): EntityNonTransactionalQueryContext { return this.entityCompanionProvider .getQueryContextProviderForDatabaseAdaptorFlavor(databaseAdaptorFlavor) - .getQueryContext(); + .getNonTransactionalQueryContext(); } /** @@ -88,7 +88,7 @@ export default class ViewerContext { ): Promise { return await this.entityCompanionProvider .getQueryContextProviderForDatabaseAdaptorFlavor(databaseAdaptorFlavor) - .getQueryContext() + .getNonTransactionalQueryContext() .runInTransactionIfNotInTransactionAsync(transactionScope, transactionConfig); } } diff --git a/packages/entity/src/__tests__/Entity-test.ts b/packages/entity/src/__tests__/Entity-test.ts index 74fde329..484ad318 100644 --- a/packages/entity/src/__tests__/Entity-test.ts +++ b/packages/entity/src/__tests__/Entity-test.ts @@ -4,25 +4,27 @@ import EntityConfiguration from '../EntityConfiguration'; import { UUIDField } from '../EntityFields'; import { CreateMutator, UpdateMutator } from '../EntityMutator'; import EntityPrivacyPolicy from '../EntityPrivacyPolicy'; -import ViewerContext from '../ViewerContext'; import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule'; import AlwaysDenyPrivacyPolicyRule from '../rules/AlwaysDenyPrivacyPolicyRule'; import SimpleTestEntity from '../testfixtures/SimpleTestEntity'; +import TestViewerContext from '../testfixtures/TestViewerContext'; import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider'; describe(Entity, () => { describe('creator', () => { it('creates a new CreateMutator', () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); - expect(SimpleTestEntity.creator(viewerContext)).toBeInstanceOf(CreateMutator); + const viewerContext = new TestViewerContext(companionProvider); + expect( + SimpleTestEntity.creator(viewerContext, viewerContext.getQueryContext()) + ).toBeInstanceOf(CreateMutator); }); }); describe('updater', () => { it('creates a new UpdateMutator', () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); const data = { id: 'what', }; @@ -32,14 +34,16 @@ describe(Entity, () => { databaseFields: data, selectedFields: data, }); - expect(SimpleTestEntity.updater(testEntity)).toBeInstanceOf(UpdateMutator); + expect(SimpleTestEntity.updater(testEntity, viewerContext.getQueryContext())).toBeInstanceOf( + UpdateMutator + ); }); }); describe('canViewerUpdateAsync', () => { it('appropriately executes update privacy policy', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); const data = { id: 'what', }; @@ -49,13 +53,16 @@ describe(Entity, () => { databaseFields: data, selectedFields: data, }); - const canViewerUpdate = await SimpleTestDenyDeleteEntity.canViewerUpdateAsync(testEntity); + const canViewerUpdate = await SimpleTestDenyDeleteEntity.canViewerUpdateAsync( + testEntity, + viewerContext.getQueryContext() + ); expect(canViewerUpdate).toBe(true); }); it('denies when policy denies', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); const data = { id: 'what', }; @@ -65,7 +72,10 @@ describe(Entity, () => { databaseFields: data, selectedFields: data, }); - const canViewerUpdate = await SimpleTestDenyUpdateEntity.canViewerUpdateAsync(testEntity); + const canViewerUpdate = await SimpleTestDenyUpdateEntity.canViewerUpdateAsync( + testEntity, + viewerContext.getQueryContext() + ); expect(canViewerUpdate).toBe(false); }); }); @@ -73,7 +83,7 @@ describe(Entity, () => { describe('canViewerDeleteAsync', () => { it('appropriately executes update privacy policy', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); const data = { id: 'what', }; @@ -83,13 +93,16 @@ describe(Entity, () => { databaseFields: data, selectedFields: data, }); - const canViewerDelete = await SimpleTestDenyUpdateEntity.canViewerDeleteAsync(testEntity); + const canViewerDelete = await SimpleTestDenyUpdateEntity.canViewerDeleteAsync( + testEntity, + viewerContext.getQueryContext() + ); expect(canViewerDelete).toBe(true); }); it('denies when policy denies', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); const data = { id: 'what', }; @@ -99,7 +112,10 @@ describe(Entity, () => { databaseFields: data, selectedFields: data, }); - const canViewerDelete = await SimpleTestDenyDeleteEntity.canViewerDeleteAsync(testEntity); + const canViewerDelete = await SimpleTestDenyDeleteEntity.canViewerDeleteAsync( + testEntity, + viewerContext.getQueryContext() + ); expect(canViewerDelete).toBe(false); }); }); @@ -124,14 +140,14 @@ const testEntityConfiguration = new EntityConfiguration({ class SimpleTestDenyUpdateEntityPrivacyPolicy extends EntityPrivacyPolicy< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyUpdateEntity > { protected override readonly readRules = [ new AlwaysAllowPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyUpdateEntity >(), ]; @@ -139,7 +155,7 @@ class SimpleTestDenyUpdateEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyUpdateEntity >(), ]; @@ -147,7 +163,7 @@ class SimpleTestDenyUpdateEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysDenyPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyUpdateEntity >(), ]; @@ -155,7 +171,7 @@ class SimpleTestDenyUpdateEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyUpdateEntity >(), ]; @@ -164,14 +180,14 @@ class SimpleTestDenyUpdateEntityPrivacyPolicy extends EntityPrivacyPolicy< class SimpleTestDenyDeleteEntityPrivacyPolicy extends EntityPrivacyPolicy< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyDeleteEntity > { protected override readonly readRules = [ new AlwaysAllowPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyDeleteEntity >(), ]; @@ -179,7 +195,7 @@ class SimpleTestDenyDeleteEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyDeleteEntity >(), ]; @@ -187,7 +203,7 @@ class SimpleTestDenyDeleteEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyDeleteEntity >(), ]; @@ -195,17 +211,17 @@ class SimpleTestDenyDeleteEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysDenyPrivacyPolicyRule< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyDeleteEntity >(), ]; } -class SimpleTestDenyUpdateEntity extends Entity { +class SimpleTestDenyUpdateEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyUpdateEntity, SimpleTestDenyUpdateEntityPrivacyPolicy > { @@ -217,11 +233,11 @@ class SimpleTestDenyUpdateEntity extends Entity { +class SimpleTestDenyDeleteEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< TestEntityFields, string, - ViewerContext, + TestViewerContext, SimpleTestDenyDeleteEntity, SimpleTestDenyDeleteEntityPrivacyPolicy > { diff --git a/packages/entity/src/__tests__/EntityAssociationLoader-test.ts b/packages/entity/src/__tests__/EntityAssociationLoader-test.ts index f54b1df2..aa44680b 100644 --- a/packages/entity/src/__tests__/EntityAssociationLoader-test.ts +++ b/packages/entity/src/__tests__/EntityAssociationLoader-test.ts @@ -14,20 +14,24 @@ describe(EntityAssociationLoader, () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); const testOtherEntity = await enforceAsyncResult( - TestEntity.creator(viewerContext).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()).createAsync() ); const testEntity = await enforceAsyncResult( - TestEntity.creator(viewerContext) + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('stringField', testOtherEntity.getID()) .createAsync() ); const loadedOther = await enforceAsyncResult( - testEntity.associationLoader().loadAssociatedEntityAsync('stringField', TestEntity) + testEntity + .associationLoader() + .loadAssociatedEntityAsync('stringField', TestEntity, viewerContext.getQueryContext()) ); expect(loadedOther.getID()).toEqual(testOtherEntity.getID()); const loadedOther2 = await enforceAsyncResult( - testEntity.associationLoader().loadAssociatedEntityAsync('nullableField', TestEntity) + testEntity + .associationLoader() + .loadAssociatedEntityAsync('nullableField', TestEntity, viewerContext.getQueryContext()) ); expect(loadedOther2).toBeNull(); }); @@ -37,15 +41,27 @@ describe(EntityAssociationLoader, () => { it('loads many associated entities referencing this entity', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const testEntity = await enforceAsyncResult(TestEntity.creator(viewerContext).createAsync()); + const testEntity = await enforceAsyncResult( + TestEntity.creator(viewerContext, viewerContext.getQueryContext()).createAsync() + ); const testOtherEntity1 = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('stringField', testEntity.getID()) + .createAsync() ); const testOtherEntity2 = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('stringField', testEntity.getID()) + .createAsync() ); const loaded = await enforceResultsAsync( - testEntity.associationLoader().loadManyAssociatedEntitiesAsync(TestEntity, 'stringField') + testEntity + .associationLoader() + .loadManyAssociatedEntitiesAsync( + TestEntity, + 'stringField', + viewerContext.getQueryContext() + ) ); expect(loaded).toHaveLength(2); expect(loaded.find((e) => e.getID() === testOtherEntity1.getID())).not.toBeUndefined(); @@ -58,16 +74,21 @@ describe(EntityAssociationLoader, () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); const testOtherEntity = await enforceAsyncResult( - TestEntity.creator(viewerContext).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()).createAsync() ); const testEntity = await enforceAsyncResult( - TestEntity.creator(viewerContext) + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('stringField', testOtherEntity.getID()) .createAsync() ); const loadedOtherResult = await testEntity .associationLoader() - .loadAssociatedEntityByFieldEqualingAsync('stringField', TestEntity, 'customIdField'); + .loadAssociatedEntityByFieldEqualingAsync( + 'stringField', + TestEntity, + 'customIdField', + viewerContext.getQueryContext() + ); expect(loadedOtherResult?.enforceValue().getID()).toEqual(testOtherEntity.getID()); }); @@ -75,11 +96,18 @@ describe(EntityAssociationLoader, () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); const testEntity = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('stringField', uuidv4()).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('stringField', uuidv4()) + .createAsync() ); const loadedOtherResult = await testEntity .associationLoader() - .loadAssociatedEntityByFieldEqualingAsync('stringField', TestEntity, 'customIdField'); + .loadAssociatedEntityByFieldEqualingAsync( + 'stringField', + TestEntity, + 'customIdField', + viewerContext.getQueryContext() + ); expect(loadedOtherResult).toBeNull(); }); @@ -87,11 +115,18 @@ describe(EntityAssociationLoader, () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); const testEntity = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('stringField', 'blah').createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('stringField', 'blah') + .createAsync() ); const loadedOtherResult = await testEntity .associationLoader() - .loadAssociatedEntityByFieldEqualingAsync('nullableField', TestEntity, 'customIdField'); + .loadAssociatedEntityByFieldEqualingAsync( + 'nullableField', + TestEntity, + 'customIdField', + viewerContext.getQueryContext() + ); expect(loadedOtherResult).toBeNull(); }); }); @@ -100,12 +135,18 @@ describe(EntityAssociationLoader, () => { it('loads many associated entities by field equaling', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const testEntity = await enforceAsyncResult(TestEntity.creator(viewerContext).createAsync()); + const testEntity = await enforceAsyncResult( + TestEntity.creator(viewerContext, viewerContext.getQueryContext()).createAsync() + ); const testOtherEntity1 = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('stringField', testEntity.getID()) + .createAsync() ); const testOtherEntity2 = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('stringField', testEntity.getID()) + .createAsync() ); const loaded = await enforceResultsAsync( testEntity @@ -113,7 +154,8 @@ describe(EntityAssociationLoader, () => { .loadManyAssociatedEntitiesByFieldEqualingAsync( 'customIdField', TestEntity, - 'stringField' + 'stringField', + viewerContext.getQueryContext() ) ); expect(loaded).toHaveLength(2); @@ -124,14 +166,17 @@ describe(EntityAssociationLoader, () => { it('returns empty results when field being queried by is null', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const testEntity = await enforceAsyncResult(TestEntity.creator(viewerContext).createAsync()); + const testEntity = await enforceAsyncResult( + TestEntity.creator(viewerContext, viewerContext.getQueryContext()).createAsync() + ); const loaded = await enforceResultsAsync( testEntity .associationLoader() .loadManyAssociatedEntitiesByFieldEqualingAsync( 'nullableField', TestEntity, - 'stringField' + 'stringField', + viewerContext.getQueryContext() ) ); expect(loaded).toHaveLength(0); @@ -142,53 +187,68 @@ describe(EntityAssociationLoader, () => { it('chain loads associated entities', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const testEntity4 = await enforceAsyncResult(TestEntity.creator(viewerContext).createAsync()); + const testEntity4 = await enforceAsyncResult( + TestEntity.creator(viewerContext, viewerContext.getQueryContext()).createAsync() + ); const testEntity3 = await enforceAsyncResult( - TestEntity2.creator(viewerContext).setField('foreignKey', testEntity4.getID()).createAsync() + TestEntity2.creator(viewerContext, viewerContext.getQueryContext()) + .setField('foreignKey', testEntity4.getID()) + .createAsync() ); const testEntity2 = await enforceAsyncResult( - TestEntity.creator(viewerContext) + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('testIndexedField', testEntity3.getID()) .createAsync() ); const testEntity = await enforceAsyncResult( - TestEntity2.creator(viewerContext).setField('foreignKey', testEntity2.getID()).createAsync() + TestEntity2.creator(viewerContext, viewerContext.getQueryContext()) + .setField('foreignKey', testEntity2.getID()) + .createAsync() ); - const loaded2Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([ - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'foreignKey', - }, - ]); + const loaded2Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync( + [ + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'foreignKey', + }, + ], + viewerContext.getQueryContext() + ); expect(loaded2Result?.enforceValue().getID()).toEqual(testEntity2.getID()); - const loaded3Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([ - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'foreignKey', - }, - { - associatedEntityClass: TestEntity2, - fieldIdentifyingAssociatedEntity: 'testIndexedField', - }, - ]); + const loaded3Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync( + [ + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'foreignKey', + }, + { + associatedEntityClass: TestEntity2, + fieldIdentifyingAssociatedEntity: 'testIndexedField', + }, + ], + viewerContext.getQueryContext() + ); expect(loaded3Result?.enforceValue().getID()).toEqual(testEntity3.getID()); - const loaded4Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([ - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'foreignKey', - }, - { - associatedEntityClass: TestEntity2, - fieldIdentifyingAssociatedEntity: 'testIndexedField', - }, - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'foreignKey', - }, - ]); + const loaded4Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync( + [ + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'foreignKey', + }, + { + associatedEntityClass: TestEntity2, + fieldIdentifyingAssociatedEntity: 'testIndexedField', + }, + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'foreignKey', + }, + ], + viewerContext.getQueryContext() + ); expect(loaded4Result?.enforceValue().getID()).toEqual(testEntity4.getID()); }); @@ -197,15 +257,20 @@ describe(EntityAssociationLoader, () => { const viewerContext = new TestViewerContext(companionProvider); const testEntity = await enforceAsyncResult( - TestEntity2.creator(viewerContext).setField('foreignKey', uuidv4()).createAsync() + TestEntity2.creator(viewerContext, viewerContext.getQueryContext()) + .setField('foreignKey', uuidv4()) + .createAsync() ); - const loadResult = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([ - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'foreignKey', - }, - ]); + const loadResult = await testEntity.associationLoader().loadAssociatedEntityThroughAsync( + [ + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'foreignKey', + }, + ], + viewerContext.getQueryContext() + ); expect(loadResult?.ok).toBe(false); }); @@ -215,19 +280,26 @@ describe(EntityAssociationLoader, () => { const fieldValue = uuidv4(); const testEntity2 = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('stringField', fieldValue).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('stringField', fieldValue) + .createAsync() ); const testEntity = await enforceAsyncResult( - TestEntity2.creator(viewerContext).setField('foreignKey', fieldValue).createAsync() + TestEntity2.creator(viewerContext, viewerContext.getQueryContext()) + .setField('foreignKey', fieldValue) + .createAsync() ); - const loaded2Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([ - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'foreignKey', - associatedEntityLookupByField: 'stringField', - }, - ]); + const loaded2Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync( + [ + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'foreignKey', + associatedEntityLookupByField: 'stringField', + }, + ], + viewerContext.getQueryContext() + ); expect(loaded2Result?.enforceValue().getID()).toEqual(testEntity2.getID()); }); @@ -236,16 +308,21 @@ describe(EntityAssociationLoader, () => { const viewerContext = new TestViewerContext(companionProvider); const testEntity = await enforceAsyncResult( - TestEntity2.creator(viewerContext).setField('foreignKey', uuidv4()).createAsync() + TestEntity2.creator(viewerContext, viewerContext.getQueryContext()) + .setField('foreignKey', uuidv4()) + .createAsync() ); - const loaded2Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([ - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'foreignKey', - associatedEntityLookupByField: 'stringField', - }, - ]); + const loaded2Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync( + [ + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'foreignKey', + associatedEntityLookupByField: 'stringField', + }, + ], + viewerContext.getQueryContext() + ); expect(loaded2Result).toBeNull(); }); @@ -254,15 +331,20 @@ describe(EntityAssociationLoader, () => { const viewerContext = new TestViewerContext(companionProvider); const testEntity = await enforceAsyncResult( - TestEntity.creator(viewerContext).setField('nullableField', null).createAsync() + TestEntity.creator(viewerContext, viewerContext.getQueryContext()) + .setField('nullableField', null) + .createAsync() ); - const loadedResult = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([ - { - associatedEntityClass: TestEntity, - fieldIdentifyingAssociatedEntity: 'nullableField', - }, - ]); + const loadedResult = await testEntity.associationLoader().loadAssociatedEntityThroughAsync( + [ + { + associatedEntityClass: TestEntity, + fieldIdentifyingAssociatedEntity: 'nullableField', + }, + ], + viewerContext.getQueryContext() + ); expect(loadedResult).toBeNull(); }); }); diff --git a/packages/entity/src/__tests__/EntityCommonUseCases-test.ts b/packages/entity/src/__tests__/EntityCommonUseCases-test.ts index 2f82ca41..a7be860c 100644 --- a/packages/entity/src/__tests__/EntityCommonUseCases-test.ts +++ b/packages/entity/src/__tests__/EntityCommonUseCases-test.ts @@ -7,15 +7,15 @@ import EntityConfiguration from '../EntityConfiguration'; import { UUIDField } from '../EntityFields'; import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy'; import { EntityQueryContext } from '../EntityQueryContext'; -import ViewerContext from '../ViewerContext'; import { enforceResultsAsync } from '../entityUtils'; import EntityNotAuthorizedError from '../errors/EntityNotAuthorizedError'; import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule'; import AlwaysDenyPrivacyPolicyRule from '../rules/AlwaysDenyPrivacyPolicyRule'; import PrivacyPolicyRule, { RuleEvaluationResult } from '../rules/PrivacyPolicyRule'; +import TestViewerContext from '../testfixtures/TestViewerContext'; import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider'; -class TestUserViewerContext extends ViewerContext { +class TestUserViewerContext extends TestViewerContext { constructor(entityCompanionProvider: EntityCompanionProvider, private readonly userID: string) { super(entityCompanionProvider); } @@ -82,23 +82,23 @@ class DenyIfNotOwnerPrivacyPolicyRule extends PrivacyPolicyRule< class BlahEntityPrivacyPolicy extends EntityPrivacyPolicy< BlahFields, string, - ViewerContext, + TestUserViewerContext, BlahEntity > { protected override readonly createRules = [ new DenyIfNotOwnerPrivacyPolicyRule(), - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly readRules = [ new DenyIfNotOwnerPrivacyPolicyRule(), - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ new DenyIfNotOwnerPrivacyPolicyRule(), - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysDenyPrivacyPolicyRule(), + new AlwaysDenyPrivacyPolicyRule(), ]; } @@ -110,15 +110,21 @@ it('runs through a common workflow', async () => { const vc2 = new TestUserViewerContext(entityCompanionProvider, uuidv4()); const blahOwner1 = await enforceAsyncResult( - BlahEntity.creator(vc1).setField('ownerID', vc1.getUserID()!).createAsync() + BlahEntity.creator(vc1, vc1.getQueryContext()) + .setField('ownerID', vc1.getUserID()!) + .createAsync() ); await enforceAsyncResult( - BlahEntity.creator(vc1).setField('ownerID', vc1.getUserID()!).createAsync() + BlahEntity.creator(vc1, vc1.getQueryContext()) + .setField('ownerID', vc1.getUserID()!) + .createAsync() ); const blahOwner2 = await enforceAsyncResult( - BlahEntity.creator(vc2).setField('ownerID', vc2.getUserID()!).createAsync() + BlahEntity.creator(vc2, vc2.getQueryContext()) + .setField('ownerID', vc2.getUserID()!) + .createAsync() ); // sanity check created objects @@ -127,33 +133,45 @@ it('runs through a common workflow', async () => { // check that two people can't read each others data await expect( - enforceAsyncResult(BlahEntity.loader(vc1).loadByIDAsync(blahOwner2.getID())) + enforceAsyncResult( + BlahEntity.loader(vc1, vc1.getQueryContext()).loadByIDAsync(blahOwner2.getID()) + ) ).rejects.toBeInstanceOf(EntityNotAuthorizedError); await expect( - enforceAsyncResult(BlahEntity.loader(vc2).loadByIDAsync(blahOwner1.getID())) + enforceAsyncResult( + BlahEntity.loader(vc2, vc2.getQueryContext()).loadByIDAsync(blahOwner1.getID()) + ) ).rejects.toBeInstanceOf(EntityNotAuthorizedError); // check that all of owner 1's objects can be loaded const results = await enforceResultsAsync( - BlahEntity.loader(vc1).loadManyByFieldEqualingAsync('ownerID', vc1.getUserID()!) + BlahEntity.loader(vc1, vc1.getQueryContext()).loadManyByFieldEqualingAsync( + 'ownerID', + vc1.getUserID()! + ) ); expect(results).toHaveLength(2); // check that two people can't create objects owned by others await expect( enforceAsyncResult( - BlahEntity.creator(vc2).setField('ownerID', blahOwner1.getID()).createAsync() + BlahEntity.creator(vc2, vc2.getQueryContext()) + .setField('ownerID', blahOwner1.getID()) + .createAsync() ) ).rejects.toBeInstanceOf(EntityNotAuthorizedError); // check that empty load many returns nothing - const results2 = await BlahEntity.loader(vc1).loadManyByFieldEqualingManyAsync('ownerID', []); + const results2 = await BlahEntity.loader( + vc1, + vc1.getQueryContext() + ).loadManyByFieldEqualingManyAsync('ownerID', []); for (const value in results2.values) { expect(value).toHaveLength(0); } // check that the user can't delete their own data (as specified by privacy rules) - await expect(enforceAsyncResult(BlahEntity.deleteAsync(blahOwner2))).rejects.toBeInstanceOf( - EntityNotAuthorizedError - ); + await expect( + enforceAsyncResult(BlahEntity.deleteAsync(blahOwner2, vc1.getQueryContext())) + ).rejects.toBeInstanceOf(EntityNotAuthorizedError); }); diff --git a/packages/entity/src/__tests__/EntityEdges-test.ts b/packages/entity/src/__tests__/EntityEdges-test.ts index b0678bf8..d035a2e4 100644 --- a/packages/entity/src/__tests__/EntityEdges-test.ts +++ b/packages/entity/src/__tests__/EntityEdges-test.ts @@ -469,36 +469,54 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const parent = await ParentEntity.creator(viewerContext).enforceCreateAsync(); - const child = await ChildEntity.creator(viewerContext) + const parent = await ParentEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const child = await ChildEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_id', parent.getID()) .enforceCreateAsync(); - const grandchild = await GrandChildEntity.creator(viewerContext) + const grandchild = await GrandChildEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('parent_id', child.getID()) .enforceCreateAsync(); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.not.toBeNull(); await expect( - ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID()) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(child.getID()) ).resolves.not.toBeNull(); await expect( - GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID()) + GrandChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(grandchild.getID()) ).resolves.not.toBeNull(); privacyPolicyEvaluationRecords.shouldRecord = true; - await ParentEntity.enforceDeleteAsync(parent); + await ParentEntity.enforceDeleteAsync(parent, viewerContext.getQueryContext()); privacyPolicyEvaluationRecords.shouldRecord = false; await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.toBeNull(); await expect( - ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID()) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(child.getID()) ).resolves.toBeNull(); await expect( - GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID()) + GrandChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(grandchild.getID()) ).resolves.toBeNull(); // two calls for each trigger, one beforeDelete, one afterDelete @@ -587,38 +605,55 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const parent = await ParentEntity.creator(viewerContext).enforceCreateAsync(); - const child = await ChildEntity.creator(viewerContext) + const parent = await ParentEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const child = await ChildEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_id', parent.getID()) .enforceCreateAsync(); - const grandchild = await GrandChildEntity.creator(viewerContext) + const grandchild = await GrandChildEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('parent_id', child.getID()) .enforceCreateAsync(); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.not.toBeNull(); await expect( - ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID()) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(child.getID()) ).resolves.not.toBeNull(); await expect( - GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID()) + GrandChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(grandchild.getID()) ).resolves.not.toBeNull(); privacyPolicyEvaluationRecords.shouldRecord = true; - await ParentEntity.enforceDeleteAsync(parent); + await ParentEntity.enforceDeleteAsync(parent, viewerContext.getQueryContext()); privacyPolicyEvaluationRecords.shouldRecord = false; await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.toBeNull(); - const loadedChild = await ChildEntity.loader(viewerContext) + const loadedChild = await ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDAsync(child.getID()); expect(loadedChild.getField('parent_id')).toBeNull(); - const loadedGrandchild = await GrandChildEntity.loader(viewerContext) + const loadedGrandchild = await GrandChildEntity.loader( + viewerContext, + viewerContext.getQueryContext() + ) .enforcing() .loadByIDAsync(grandchild.getID()); expect(loadedGrandchild.getField('parent_id')).toEqual(loadedChild.getID()); @@ -698,24 +733,32 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const parent = await ParentEntity.creator(viewerContext).enforceCreateAsync(); - const child = await ChildEntity.creator(viewerContext) + const parent = await ParentEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const child = await ChildEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_id', parent.getID()) .enforceCreateAsync(); - const grandchild = await GrandChildEntity.creator(viewerContext) + const grandchild = await GrandChildEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('parent_id', child.getID()) .enforceCreateAsync(); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.not.toBeNull(); await expect( - ChildEntity.loader(viewerContext) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_id', parent.getID()) ).resolves.not.toBeNull(); await expect( - GrandChildEntity.loader(viewerContext) + GrandChildEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_id', child.getID()) ).resolves.not.toBeNull(); @@ -739,7 +782,7 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { expect(grandChildCachedBefore.get(child.getID())?.status).toEqual(CacheStatus.HIT); privacyPolicyEvaluationRecords.shouldRecord = true; - await ParentEntity.enforceDeleteAsync(parent); + await ParentEntity.enforceDeleteAsync(parent, viewerContext.getQueryContext()); privacyPolicyEvaluationRecords.shouldRecord = false; const childCachedAfter = await childCacheAdapter.loadManyAsync('parent_id', [parent.getID()]); @@ -751,15 +794,20 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { expect(grandChildCachedAfter.get(child.getID())?.status).toEqual(CacheStatus.HIT); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.toBeNull(); - const loadedChild = await ChildEntity.loader(viewerContext) + const loadedChild = await ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDAsync(child.getID()); expect(loadedChild).not.toBeNull(); - const loadedGrandchild = await GrandChildEntity.loader(viewerContext) + const loadedGrandchild = await GrandChildEntity.loader( + viewerContext, + viewerContext.getQueryContext() + ) .enforcing() .loadByIDAsync(grandchild.getID()); expect(loadedGrandchild.getField('parent_id')).toEqual(loadedChild.getID()); @@ -831,24 +879,32 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const parent = await ParentEntity.creator(viewerContext).enforceCreateAsync(); - const child = await ChildEntity.creator(viewerContext) + const parent = await ParentEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const child = await ChildEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_id', parent.getID()) .enforceCreateAsync(); - const grandchild = await GrandChildEntity.creator(viewerContext) + const grandchild = await GrandChildEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('parent_id', child.getID()) .enforceCreateAsync(); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.not.toBeNull(); await expect( - ChildEntity.loader(viewerContext) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_id', parent.getID()) ).resolves.not.toBeNull(); await expect( - GrandChildEntity.loader(viewerContext) + GrandChildEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_id', child.getID()) ).resolves.not.toBeNull(); @@ -872,7 +928,7 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { expect(grandChildCachedBefore.get(child.getID())?.status).toEqual(CacheStatus.HIT); privacyPolicyEvaluationRecords.shouldRecord = true; - await ParentEntity.enforceDeleteAsync(parent); + await ParentEntity.enforceDeleteAsync(parent, viewerContext.getQueryContext()); privacyPolicyEvaluationRecords.shouldRecord = false; const childCachedAfter = await childCacheAdapter.loadManyAsync('parent_id', [parent.getID()]); @@ -884,13 +940,19 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => { expect(grandChildCachedAfter.get(child.getID())?.status).toEqual(CacheStatus.MISS); await expect( - ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID()) + ParentEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parent.getID()) ).resolves.toBeNull(); await expect( - ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID()) + ChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(child.getID()) ).resolves.not.toBeNull(); await expect( - GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID()) + GrandChildEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(grandchild.getID()) ).resolves.not.toBeNull(); // two calls for each trigger, one beforeDelete, one afterDelete diff --git a/packages/entity/src/__tests__/EntityLoader-constructor-test.ts b/packages/entity/src/__tests__/EntityLoader-constructor-test.ts index ca100c31..0b026b85 100644 --- a/packages/entity/src/__tests__/EntityLoader-constructor-test.ts +++ b/packages/entity/src/__tests__/EntityLoader-constructor-test.ts @@ -123,7 +123,7 @@ describe(EntityLoader, () => { const viewerContext = instance(mock(ViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const databaseAdapter = new StubDatabaseAdapter( testEntityConfiguration, diff --git a/packages/entity/src/__tests__/EntityLoader-test.ts b/packages/entity/src/__tests__/EntityLoader-test.ts index 76c6a509..83cdb277 100644 --- a/packages/entity/src/__tests__/EntityLoader-test.ts +++ b/packages/entity/src/__tests__/EntityLoader-test.ts @@ -5,7 +5,6 @@ import { v4 as uuidv4 } from 'uuid'; import { OrderByOrdering } from '../EntityDatabaseAdapter'; import EntityLoader from '../EntityLoader'; import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy'; -import ViewerContext from '../ViewerContext'; import { enforceResultsAsync } from '../entityUtils'; import EntityDataManager from '../internal/EntityDataManager'; import ReadThroughEntityCache from '../internal/ReadThroughEntityCache'; @@ -15,6 +14,7 @@ import TestEntity, { TestEntityPrivacyPolicy, testEntityConfiguration, } from '../testfixtures/TestEntity'; +import TestViewerContext from '../testfixtures/TestViewerContext'; import { NoCacheStubCacheAdapterProvider } from '../utils/testing/StubCacheAdapter'; import StubDatabaseAdapter from '../utils/testing/StubDatabaseAdapter'; import StubQueryContextProvider from '../utils/testing/StubQueryContextProvider'; @@ -22,10 +22,10 @@ import StubQueryContextProvider from '../utils/testing/StubQueryContextProvider' describe(EntityLoader, () => { it('loads entities', async () => { const dateToInsert = new Date(); - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -117,10 +117,10 @@ describe(EntityLoader, () => { it('loads entities with loadManyByFieldEqualityConjunction', async () => { const privacyPolicy = new TestEntityPrivacyPolicy(); const spiedPrivacyPolicy = spy(privacyPolicy); - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -216,10 +216,10 @@ describe(EntityLoader, () => { it('loads entities with loadFirstByFieldEqualityConjunction', async () => { const privacyPolicy = new TestEntityPrivacyPolicy(); const spiedPrivacyPolicy = spy(privacyPolicy); - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -313,10 +313,10 @@ describe(EntityLoader, () => { const privacyPolicy = new TestEntityPrivacyPolicy(); const spiedPrivacyPolicy = spy(privacyPolicy); - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const databaseAdapter = new StubDatabaseAdapter( @@ -374,10 +374,10 @@ describe(EntityLoader, () => { }); it('invalidates upon invalidate one', async () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); const dataManagerMock = mock>(); const dataManagerInstance = instance(dataManagerMock); @@ -402,10 +402,10 @@ describe(EntityLoader, () => { }); it('invalidates upon invalidate by field', async () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); const dataManagerMock = mock>(); const dataManagerInstance = instance(dataManagerMock); @@ -429,10 +429,10 @@ describe(EntityLoader, () => { }); it('invalidates upon invalidate by entity', async () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); const dataManagerMock = mock>(); const dataManagerInstance = instance(dataManagerMock); @@ -460,10 +460,10 @@ describe(EntityLoader, () => { }); it('returns error result when not allowed', async () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const privacyPolicyMock = mock(TestEntityPrivacyPolicy); const dataManagerMock = mock>(); @@ -506,10 +506,10 @@ describe(EntityLoader, () => { }); it('throws upon database adapter error', async () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const privacyPolicyEvaluationContext = instance(mock()); const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); const dataManagerMock = mock>(); diff --git a/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts b/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts index a5589628..d7c8a6ad 100644 --- a/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +++ b/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts @@ -5,8 +5,8 @@ import { UUIDField } from '../EntityFields'; import { EntityMutationType, EntityTriggerMutationInfo } from '../EntityMutationInfo'; import { EntityNonTransactionalMutationTrigger } from '../EntityMutationTriggerConfiguration'; import EntityPrivacyPolicy from '../EntityPrivacyPolicy'; -import ViewerContext from '../ViewerContext'; import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule'; +import TestViewerContext from '../testfixtures/TestViewerContext'; import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider'; type BlahFields = { @@ -16,28 +16,28 @@ type BlahFields = { class BlahEntityPrivacyPolicy extends EntityPrivacyPolicy< BlahFields, string, - ViewerContext, + TestViewerContext, BlahEntity > { protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -class BlahEntity extends Entity { +class BlahEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< BlahFields, string, - ViewerContext, + TestViewerContext, BlahEntity, BlahEntityPrivacyPolicy > { @@ -66,16 +66,16 @@ class BlahEntity extends Entity { class TestNonTransactionalMutationTrigger extends EntityNonTransactionalMutationTrigger< BlahFields, string, - ViewerContext, + TestViewerContext, BlahEntity > { async executeAsync( - viewerContext: ViewerContext, + viewerContext: TestViewerContext, entity: BlahEntity, - mutationInfo: EntityTriggerMutationInfo + mutationInfo: EntityTriggerMutationInfo ): Promise { if (mutationInfo.type === EntityMutationType.DELETE) { - const entityLoaded = await BlahEntity.loader(viewerContext) + const entityLoaded = await BlahEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDNullableAsync(entity.getID()); if (entityLoaded) { @@ -90,14 +90,17 @@ class TestNonTransactionalMutationTrigger extends EntityNonTransactionalMutation describe('EntityMutator', () => { test('cache consistency with post-commit callbacks', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); // put it in cache - const entity = await BlahEntity.creator(viewerContext).enforceCreateAsync(); - const entityLoaded = await BlahEntity.loader(viewerContext) + const entity = await BlahEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const entityLoaded = await BlahEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDAsync(entity.getID()); - await BlahEntity.enforceDeleteAsync(entityLoaded); + await BlahEntity.enforceDeleteAsync(entityLoaded, viewerContext.getQueryContext()); }); }); diff --git a/packages/entity/src/__tests__/EntityMutator-test.ts b/packages/entity/src/__tests__/EntityMutator-test.ts index a6b56396..9ecb94db 100644 --- a/packages/entity/src/__tests__/EntityMutator-test.ts +++ b/packages/entity/src/__tests__/EntityMutator-test.ts @@ -31,7 +31,6 @@ import EntityMutatorFactory from '../EntityMutatorFactory'; import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy'; import { EntityTransactionalQueryContext, EntityQueryContext } from '../EntityQueryContext'; import IEntityDatabaseAdapterProvider from '../IEntityDatabaseAdapterProvider'; -import ViewerContext from '../ViewerContext'; import { enforceResultsAsync } from '../entityUtils'; import EntityDataManager from '../internal/EntityDataManager'; import ReadThroughEntityCache from '../internal/ReadThroughEntityCache'; @@ -47,6 +46,7 @@ import TestEntity, { TestEntityPrivacyPolicy, testEntityConfiguration, } from '../testfixtures/TestEntity'; +import TestViewerContext from '../testfixtures/TestViewerContext'; import { NoCacheStubCacheAdapterProvider } from '../utils/testing/StubCacheAdapter'; import StubDatabaseAdapter from '../utils/testing/StubDatabaseAdapter'; import StubQueryContextProvider from '../utils/testing/StubQueryContextProvider'; @@ -54,18 +54,18 @@ import StubQueryContextProvider from '../utils/testing/StubQueryContextProvider' class TestMutationTrigger extends EntityMutationTrigger< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > { async executeAsync( - _viewerContext: ViewerContext, + _viewerContext: TestViewerContext, _queryContext: EntityQueryContext, _entity: TestEntity, _mutationInfo: EntityTriggerMutationInfo< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > @@ -75,31 +75,37 @@ class TestMutationTrigger extends EntityMutationTrigger< class TestNonTransactionalMutationTrigger extends EntityNonTransactionalMutationTrigger< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > { - async executeAsync(_viewerContext: ViewerContext, _entity: TestEntity): Promise {} + async executeAsync(_viewerContext: TestViewerContext, _entity: TestEntity): Promise {} } const setUpMutationValidatorSpies = ( mutationValidators: EntityMutationValidator< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields >[] -): EntityMutationValidator[] => { +): EntityMutationValidator< + TestFields, + string, + TestViewerContext, + TestEntity, + keyof TestFields +>[] => { return mutationValidators.map((validator) => spy(validator)); }; const verifyValidatorCounts = ( - viewerContext: ViewerContext, + viewerContext: TestViewerContext, mutationValidatorSpies: EntityMutationValidator< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields >[], @@ -107,7 +113,7 @@ const verifyValidatorCounts = ( mutationInfo: EntityValidatorMutationInfo< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > @@ -128,14 +134,14 @@ const setUpMutationTriggerSpies = ( mutationTriggers: EntityMutationTriggerConfiguration< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > ): EntityMutationTriggerConfiguration< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > => { @@ -153,11 +159,11 @@ const setUpMutationTriggerSpies = ( }; const verifyTriggerCounts = ( - viewerContext: ViewerContext, + viewerContext: TestViewerContext, mutationTriggerSpies: EntityMutationTriggerConfiguration< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields >, @@ -166,7 +172,7 @@ const verifyTriggerCounts = ( EntityMutationTriggerConfiguration< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields >, @@ -182,7 +188,7 @@ const verifyTriggerCounts = ( mutationInfo: EntityTriggerMutationInfo< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > @@ -243,7 +249,7 @@ const createEntityMutatorFactory = ( entityLoaderFactory: EntityLoaderFactory< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, TestEntityPrivacyPolicy, keyof TestFields @@ -251,7 +257,7 @@ const createEntityMutatorFactory = ( entityMutatorFactory: EntityMutatorFactory< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, TestEntityPrivacyPolicy >; @@ -259,14 +265,14 @@ const createEntityMutatorFactory = ( mutationValidators: EntityMutationValidator< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields >[]; mutationTriggers: EntityMutationTriggerConfiguration< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields >; @@ -274,14 +280,14 @@ const createEntityMutatorFactory = ( const mutationValidators: EntityMutationValidator< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields >[] = [new TestMutationTrigger()]; const mutationTriggers: EntityMutationTriggerConfiguration< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, keyof TestFields > = { @@ -372,8 +378,8 @@ const createEntityMutatorFactory = ( describe(EntityMutatorFactory, () => { describe('forCreate', () => { it('creates entities', async () => { - const viewerContext = mock(); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = mock(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -403,8 +409,8 @@ describe(EntityMutatorFactory, () => { }); it('checks privacy', async () => { - const viewerContext = mock(); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = mock(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -446,8 +452,8 @@ describe(EntityMutatorFactory, () => { }); it('executes triggers', async () => { - const viewerContext = mock(); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = mock(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -493,8 +499,8 @@ describe(EntityMutatorFactory, () => { }); it('executes validators', async () => { - const viewerContext = mock(); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = mock(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -530,9 +536,9 @@ describe(EntityMutatorFactory, () => { describe('forUpdate', () => { it('updates entities', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -579,9 +585,9 @@ describe(EntityMutatorFactory, () => { }); it('checks privacy', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -630,9 +636,9 @@ describe(EntityMutatorFactory, () => { }); it('executes triggers', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -688,9 +694,9 @@ describe(EntityMutatorFactory, () => { ); }); it('executes validators', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const id2 = uuidv4(); @@ -737,9 +743,9 @@ describe(EntityMutatorFactory, () => { describe('forDelete', () => { it('deletes entities', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const { entityMutatorFactory, entityLoaderFactory } = createEntityMutatorFactory([ @@ -772,9 +778,9 @@ describe(EntityMutatorFactory, () => { }); it('checks privacy', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const { privacyPolicy, entityMutatorFactory, entityLoaderFactory } = @@ -811,9 +817,9 @@ describe(EntityMutatorFactory, () => { }); it('executes triggers', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const { mutationTriggers, entityMutatorFactory, entityLoaderFactory } = @@ -854,9 +860,9 @@ describe(EntityMutatorFactory, () => { }); it('does not execute validators', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const { mutationValidators, entityMutatorFactory, entityLoaderFactory } = @@ -888,9 +894,9 @@ describe(EntityMutatorFactory, () => { }); it('invalidates cache for fields upon create', async () => { - const viewerContext = mock(); + const viewerContext = mock(); const privacyPolicyEvaluationContext = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const { entityMutatorFactory, entityLoaderFactory } = createEntityMutatorFactory([ @@ -927,8 +933,8 @@ describe(EntityMutatorFactory, () => { }); it('throws error when field not valid', async () => { - const viewerContext = mock(); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = mock(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const id1 = uuidv4(); const { entityMutatorFactory } = createEntityMutatorFactory([ { @@ -963,8 +969,8 @@ describe(EntityMutatorFactory, () => { it('returns error result when not authorized to create', async () => { const entityCompanionProvider = instance(mock(EntityCompanionProvider)); - const viewerContext = instance(mock(ViewerContext)); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = instance(mock(TestViewerContext)); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const privacyPolicyMock = mock(SimpleTestEntityPrivacyPolicy); const databaseAdapter = instance(mock>()); const metricsAdapter = instance(mock()); @@ -986,7 +992,7 @@ describe(EntityMutatorFactory, () => { EntityLoader< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestEntityPrivacyPolicy, keyof SimpleTestFields @@ -1000,7 +1006,7 @@ describe(EntityMutatorFactory, () => { EntityLoaderFactory< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestEntityPrivacyPolicy, keyof SimpleTestFields @@ -1087,8 +1093,8 @@ describe(EntityMutatorFactory, () => { const entityCompanionProvider = instance(entityCompanionProviderMock); - const viewerContext = instance(mock(ViewerContext)); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = instance(mock(TestViewerContext)); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const privacyPolicy = instance(mock(SimpleTestEntityPrivacyPolicy)); const databaseAdapterMock = mock>(); const metricsAdapter = instance(mock()); @@ -1110,7 +1116,7 @@ describe(EntityMutatorFactory, () => { EntityLoader< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestEntityPrivacyPolicy, keyof SimpleTestFields @@ -1124,7 +1130,7 @@ describe(EntityMutatorFactory, () => { EntityLoaderFactory< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestEntityPrivacyPolicy, keyof SimpleTestFields @@ -1184,8 +1190,8 @@ describe(EntityMutatorFactory, () => { }); it('records metrics appropriately', async () => { - const viewerContext = mock(); - const queryContext = StubQueryContextProvider.getQueryContext(); + const viewerContext = mock(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const { entityMutatorFactory, metricsAdapter } = createEntityMutatorFactory([]); const spiedMetricsAdapter = spy(metricsAdapter); diff --git a/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts b/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts index 99797ef2..ac79b85c 100644 --- a/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts +++ b/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts @@ -2,11 +2,11 @@ import { anyOfClass, anything, deepEqual, instance, mock, spy, verify, when } fr import { EntityNonTransactionalQueryContext } from '../EntityQueryContext'; import EntitySecondaryCacheLoader, { ISecondaryEntityCache } from '../EntitySecondaryCacheLoader'; -import ViewerContext from '../ViewerContext'; import SimpleTestEntity, { SimpleTestEntityPrivacyPolicy, SimpleTestFields, } from '../testfixtures/SimpleTestEntity'; +import TestViewerContext from '../testfixtures/TestViewerContext'; import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider'; type TestLoadParams = { id: string }; @@ -15,7 +15,7 @@ class TestSecondaryRedisCacheLoader extends EntitySecondaryCacheLoader< TestLoadParams, SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestEntityPrivacyPolicy > { @@ -30,9 +30,12 @@ class TestSecondaryRedisCacheLoader extends EntitySecondaryCacheLoader< describe(EntitySecondaryCacheLoader, () => { describe('loadManyAsync', () => { it('calls into secondary cache with correct params', async () => { - const vc1 = new ViewerContext(createUnitTestEntityCompanionProvider()); + const vc1 = new TestViewerContext(createUnitTestEntityCompanionProvider()); - const createdEntity = await SimpleTestEntity.creator(vc1).enforceCreateAsync(); + const createdEntity = await SimpleTestEntity.creator( + vc1, + vc1.getQueryContext() + ).enforceCreateAsync(); const loadParams = { id: createdEntity.getID() }; const secondaryEntityCacheMock = @@ -44,7 +47,7 @@ describe(EntitySecondaryCacheLoader, () => { const secondaryCacheLoader = new TestSecondaryRedisCacheLoader( secondaryEntityCache, - SimpleTestEntity.loader(vc1) + SimpleTestEntity.loader(vc1, vc1.getQueryContext()) ); await secondaryCacheLoader.loadManyAsync([loadParams]); @@ -55,9 +58,12 @@ describe(EntitySecondaryCacheLoader, () => { }); it('constructs and authorizes entities', async () => { - const vc1 = new ViewerContext(createUnitTestEntityCompanionProvider()); + const vc1 = new TestViewerContext(createUnitTestEntityCompanionProvider()); - const createdEntity = await SimpleTestEntity.creator(vc1).enforceCreateAsync(); + const createdEntity = await SimpleTestEntity.creator( + vc1, + vc1.getQueryContext() + ).enforceCreateAsync(); const loadParams = { id: createdEntity.getID() }; const secondaryEntityCacheMock = @@ -67,7 +73,7 @@ describe(EntitySecondaryCacheLoader, () => { ).thenResolve(new Map([[loadParams, createdEntity.getAllFields()]])); const secondaryEntityCache = instance(secondaryEntityCacheMock); - const loader = SimpleTestEntity.loader(vc1); + const loader = SimpleTestEntity.loader(vc1, vc1.getQueryContext()); const spiedPrivacyPolicy = spy(loader['privacyPolicy']); const secondaryCacheLoader = new TestSecondaryRedisCacheLoader(secondaryEntityCache, loader); @@ -88,15 +94,18 @@ describe(EntitySecondaryCacheLoader, () => { describe('invalidateManyAsync', () => { it('calls invalidate on the secondary cache', async () => { - const vc1 = new ViewerContext(createUnitTestEntityCompanionProvider()); + const vc1 = new TestViewerContext(createUnitTestEntityCompanionProvider()); - const createdEntity = await SimpleTestEntity.creator(vc1).enforceCreateAsync(); + const createdEntity = await SimpleTestEntity.creator( + vc1, + vc1.getQueryContext() + ).enforceCreateAsync(); const loadParams = { id: createdEntity.getID() }; const secondaryEntityCacheMock = mock>(); const secondaryEntityCache = instance(secondaryEntityCacheMock); - const loader = SimpleTestEntity.loader(vc1); + const loader = SimpleTestEntity.loader(vc1, vc1.getQueryContext()); const secondaryCacheLoader = new TestSecondaryRedisCacheLoader(secondaryEntityCache, loader); await secondaryCacheLoader.invalidateManyAsync([loadParams]); diff --git a/packages/entity/src/__tests__/EntitySelfReferentialEdges-test.ts b/packages/entity/src/__tests__/EntitySelfReferentialEdges-test.ts index 8ce828de..f46fe75a 100644 --- a/packages/entity/src/__tests__/EntitySelfReferentialEdges-test.ts +++ b/packages/entity/src/__tests__/EntitySelfReferentialEdges-test.ts @@ -87,34 +87,52 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const parentCategory = await CategoryEntity.creator(viewerContext).enforceCreateAsync(); - const subCategory = await CategoryEntity.creator(viewerContext) + const parentCategory = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const subCategory = await CategoryEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_category_id', parentCategory.getID()) .enforceCreateAsync(); - const subSubCategory = await CategoryEntity.creator(viewerContext) + const subSubCategory = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('parent_category_id', subCategory.getID()) .enforceCreateAsync(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parentCategory.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subCategory.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subSubCategory.getID()) ).resolves.not.toBeNull(); - await CategoryEntity.enforceDeleteAsync(parentCategory); + await CategoryEntity.enforceDeleteAsync(parentCategory, viewerContext.getQueryContext()); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parentCategory.getID()) ).resolves.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subCategory.getID()) ).resolves.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subSubCategory.getID()) ).resolves.toBeNull(); }); @@ -124,21 +142,28 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const categoryA = await CategoryEntity.creator(viewerContext).enforceCreateAsync(); - const categoryB = await CategoryEntity.creator(viewerContext) + const categoryA = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const categoryB = await CategoryEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_category_id', categoryA.getID()) .enforceCreateAsync(); - await CategoryEntity.updater(categoryA) + await CategoryEntity.updater(categoryA, viewerContext.getQueryContext()) .setField('parent_category_id', categoryB.getID()) .enforceUpdateAsync(); - await CategoryEntity.enforceDeleteAsync(categoryA); + await CategoryEntity.enforceDeleteAsync(categoryA, viewerContext.getQueryContext()); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(categoryA.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(categoryA.getID()) ).resolves.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(categoryB.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(categoryB.getID()) ).resolves.toBeNull(); }); }); @@ -150,36 +175,56 @@ describe('EntityEdgeDeletionBehavior.SET_NULL', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const parentCategory = await CategoryEntity.creator(viewerContext).enforceCreateAsync(); - const subCategory = await CategoryEntity.creator(viewerContext) + const parentCategory = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const subCategory = await CategoryEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_category_id', parentCategory.getID()) .enforceCreateAsync(); - const subSubCategory = await CategoryEntity.creator(viewerContext) + const subSubCategory = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('parent_category_id', subCategory.getID()) .enforceCreateAsync(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parentCategory.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subCategory.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subSubCategory.getID()) ).resolves.not.toBeNull(); - await CategoryEntity.enforceDeleteAsync(parentCategory); + await CategoryEntity.enforceDeleteAsync(parentCategory, viewerContext.getQueryContext()); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parentCategory.getID()) ).resolves.toBeNull(); - const loadedSubCategory = await CategoryEntity.loader(viewerContext) + const loadedSubCategory = await CategoryEntity.loader( + viewerContext, + viewerContext.getQueryContext() + ) .enforcing() .loadByIDAsync(subCategory.getID()); expect(loadedSubCategory.getField('parent_category_id')).toBeNull(); - const loadedSubSubCategory = await CategoryEntity.loader(viewerContext) + const loadedSubSubCategory = await CategoryEntity.loader( + viewerContext, + viewerContext.getQueryContext() + ) .enforcing() .loadByIDAsync(subSubCategory.getID()); expect(loadedSubSubCategory.getField('parent_category_id')).not.toBeNull(); @@ -191,17 +236,23 @@ describe('EntityEdgeDeletionBehavior.SET_NULL', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const categoryA = await CategoryEntity.creator(viewerContext).enforceCreateAsync(); - const categoryB = await CategoryEntity.creator(viewerContext) + const categoryA = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const categoryB = await CategoryEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_category_id', categoryA.getID()) .enforceCreateAsync(); - await CategoryEntity.updater(categoryA) + await CategoryEntity.updater(categoryA, viewerContext.getQueryContext()) .setField('parent_category_id', categoryB.getID()) .enforceUpdateAsync(); - await CategoryEntity.enforceDeleteAsync(categoryA); + await CategoryEntity.enforceDeleteAsync(categoryA, viewerContext.getQueryContext()); - const loadedCategoryB = await CategoryEntity.loader(viewerContext) + const loadedCategoryB = await CategoryEntity.loader( + viewerContext, + viewerContext.getQueryContext() + ) .enforcing() .loadByIDAsync(categoryB.getID()); expect(loadedCategoryB.getField('parent_category_id')).toBeNull(); @@ -217,24 +268,32 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const parentCategory = await CategoryEntity.creator(viewerContext).enforceCreateAsync(); - const subCategory = await CategoryEntity.creator(viewerContext) + const parentCategory = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const subCategory = await CategoryEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_category_id', parentCategory.getID()) .enforceCreateAsync(); - const subSubCategory = await CategoryEntity.creator(viewerContext) + const subSubCategory = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ) .setField('parent_category_id', subCategory.getID()) .enforceCreateAsync(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parentCategory.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_category_id', parentCategory.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_category_id', subCategory.getID()) ).resolves.not.toBeNull(); @@ -255,7 +314,7 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => { ); expect(subSubCategoryCachedBefore.get(subCategory.getID())?.status).toEqual(CacheStatus.HIT); - await CategoryEntity.enforceDeleteAsync(parentCategory); + await CategoryEntity.enforceDeleteAsync(parentCategory, viewerContext.getQueryContext()); const subCategoryCachedAfter = await categoryCacheAdapter.loadManyAsync('parent_category_id', [ parentCategory.getID(), @@ -269,13 +328,19 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => { expect(subSubCategoryCachedAfter.get(subCategory.getID())?.status).toEqual(CacheStatus.MISS); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(parentCategory.getID()) ).resolves.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subCategory.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID()) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDNullableAsync(subSubCategory.getID()) ).resolves.not.toBeNull(); }); @@ -287,21 +352,24 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new TestViewerContext(companionProvider); - const categoryA = await CategoryEntity.creator(viewerContext).enforceCreateAsync(); - const categoryB = await CategoryEntity.creator(viewerContext) + const categoryA = await CategoryEntity.creator( + viewerContext, + viewerContext.getQueryContext() + ).enforceCreateAsync(); + const categoryB = await CategoryEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('parent_category_id', categoryA.getID()) .enforceCreateAsync(); - await CategoryEntity.updater(categoryA) + await CategoryEntity.updater(categoryA, viewerContext.getQueryContext()) .setField('parent_category_id', categoryB.getID()) .enforceUpdateAsync(); await expect( - CategoryEntity.loader(viewerContext) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_category_id', categoryA.getID()) ).resolves.not.toBeNull(); await expect( - CategoryEntity.loader(viewerContext) + CategoryEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('parent_category_id', categoryB.getID()) ).resolves.not.toBeNull(); @@ -318,7 +386,7 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => { expect(categoriesCachedBefore.get(categoryA.getID())?.status).toEqual(CacheStatus.HIT); expect(categoriesCachedBefore.get(categoryB.getID())?.status).toEqual(CacheStatus.HIT); - await CategoryEntity.enforceDeleteAsync(categoryA); + await CategoryEntity.enforceDeleteAsync(categoryA, viewerContext.getQueryContext()); const categoriesCachedAfter = await categoryCacheAdapter.loadManyAsync('parent_category_id', [ categoryA.getID(), diff --git a/packages/entity/src/__tests__/ReadonlyEntity-test.ts b/packages/entity/src/__tests__/ReadonlyEntity-test.ts index bcfa1615..7de094c3 100644 --- a/packages/entity/src/__tests__/ReadonlyEntity-test.ts +++ b/packages/entity/src/__tests__/ReadonlyEntity-test.ts @@ -3,15 +3,15 @@ import { instance, mock } from 'ts-mockito'; import EntityAssociationLoader from '../EntityAssociationLoader'; import EntityLoader from '../EntityLoader'; import ReadonlyEntity from '../ReadonlyEntity'; -import ViewerContext from '../ViewerContext'; import SimpleTestEntity from '../testfixtures/SimpleTestEntity'; import TestEntity from '../testfixtures/TestEntity'; +import TestViewerContext from '../testfixtures/TestViewerContext'; import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider'; describe(ReadonlyEntity, () => { describe('getID', () => { it('returns correct value', () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const data = { id: 'what', }; @@ -27,7 +27,7 @@ describe(ReadonlyEntity, () => { describe('toString', () => { it('returns correct value', () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const data = { id: 'what', }; @@ -43,7 +43,7 @@ describe(ReadonlyEntity, () => { describe('getUniqueIdentifier', () => { it('returns a different value for two different entities of the same type', () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const testEntity = new SimpleTestEntity({ viewerContext, id: '1', @@ -68,8 +68,8 @@ describe(ReadonlyEntity, () => { }); it('returns the same value even if different viewer context', () => { - const viewerContext = instance(mock(ViewerContext)); - const viewerContext2 = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); + const viewerContext2 = instance(mock(TestViewerContext)); const data = { id: '1' }; const testEntity = new SimpleTestEntity({ viewerContext, @@ -87,7 +87,7 @@ describe(ReadonlyEntity, () => { }); it('returns a different value for different entities even if same ID', () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const data = { id: '1' }; const testEntity = new SimpleTestEntity({ viewerContext, @@ -114,7 +114,7 @@ describe(ReadonlyEntity, () => { }); it('cannot be created without an ID', () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const dataWithoutID = {}; expect(() => { // eslint-disable-next-line no-new @@ -128,7 +128,7 @@ describe(ReadonlyEntity, () => { }); it('returns correct viewerCo}ntext from instantiation', () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const data = { id: 'what', }; @@ -142,7 +142,7 @@ describe(ReadonlyEntity, () => { }); it('returns correct data for field getters', () => { - const viewerContext = instance(mock(ViewerContext)); + const viewerContext = instance(mock(TestViewerContext)); const data = { id: 'what', }; @@ -159,7 +159,7 @@ describe(ReadonlyEntity, () => { describe('associationLoader', () => { it('returns a new association loader', () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); const data = { id: 'what', }; @@ -176,8 +176,10 @@ describe(ReadonlyEntity, () => { describe('loader', () => { it('creates a new EntityLoader', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); - expect(SimpleTestEntity.loader(viewerContext)).toBeInstanceOf(EntityLoader); + const viewerContext = new TestViewerContext(companionProvider); + expect( + SimpleTestEntity.loader(viewerContext, viewerContext.getQueryContext()) + ).toBeInstanceOf(EntityLoader); }); }); }); diff --git a/packages/entity/src/__tests__/ViewerContext-test.ts b/packages/entity/src/__tests__/ViewerContext-test.ts index 45fa1ad3..c8d28514 100644 --- a/packages/entity/src/__tests__/ViewerContext-test.ts +++ b/packages/entity/src/__tests__/ViewerContext-test.ts @@ -3,11 +3,12 @@ import ViewerContext from '../ViewerContext'; import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider'; describe(ViewerContext, () => { - describe('getQueryContextForDatabaseAdaptorFlavor', () => { + describe('getNonTransactionalQueryContextForDatabaseAdaptorFlavor', () => { it('creates a new regular query context', () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new ViewerContext(companionProvider); - const queryContext = viewerContext.getQueryContextForDatabaseAdaptorFlavor('postgres'); + const queryContext = + viewerContext.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres'); expect(queryContext).toBeInstanceOf(EntityQueryContext); expect(queryContext.isInTransaction()).toBe(false); }); diff --git a/packages/entity/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts b/packages/entity/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts index cc136089..00516f8c 100644 --- a/packages/entity/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +++ b/packages/entity/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts @@ -3,22 +3,22 @@ import { EntityCompanionDefinition } from '../../EntityCompanionProvider'; import EntityConfiguration from '../../EntityConfiguration'; import { UUIDField, EnumField, StringField } from '../../EntityFields'; import EntityPrivacyPolicy from '../../EntityPrivacyPolicy'; -import ViewerContext from '../../ViewerContext'; import { successfulResults, failedResults } from '../../entityUtils'; import AlwaysAllowPrivacyPolicyRule from '../../rules/AlwaysAllowPrivacyPolicyRule'; +import TestViewerContext from '../../testfixtures/TestViewerContext'; import { createUnitTestEntityCompanionProvider } from '../../utils/testing/createUnitTestEntityCompanionProvider'; describe('Two entities backed by the same table', () => { test('load by different types', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); - const one = await OneTestEntity.creator(viewerContext) + const one = await OneTestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('entity_type', EntityType.ONE) .setField('common_other_field', 'wat') .enforceCreateAsync(); - const two = await TwoTestEntity.creator(viewerContext) + const two = await TwoTestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('entity_type', EntityType.TWO) .setField('other_field', 'blah') .setField('common_other_field', 'wat') @@ -28,17 +28,21 @@ describe('Two entities backed by the same table', () => { expect(two).toBeInstanceOf(TwoTestEntity); await expect( - TwoTestEntity.loader(viewerContext).enforcing().loadByIDAsync(one.getID()) + TwoTestEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDAsync(one.getID()) ).rejects.toThrowError('TwoTestEntity must be instantiated with two data'); await expect( - OneTestEntity.loader(viewerContext).enforcing().loadByIDAsync(two.getID()) + OneTestEntity.loader(viewerContext, viewerContext.getQueryContext()) + .enforcing() + .loadByIDAsync(two.getID()) ).rejects.toThrowError('OneTestEntity must be instantiated with one data'); - const manyResults = await OneTestEntity.loader(viewerContext).loadManyByFieldEqualingAsync( - 'common_other_field', - 'wat' - ); + const manyResults = await OneTestEntity.loader( + viewerContext, + viewerContext.getQueryContext() + ).loadManyByFieldEqualingAsync('common_other_field', 'wat'); const successfulManyResults = successfulResults(manyResults); const failedManyResults = failedResults(manyResults); @@ -51,7 +55,8 @@ describe('Two entities backed by the same table', () => { ); const fieldEqualityConjunctionResults = await OneTestEntity.loader( - viewerContext + viewerContext, + viewerContext.getQueryContext() ).loadManyByFieldEqualityConjunctionAsync([ { fieldName: 'common_other_field', @@ -106,24 +111,30 @@ const testEntityConfiguration = new EntityConfiguration({ cacheAdapterFlavor: 'redis', }); -class TestEntityPrivacyPolicy extends EntityPrivacyPolicy { +class TestEntityPrivacyPolicy extends EntityPrivacyPolicy< + any, + string, + TestViewerContext, + any, + any +> { protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -class OneTestEntity extends Entity { +class OneTestEntity extends Entity { constructor(constructorParams: { - viewerContext: ViewerContext; + viewerContext: TestViewerContext; id: string; databaseFields: Readonly; selectedFields: Readonly>; @@ -137,7 +148,7 @@ class OneTestEntity extends Entity { +class TwoTestEntity extends Entity { constructor(constructorParams: { - viewerContext: ViewerContext; + viewerContext: TestViewerContext; id: string; databaseFields: Readonly; selectedFields: Readonly>; @@ -167,7 +178,7 @@ class TwoTestEntity extends Entity { test('mutate through different types and keep consistent cache and dataloader', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); - const entity1 = await OneTestEntity.creator(viewerContext) + const entity1 = await OneTestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('fake_field', 'hello') .enforceCreateAsync(); expect(entity1).toBeInstanceOf(OneTestEntity); - const entity2 = await TwoTestEntity.loader(viewerContext) + const entity2 = await TwoTestEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDAsync(entity1.getID()); expect(entity2).toBeInstanceOf(TwoTestEntity); - const updated2 = await TwoTestEntity.updater(entity2) + const updated2 = await TwoTestEntity.updater(entity2, viewerContext.getQueryContext()) .setField('fake_field', 'world') .setField('other_field', 'wat') .enforceUpdateAsync(); @@ -32,7 +32,7 @@ describe('Two entities backed by the same table', () => { fake_field: 'world', }); - const loaded1 = await OneTestEntity.loader(viewerContext) + const loaded1 = await OneTestEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDAsync(entity1.getID()); expect(loaded1.getAllFields()).toMatchObject({ @@ -43,14 +43,14 @@ describe('Two entities backed by the same table', () => { test('cached field that differs between the two to test invalidation', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); - const viewerContext = new ViewerContext(companionProvider); + const viewerContext = new TestViewerContext(companionProvider); - const entity = await TwoTestEntity.creator(viewerContext) + const entity = await TwoTestEntity.creator(viewerContext, viewerContext.getQueryContext()) .setField('fake_field', 'hello') .setField('other_field', 'huh') .enforceCreateAsync(); - const loadedEntity = await TwoTestEntity.loader(viewerContext) + const loadedEntity = await TwoTestEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('other_field', 'huh'); expect(loadedEntity?.getAllFields()).toMatchObject({ @@ -59,12 +59,14 @@ describe('Two entities backed by the same table', () => { other_field: 'huh', }); - const loaded1 = await OneTestEntity.loader(viewerContext) + const loaded1 = await OneTestEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByIDAsync(entity.getID()); - await OneTestEntity.updater(loaded1).setField('fake_field', 'world').enforceUpdateAsync(); + await OneTestEntity.updater(loaded1, viewerContext.getQueryContext()) + .setField('fake_field', 'world') + .enforceUpdateAsync(); - const loaded2 = await TwoTestEntity.loader(viewerContext) + const loaded2 = await TwoTestEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('other_field', 'huh'); expect(loaded2?.getAllFields()).toMatchObject({ @@ -73,7 +75,7 @@ describe('Two entities backed by the same table', () => { other_field: 'huh', }); - const loaded22 = await TwoTestEntity.loader(viewerContext) + const loaded22 = await TwoTestEntity.loader(viewerContext, viewerContext.getQueryContext()) .enforcing() .loadByFieldEqualingAsync('fake_field', 'world'); expect(loaded22?.getAllFields()).toMatchObject({ @@ -114,26 +116,32 @@ const testEntityConfiguration = new EntityConfiguration({ cacheAdapterFlavor: 'redis', }); -class TestEntityPrivacyPolicy extends EntityPrivacyPolicy { +class TestEntityPrivacyPolicy extends EntityPrivacyPolicy< + any, + string, + TestViewerContext, + any, + any +> { protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -class OneTestEntity extends Entity { +class OneTestEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< TestFields, string, - ViewerContext, + TestViewerContext, OneTestEntity, TestEntityPrivacyPolicy, OneTestFields @@ -147,11 +155,11 @@ class OneTestEntity extends Entity { +class TwoTestEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< TestFields, string, - ViewerContext, + TestViewerContext, TwoTestEntity, TestEntityPrivacyPolicy, TwoTestFields diff --git a/packages/entity/src/internal/EntityDataManager.ts b/packages/entity/src/internal/EntityDataManager.ts index 7a513802..067c9750 100644 --- a/packages/entity/src/internal/EntityDataManager.ts +++ b/packages/entity/src/internal/EntityDataManager.ts @@ -76,7 +76,7 @@ export default class EntityDataManager { entityClassName: this.entityClassName, }); return await this.databaseAdapter.fetchManyWhereAsync( - this.queryContextProvider.getQueryContext(), + this.queryContextProvider.getNonTransactionalQueryContext(), fieldName, fetcherValues ); diff --git a/packages/entity/src/internal/__tests__/EntityDataManager-test.ts b/packages/entity/src/internal/__tests__/EntityDataManager-test.ts index 12b79770..65ec6b6e 100644 --- a/packages/entity/src/internal/__tests__/EntityDataManager-test.ts +++ b/packages/entity/src/internal/__tests__/EntityDataManager-test.ts @@ -78,7 +78,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const dbSpy = jest.spyOn(databaseAdapter, 'fetchManyWhereAsync'); const cacheSpy = jest.spyOn(entityCache, 'readManyThroughAsync'); @@ -126,7 +126,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const dbSpy = jest.spyOn(databaseAdapter, 'fetchManyWhereAsync'); const cacheSpy = jest.spyOn(entityCache, 'readManyThroughAsync'); @@ -174,7 +174,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); // use second data manager to ensure that cache is hit instead of data loader const entityDataManager2 = new EntityDataManager( databaseAdapter, @@ -218,7 +218,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const dbSpy = jest.spyOn(databaseAdapter, 'fetchManyWhereAsync'); const cacheSpy = jest.spyOn(entityCache, 'readManyThroughAsync'); @@ -254,7 +254,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const dbSpy = jest.spyOn(databaseAdapter, 'fetchManyWhereAsync'); const cacheSpy = jest.spyOn(entityCache, 'readManyThroughAsync'); @@ -298,7 +298,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const objectInQuestion = objects.get(testEntityConfiguration.tableName)![1]!; @@ -337,7 +337,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const objectInQuestion = objects.get(testEntityConfiguration.tableName)![1]!; @@ -415,7 +415,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); const dbSpy = jest.spyOn(databaseAdapter, 'fetchManyByFieldEqualityConjunctionAsync'); const cacheSpy = jest.spyOn(entityCache, 'readManyThroughAsync'); @@ -461,7 +461,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); await expect( entityDataManager.loadManyByFieldEqualingAsync(queryContext, 'customIdField', ['2']) @@ -488,7 +488,7 @@ describe(EntityDataManager, () => { metricsAdapter, TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); await entityDataManager.loadManyByFieldEqualingAsync(queryContext, 'customIdField', ['1']); verify( @@ -591,7 +591,7 @@ describe(EntityDataManager, () => { new NoOpEntityMetricsAdapter(), TestEntity.name ); - const queryContext = StubQueryContextProvider.getQueryContext(); + const queryContext = StubQueryContextProvider.getNonTransactionalQueryContext(); await expect( entityDataManager.loadManyByFieldEqualingAsync(queryContext, 'nullableField', [null as any]) diff --git a/packages/entity/src/testfixtures/SimpleTestEntity.ts b/packages/entity/src/testfixtures/SimpleTestEntity.ts index e6992ab3..753aca7a 100644 --- a/packages/entity/src/testfixtures/SimpleTestEntity.ts +++ b/packages/entity/src/testfixtures/SimpleTestEntity.ts @@ -1,9 +1,9 @@ +import TestViewerContext from './TestViewerContext'; import Entity from '../Entity'; import { EntityCompanionDefinition } from '../EntityCompanionProvider'; import EntityConfiguration from '../EntityConfiguration'; import { UUIDField } from '../EntityFields'; import EntityPrivacyPolicy from '../EntityPrivacyPolicy'; -import ViewerContext from '../ViewerContext'; import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule'; export type SimpleTestFields = { @@ -27,7 +27,7 @@ export const simpleTestEntityConfiguration = new EntityConfiguration { @@ -35,7 +35,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestFieldSelection >(), @@ -44,7 +44,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestFieldSelection >(), @@ -53,7 +53,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestFieldSelection >(), @@ -62,7 +62,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy< new AlwaysAllowPrivacyPolicyRule< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestFieldSelection >(), @@ -72,13 +72,13 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy< export default class SimpleTestEntity extends Entity< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestFieldSelection > { static defineCompanionDefinition(): EntityCompanionDefinition< SimpleTestFields, string, - ViewerContext, + TestViewerContext, SimpleTestEntity, SimpleTestEntityPrivacyPolicy, SimpleTestFieldSelection diff --git a/packages/entity/src/testfixtures/TestEntity.ts b/packages/entity/src/testfixtures/TestEntity.ts index 5def5fe4..d47da2a5 100644 --- a/packages/entity/src/testfixtures/TestEntity.ts +++ b/packages/entity/src/testfixtures/TestEntity.ts @@ -1,11 +1,11 @@ import { result, Result } from '@expo/results'; +import TestViewerContext from './TestViewerContext'; import Entity from '../Entity'; import { EntityCompanionDefinition } from '../EntityCompanionProvider'; import EntityConfiguration from '../EntityConfiguration'; import { UUIDField, StringField, DateField, IntField } from '../EntityFields'; import EntityPrivacyPolicy from '../EntityPrivacyPolicy'; -import ViewerContext from '../ViewerContext'; import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule'; export type TestFields = { @@ -48,28 +48,28 @@ export const testEntityConfiguration = new EntityConfiguration({ export class TestEntityPrivacyPolicy extends EntityPrivacyPolicy< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity > { protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -export default class TestEntity extends Entity { +export default class TestEntity extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< TestFields, string, - ViewerContext, + TestViewerContext, TestEntity, TestEntityPrivacyPolicy > { @@ -84,7 +84,10 @@ export default class TestEntity extends Entity> { + static async hello( + viewerContext: TestViewerContext, + testValue: string + ): Promise> { const fields = { customIdField: testValue, testIndexedField: 'hello', @@ -103,15 +106,15 @@ export default class TestEntity extends Entity> { + static async returnError(_viewerContext: TestViewerContext): Promise> { return result(new Error('return entity')); } - static async throwError(_viewerContext: ViewerContext): Promise> { + static async throwError(_viewerContext: TestViewerContext): Promise> { throw new Error('threw entity'); } - static async nonResult(_viewerContext: ViewerContext, testValue: string): Promise { + static async nonResult(_viewerContext: TestViewerContext, testValue: string): Promise { return testValue; } } diff --git a/packages/entity/src/testfixtures/TestEntity2.ts b/packages/entity/src/testfixtures/TestEntity2.ts index be286eed..541aaed0 100644 --- a/packages/entity/src/testfixtures/TestEntity2.ts +++ b/packages/entity/src/testfixtures/TestEntity2.ts @@ -1,9 +1,9 @@ +import TestViewerContext from './TestViewerContext'; import Entity from '../Entity'; import { EntityCompanionDefinition } from '../EntityCompanionProvider'; import EntityConfiguration from '../EntityConfiguration'; import { UUIDField } from '../EntityFields'; import EntityPrivacyPolicy from '../EntityPrivacyPolicy'; -import ViewerContext from '../ViewerContext'; import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule'; export type Test2Fields = { @@ -29,28 +29,28 @@ export const testEntity2Configuration = new EntityConfiguration({ export class TestEntity2PrivacyPolicy extends EntityPrivacyPolicy< Test2Fields, string, - ViewerContext, + TestViewerContext, TestEntity2 > { protected override readonly readRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly createRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly updateRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; protected override readonly deleteRules = [ - new AlwaysAllowPrivacyPolicyRule(), + new AlwaysAllowPrivacyPolicyRule(), ]; } -export default class TestEntity2 extends Entity { +export default class TestEntity2 extends Entity { static defineCompanionDefinition(): EntityCompanionDefinition< Test2Fields, string, - ViewerContext, + TestViewerContext, TestEntity2, TestEntity2PrivacyPolicy > { diff --git a/packages/entity/src/testfixtures/TestViewerContext.ts b/packages/entity/src/testfixtures/TestViewerContext.ts index 2ecf1f1e..c0d1580f 100644 --- a/packages/entity/src/testfixtures/TestViewerContext.ts +++ b/packages/entity/src/testfixtures/TestViewerContext.ts @@ -1,3 +1,14 @@ +import { EntityQueryContext, EntityTransactionalQueryContext } from '../EntityQueryContext'; import ViewerContext from '../ViewerContext'; -export default class TestViewerContext extends ViewerContext {} +export default class TestViewerContext extends ViewerContext { + public getQueryContext(): EntityQueryContext { + return super.getNonTransactionalQueryContextForDatabaseAdaptorFlavor('postgres'); + } + + public async runInTransactionAsync( + transactionScope: (queryContext: EntityTransactionalQueryContext) => Promise + ): Promise { + return await super.runInTransactionForDatabaseAdaptorFlavorAsync('postgres', transactionScope); + } +}