From 92bac056876b2dd3f15186a8aa4320c8212aa99c Mon Sep 17 00:00:00 2001 From: Will Schurman Date: Sun, 23 Feb 2025 19:26:22 -0800 Subject: [PATCH] chore: internal refactor of loader to be structured like new mutation methods (#254) --- .../LocalMemorySecondaryEntityCache-test.ts | 16 +- ...isSecondaryEntityCache-integration-test.ts | 16 +- .../AuthorizationResultBasedEntityLoader.ts | 2 +- .../AuthorizationResultBasedEntityMutator.ts | 62 +- .../entity/src/EntityAssociationLoader.ts | 25 +- packages/entity/src/EntityLoader.ts | 57 +- packages/entity/src/EntityLoaderFactory.ts | 23 +- .../entity/src/EntitySecondaryCacheLoader.ts | 12 +- packages/entity/src/ReadonlyEntity.ts | 15 +- .../src/ViewerScopedEntityLoaderFactory.ts | 11 +- ...sultBasedEntityLoader-constructor-test.ts} | 21 +- ...thorizationResultBasedEntityLoader-test.ts | 730 ++++++++++++++++++ .../entity/src/__tests__/EntityLoader-test.ts | 700 +---------------- ...tyMutator-MutationCacheConsistency-test.ts | 3 +- .../src/__tests__/EntityMutator-test.ts | 62 +- .../EntitySecondaryCacheLoader-test.ts | 8 +- .../src/__tests__/ReadonlyEntity-test.ts | 2 +- .../entity/src/utils/EntityPrivacyUtils.ts | 23 +- 18 files changed, 925 insertions(+), 863 deletions(-) rename packages/entity/src/__tests__/{EntityLoader-constructor-test.ts => AuthorizationResultBasedEntityLoader-constructor-test.ts} (90%) create mode 100644 packages/entity/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts 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 1c0d4dbef..a47a0fec8 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 @@ -157,13 +157,13 @@ class TestSecondaryLocalMemoryCacheLoader extends EntitySecondaryCacheLoader< } return nullthrows( ( - await this.entityLoader - .enforcing() - .loadManyByFieldEqualityConjunctionAsync([ - { fieldName: 'id', fieldValue: loadParams.id }, - ]) + await this.entityLoader.loadManyByFieldEqualityConjunctionAsync([ + { fieldName: 'id', fieldValue: loadParams.id }, + ]) )[0], - ).getAllFields(); + ) + .enforceValue() + .getAllFields(); }); } } @@ -182,7 +182,7 @@ describe(LocalMemorySecondaryEntityCache, () => { localMemoryTestEntityConfiguration, GenericLocalMemoryCacher.createLRUCache({}), ), - LocalMemoryTestEntity.loader(viewerContext), + LocalMemoryTestEntity.loader(viewerContext).withAuthorizationResults(), ); const loadParams = { id: createdEntity.getID() }; @@ -218,7 +218,7 @@ describe(LocalMemorySecondaryEntityCache, () => { localMemoryTestEntityConfiguration, GenericLocalMemoryCacher.createLRUCache({}), ), - LocalMemoryTestEntity.loader(viewerContext), + LocalMemoryTestEntity.loader(viewerContext).withAuthorizationResults(), ); 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 95c012c84..cd0725e79 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 @@ -40,13 +40,13 @@ class TestSecondaryRedisCacheLoader extends EntitySecondaryCacheLoader< } return nullthrows( ( - await this.entityLoader - .enforcing() - .loadManyByFieldEqualityConjunctionAsync([ - { fieldName: 'id', fieldValue: loadParams.id }, - ]) + await this.entityLoader.loadManyByFieldEqualityConjunctionAsync([ + { fieldName: 'id', fieldValue: loadParams.id }, + ]) )[0], - ).getAllFields(); + ) + .enforceValue() + .getAllFields(); }); } } @@ -90,7 +90,7 @@ describe(RedisSecondaryEntityCache, () => { genericRedisCacheContext, (loadParams) => `test-key-${loadParams.id}`, ), - RedisTestEntity.loader(viewerContext), + RedisTestEntity.loader(viewerContext).withAuthorizationResults(), ); const loadParams = { id: createdEntity.getID() }; @@ -129,7 +129,7 @@ describe(RedisSecondaryEntityCache, () => { genericRedisCacheContext, (loadParams) => `test-key-${loadParams.id}`, ), - RedisTestEntity.loader(viewerContext), + RedisTestEntity.loader(viewerContext).withAuthorizationResults(), ); const loadParams = { id: FAKE_ID }; diff --git a/packages/entity/src/AuthorizationResultBasedEntityLoader.ts b/packages/entity/src/AuthorizationResultBasedEntityLoader.ts index d41135ada..7af15b6bc 100644 --- a/packages/entity/src/AuthorizationResultBasedEntityLoader.ts +++ b/packages/entity/src/AuthorizationResultBasedEntityLoader.ts @@ -54,7 +54,7 @@ export default class AuthorizationResultBasedEntityLoader< >, private readonly dataManager: EntityDataManager, protected readonly metricsAdapter: IEntityMetricsAdapter, - private readonly utils: EntityLoaderUtils< + public readonly utils: EntityLoaderUtils< TFields, TID, TViewerContext, diff --git a/packages/entity/src/AuthorizationResultBasedEntityMutator.ts b/packages/entity/src/AuthorizationResultBasedEntityMutator.ts index 3c08d81eb..6353621d3 100644 --- a/packages/entity/src/AuthorizationResultBasedEntityMutator.ts +++ b/packages/entity/src/AuthorizationResultBasedEntityMutator.ts @@ -21,6 +21,7 @@ import EntityMutationValidator from './EntityMutationValidator'; import EntityPrivacyPolicy from './EntityPrivacyPolicy'; import { EntityQueryContext, EntityTransactionalQueryContext } from './EntityQueryContext'; import ViewerContext from './ViewerContext'; +import { enforceResultsAsync } from './entityUtils'; import EntityInvalidFieldValueError from './errors/EntityInvalidFieldValueError'; import { timeAndLogMutationEventAsync } from './metrics/EntityMetricsUtils'; import IEntityMetricsAdapter, { EntityMetricsMutationType } from './metrics/IEntityMetricsAdapter'; @@ -216,7 +217,7 @@ export class AuthorizationResultBasedCreateMutator< cascadingDeleteCause: null, }); - const temporaryEntityForPrivacyCheck = entityLoader.utils().constructEntity({ + const temporaryEntityForPrivacyCheck = entityLoader.utils.constructEntity({ [this.entityConfiguration.idField]: '00000000-0000-0000-0000-000000000000', // zero UUID ...this.fieldsForEntity, } as unknown as TFields); @@ -256,13 +257,13 @@ export class AuthorizationResultBasedCreateMutator< const insertResult = await this.databaseAdapter.insertAsync(queryContext, this.fieldsForEntity); queryContext.appendPostCommitInvalidationCallback( - entityLoader.utils().invalidateFieldsAsync.bind(entityLoader, insertResult), + entityLoader.utils.invalidateFieldsAsync.bind(entityLoader, insertResult), ); - const unauthorizedEntityAfterInsert = entityLoader.utils().constructEntity(insertResult); - const newEntity = await entityLoader - .enforcing() - .loadByIDAsync(unauthorizedEntityAfterInsert.getID()); + const unauthorizedEntityAfterInsert = entityLoader.utils.constructEntity(insertResult); + const newEntity = await enforceAsyncResult( + entityLoader.loadByIDAsync(unauthorizedEntityAfterInsert.getID()), + ); await this.executeMutationTriggersAsync( this.mutationTriggers.afterCreate, @@ -429,7 +430,7 @@ export class AuthorizationResultBasedUpdateMutator< cascadingDeleteCause, }); - const entityAboutToBeUpdated = entityLoader.utils().constructEntity(this.fieldsForEntity); + const entityAboutToBeUpdated = entityLoader.utils.constructEntity(this.fieldsForEntity); const authorizeUpdateResult = await asyncResult( this.privacyPolicy.authorizeUpdateAsync( this.viewerContext, @@ -473,17 +474,18 @@ export class AuthorizationResultBasedUpdateMutator< } queryContext.appendPostCommitInvalidationCallback( - entityLoader - .utils() - .invalidateFieldsAsync.bind(entityLoader, this.originalEntity.getAllDatabaseFields()), + entityLoader.utils.invalidateFieldsAsync.bind( + entityLoader, + this.originalEntity.getAllDatabaseFields(), + ), ); queryContext.appendPostCommitInvalidationCallback( - entityLoader.utils().invalidateFieldsAsync.bind(entityLoader, this.fieldsForEntity), + entityLoader.utils.invalidateFieldsAsync.bind(entityLoader, this.fieldsForEntity), ); - const updatedEntity = await entityLoader - .enforcing() - .loadByIDAsync(entityAboutToBeUpdated.getID()); // ID is guaranteed to be stable by ensureStableIDField + const updatedEntity = await enforceAsyncResult( + entityLoader.loadByIDAsync(entityAboutToBeUpdated.getID()), + ); // ID is guaranteed to be stable by ensureStableIDField await this.executeMutationTriggersAsync( this.mutationTriggers.afterUpdate, @@ -681,9 +683,10 @@ export class AuthorizationResultBasedDeleteMutator< cascadingDeleteCause, }); queryContext.appendPostCommitInvalidationCallback( - entityLoader - .utils() - .invalidateFieldsAsync.bind(entityLoader, this.entity.getAllDatabaseFields()), + entityLoader.utils.invalidateFieldsAsync.bind( + entityLoader, + this.entity.getAllDatabaseFields(), + ), ); await this.executeMutationTriggersAsync( @@ -782,18 +785,19 @@ export class AuthorizationResultBasedDeleteMutator< return; } - const inboundReferenceEntities = await loaderFactory - .forLoad(queryContext, { - previousValue: null, - cascadingDeleteCause: newCascadingDeleteCause, - }) - .enforcing() - .loadManyByFieldEqualingAsync( - fieldName, - association.associatedEntityLookupByField - ? entity.getField(association.associatedEntityLookupByField as any) - : entity.getID(), - ); + const inboundReferenceEntities = await enforceResultsAsync( + loaderFactory + .forLoad(queryContext, { + previousValue: null, + cascadingDeleteCause: newCascadingDeleteCause, + }) + .loadManyByFieldEqualingAsync( + fieldName, + association.associatedEntityLookupByField + ? entity.getField(association.associatedEntityLookupByField as any) + : entity.getID(), + ), + ); switch (association.edgeDeletionBehavior) { case EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY: { diff --git a/packages/entity/src/EntityAssociationLoader.ts b/packages/entity/src/EntityAssociationLoader.ts index 488a653d2..4895b9f02 100644 --- a/packages/entity/src/EntityAssociationLoader.ts +++ b/packages/entity/src/EntityAssociationLoader.ts @@ -76,9 +76,7 @@ export default class EntityAssociationLoader< .getLoaderFactory() .forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null }); - return (await loader - .withAuthorizationResults() - .loadByIDAsync(associatedEntityID as unknown as TAssociatedID)) as Result< + return (await loader.loadByIDAsync(associatedEntityID as unknown as TAssociatedID)) as Result< null extends TFields[TIdentifyingField] ? TAssociatedEntity | null : TAssociatedEntity >; } @@ -131,9 +129,10 @@ export default class EntityAssociationLoader< .getViewerScopedEntityCompanionForClass(associatedEntityClass) .getLoaderFactory() .forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null }); - return await loader - .withAuthorizationResults() - .loadManyByFieldEqualingAsync(associatedEntityFieldContainingThisID, thisID as any); + return await loader.loadManyByFieldEqualingAsync( + associatedEntityFieldContainingThisID, + thisID as any, + ); } /** @@ -187,9 +186,10 @@ export default class EntityAssociationLoader< .getViewerScopedEntityCompanionForClass(associatedEntityClass) .getLoaderFactory() .forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null }); - return await loader - .withAuthorizationResults() - .loadByFieldEqualingAsync(associatedEntityLookupByField, associatedFieldValue as any); + return await loader.loadByFieldEqualingAsync( + associatedEntityLookupByField, + associatedFieldValue as any, + ); } /** @@ -244,9 +244,10 @@ export default class EntityAssociationLoader< .getViewerScopedEntityCompanionForClass(associatedEntityClass) .getLoaderFactory() .forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null }); - return await loader - .withAuthorizationResults() - .loadManyByFieldEqualingAsync(associatedEntityLookupByField, associatedFieldValue as any); + return await loader.loadManyByFieldEqualingAsync( + associatedEntityLookupByField, + associatedFieldValue as any, + ); } /** diff --git a/packages/entity/src/EntityLoader.ts b/packages/entity/src/EntityLoader.ts index 92579477c..60e3cdead 100644 --- a/packages/entity/src/EntityLoader.ts +++ b/packages/entity/src/EntityLoader.ts @@ -1,14 +1,11 @@ import AuthorizationResultBasedEntityLoader from './AuthorizationResultBasedEntityLoader'; import EnforcingEntityLoader from './EnforcingEntityLoader'; import { IEntityClass } from './Entity'; -import EntityConfiguration from './EntityConfiguration'; import EntityLoaderUtils from './EntityLoaderUtils'; -import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy'; +import EntityPrivacyPolicy from './EntityPrivacyPolicy'; import { EntityQueryContext } from './EntityQueryContext'; import ReadonlyEntity from './ReadonlyEntity'; import ViewerContext from './ViewerContext'; -import EntityDataManager from './internal/EntityDataManager'; -import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter'; /** * The primary interface for loading entities. All normal loads are batched, @@ -18,6 +15,7 @@ export default class EntityLoader< TFields extends object, TID extends NonNullable, TViewerContext extends ViewerContext, + TViewerContext2 extends TViewerContext, TEntity extends ReadonlyEntity, TPrivacyPolicy extends EntityPrivacyPolicy< TFields, @@ -28,26 +26,9 @@ export default class EntityLoader< >, TSelectedFields extends keyof TFields, > { - private readonly utilsPrivate: EntityLoaderUtils< - TFields, - TID, - TViewerContext, - TEntity, - TPrivacyPolicy, - TSelectedFields - >; - constructor( - private readonly viewerContext: TViewerContext, + private readonly viewerContext: TViewerContext2, private readonly queryContext: EntityQueryContext, - private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext< - TFields, - TID, - TViewerContext, - TEntity, - TSelectedFields - >, - private readonly entityConfiguration: EntityConfiguration, private readonly entityClass: IEntityClass< TFields, TID, @@ -56,23 +37,7 @@ export default class EntityLoader< TPrivacyPolicy, TSelectedFields >, - private readonly entitySelectedFields: TSelectedFields[] | undefined, - private readonly privacyPolicy: TPrivacyPolicy, - private readonly dataManager: EntityDataManager, - protected readonly metricsAdapter: IEntityMetricsAdapter, - ) { - this.utilsPrivate = new EntityLoaderUtils( - this.viewerContext, - this.queryContext, - this.privacyPolicyEvaluationContext, - this.entityConfiguration, - this.entityClass, - this.entitySelectedFields, - this.privacyPolicy, - this.dataManager, - this.metricsAdapter, - ); - } + ) {} /** * Enforcing entity loader. All loads through this loader are @@ -103,14 +68,10 @@ export default class EntityLoader< TPrivacyPolicy, TSelectedFields > { - return new AuthorizationResultBasedEntityLoader( - this.queryContext, - this.entityConfiguration, - this.entityClass, - this.dataManager, - this.metricsAdapter, - this.utilsPrivate, - ); + return this.viewerContext + .getViewerScopedEntityCompanionForClass(this.entityClass) + .getLoaderFactory() + .forLoad(this.queryContext, { previousValue: null, cascadingDeleteCause: null }); } /** @@ -125,6 +86,6 @@ export default class EntityLoader< TPrivacyPolicy, TSelectedFields > { - return this.utilsPrivate; + return this.withAuthorizationResults().utils; } } diff --git a/packages/entity/src/EntityLoaderFactory.ts b/packages/entity/src/EntityLoaderFactory.ts index d4254febe..868f9823c 100644 --- a/packages/entity/src/EntityLoaderFactory.ts +++ b/packages/entity/src/EntityLoaderFactory.ts @@ -1,5 +1,6 @@ +import AuthorizationResultBasedEntityLoader from './AuthorizationResultBasedEntityLoader'; import EntityCompanion from './EntityCompanion'; -import EntityLoader from './EntityLoader'; +import EntityLoaderUtils from './EntityLoaderUtils'; import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy'; import { EntityQueryContext } from './EntityQueryContext'; import ReadonlyEntity from './ReadonlyEntity'; @@ -52,8 +53,15 @@ export default class EntityLoaderFactory< TEntity, TSelectedFields >, - ): EntityLoader { - return new EntityLoader( + ): AuthorizationResultBasedEntityLoader< + TFields, + TID, + TViewerContext, + TEntity, + TPrivacyPolicy, + TSelectedFields + > { + const utils = new EntityLoaderUtils( viewerContext, queryContext, privacyPolicyEvaluationContext, @@ -64,5 +72,14 @@ export default class EntityLoaderFactory< this.dataManager, this.metricsAdapter, ); + + return new AuthorizationResultBasedEntityLoader( + queryContext, + this.entityCompanion.entityCompanionDefinition.entityConfiguration, + this.entityCompanion.entityCompanionDefinition.entityClass, + this.dataManager, + this.metricsAdapter, + utils, + ); } } diff --git a/packages/entity/src/EntitySecondaryCacheLoader.ts b/packages/entity/src/EntitySecondaryCacheLoader.ts index fef6a4f98..17378be55 100644 --- a/packages/entity/src/EntitySecondaryCacheLoader.ts +++ b/packages/entity/src/EntitySecondaryCacheLoader.ts @@ -1,6 +1,6 @@ import { Result } from '@expo/results'; -import EntityLoader from './EntityLoader'; +import AuthorizationResultBasedEntityLoader from './AuthorizationResultBasedEntityLoader'; import EntityPrivacyPolicy from './EntityPrivacyPolicy'; import ReadonlyEntity from './ReadonlyEntity'; import ViewerContext from './ViewerContext'; @@ -60,7 +60,7 @@ export default abstract class EntitySecondaryCacheLoader< > { constructor( private readonly secondaryEntityCache: ISecondaryEntityCache, - protected readonly entityLoader: EntityLoader< + protected readonly entityLoader: AuthorizationResultBasedEntityLoader< TFields, TID, TViewerContext, @@ -84,11 +84,9 @@ export default abstract class EntitySecondaryCacheLoader< ); // convert value to and from array to reuse complex code - const entitiesMap = await this.entityLoader - .utils() - .constructAndAuthorizeEntitiesAsync( - mapMap(loadParamsToFieldObjects, (fieldObject) => (fieldObject ? [fieldObject] : [])), - ); + const entitiesMap = await this.entityLoader.utils.constructAndAuthorizeEntitiesAsync( + mapMap(loadParamsToFieldObjects, (fieldObject) => (fieldObject ? [fieldObject] : [])), + ); return mapMap(entitiesMap, (fieldObjects) => fieldObjects[0] ?? null); } diff --git a/packages/entity/src/ReadonlyEntity.ts b/packages/entity/src/ReadonlyEntity.ts index 386200a30..d23fb9d14 100644 --- a/packages/entity/src/ReadonlyEntity.ts +++ b/packages/entity/src/ReadonlyEntity.ts @@ -152,10 +152,15 @@ export default abstract class ReadonlyEntity< .getViewerScopedEntityCompanionForClass(this) .getQueryContextProvider() .getQueryContext(), - ): EntityLoader { - return viewerContext - .getViewerScopedEntityCompanionForClass(this) - .getLoaderFactory() - .forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null }); + ): EntityLoader< + TMFields, + TMID, + TMViewerContext, + TMViewerContext2, + TMEntity, + TMPrivacyPolicy, + TMSelectedFields + > { + return new EntityLoader(viewerContext, queryContext, this); } } diff --git a/packages/entity/src/ViewerScopedEntityLoaderFactory.ts b/packages/entity/src/ViewerScopedEntityLoaderFactory.ts index a0a32668e..7a7be8a82 100644 --- a/packages/entity/src/ViewerScopedEntityLoaderFactory.ts +++ b/packages/entity/src/ViewerScopedEntityLoaderFactory.ts @@ -1,4 +1,4 @@ -import EntityLoader from './EntityLoader'; +import AuthorizationResultBasedEntityLoader from './AuthorizationResultBasedEntityLoader'; import EntityLoaderFactory from './EntityLoaderFactory'; import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy'; import { EntityQueryContext } from './EntityQueryContext'; @@ -43,7 +43,14 @@ export default class ViewerScopedEntityLoaderFactory< TEntity, TSelectedFields >, - ): EntityLoader { + ): AuthorizationResultBasedEntityLoader< + TFields, + TID, + TViewerContext, + TEntity, + TPrivacyPolicy, + TSelectedFields + > { return this.entityLoaderFactory.forLoad( this.viewerContext, queryContext, diff --git a/packages/entity/src/__tests__/EntityLoader-constructor-test.ts b/packages/entity/src/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.ts similarity index 90% rename from packages/entity/src/__tests__/EntityLoader-constructor-test.ts rename to packages/entity/src/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.ts index 382f8f1fb..3789fbb5d 100644 --- a/packages/entity/src/__tests__/EntityLoader-constructor-test.ts +++ b/packages/entity/src/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.ts @@ -1,10 +1,11 @@ import { instance, mock } from 'ts-mockito'; +import AuthorizationResultBasedEntityLoader from '../AuthorizationResultBasedEntityLoader'; import Entity from '../Entity'; import { EntityCompanionDefinition } from '../EntityCompanionProvider'; import EntityConfiguration from '../EntityConfiguration'; import { StringField } from '../EntityFields'; -import EntityLoader from '../EntityLoader'; +import EntityLoaderUtils from '../EntityLoaderUtils'; import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy'; import ViewerContext from '../ViewerContext'; import EntityDataManager from '../internal/EntityDataManager'; @@ -118,7 +119,7 @@ export default class TestEntity extends Entity< } } -describe(EntityLoader, () => { +describe(AuthorizationResultBasedEntityLoader, () => { it('handles thrown errors and literals from constructor', async () => { const viewerContext = instance(mock(ViewerContext)); const privacyPolicyEvaluationContext = @@ -166,7 +167,7 @@ describe(EntityLoader, () => { metricsAdapter, TestEntity.name, ); - const entityLoader = new EntityLoader( + const utils = new EntityLoaderUtils( viewerContext, queryContext, privacyPolicyEvaluationContext, @@ -177,19 +178,25 @@ describe(EntityLoader, () => { dataManager, metricsAdapter, ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManager, + metricsAdapter, + utils, + ); let capturedThrownThing1: any; try { - await entityLoader.withAuthorizationResults().loadByIDAsync(ID_SENTINEL_THROW_LITERAL); + await entityLoader.loadByIDAsync(ID_SENTINEL_THROW_LITERAL); } catch (e) { capturedThrownThing1 = e; } expect(capturedThrownThing1).not.toBeInstanceOf(Error); expect(capturedThrownThing1).toEqual('hello'); - const result = await entityLoader - .withAuthorizationResults() - .loadByIDAsync(ID_SENTINEL_THROW_ERROR); + const result = await entityLoader.loadByIDAsync(ID_SENTINEL_THROW_ERROR); expect(result.ok).toBe(false); expect(result.enforceError().message).toEqual('world'); }); diff --git a/packages/entity/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts b/packages/entity/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts new file mode 100644 index 000000000..3a959140e --- /dev/null +++ b/packages/entity/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts @@ -0,0 +1,730 @@ +import { enforceAsyncResult } from '@expo/results'; +import { mock, instance, verify, spy, deepEqual, anyOfClass, anything, when } from 'ts-mockito'; +import { v4 as uuidv4 } from 'uuid'; + +import AuthorizationResultBasedEntityLoader from '../AuthorizationResultBasedEntityLoader'; +import { OrderByOrdering } from '../EntityDatabaseAdapter'; +import EntityLoaderUtils from '../EntityLoaderUtils'; +import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy'; +import ViewerContext from '../ViewerContext'; +import { enforceResultsAsync } from '../entityUtils'; +import EntityNotFoundError from '../errors/EntityNotFoundError'; +import EntityDataManager from '../internal/EntityDataManager'; +import ReadThroughEntityCache from '../internal/ReadThroughEntityCache'; +import IEntityMetricsAdapter from '../metrics/IEntityMetricsAdapter'; +import TestEntity, { + TestFields, + TestEntityPrivacyPolicy, + testEntityConfiguration, +} from '../testfixtures/TestEntity'; +import { NoCacheStubCacheAdapterProvider } from '../utils/testing/StubCacheAdapter'; +import StubDatabaseAdapter from '../utils/testing/StubDatabaseAdapter'; +import StubQueryContextProvider from '../utils/testing/StubQueryContextProvider'; + +describe(AuthorizationResultBasedEntityLoader, () => { + it('loads entities', async () => { + const dateToInsert = new Date(); + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + + const id1 = uuidv4(); + const id2 = uuidv4(); + const databaseAdapter = new StubDatabaseAdapter( + testEntityConfiguration, + StubDatabaseAdapter.convertFieldObjectsToDataStore( + testEntityConfiguration, + new Map([ + [ + testEntityConfiguration.tableName, + [ + { + customIdField: id1, + testIndexedField: 'h1', + intField: 5, + stringField: 'huh', + dateField: dateToInsert, + nullableField: null, + }, + { + customIdField: id2, + testIndexedField: 'h2', + intField: 3, + stringField: 'huh', + dateField: dateToInsert, + nullableField: null, + }, + ], + ], + ]), + ), + ); + const privacyPolicy = new TestEntityPrivacyPolicy(); + const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); + const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); + const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); + const dataManager = new EntityDataManager( + databaseAdapter, + entityCache, + StubQueryContextProvider, + instance(mock()), + TestEntity.name, + ); + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManager, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManager, + metricsAdapter, + utils, + ); + + const entity = await enforceAsyncResult(entityLoader.loadByIDAsync(id1)); + expect(entity.getID()).toEqual(id1); + expect(entity.getField('dateField')).toEqual(dateToInsert); + + const entities = await enforceResultsAsync( + entityLoader.loadManyByFieldEqualingAsync('stringField', 'huh'), + ); + expect(entities.map((m) => m.getID())).toEqual([id1, id2]); + + const entityResultNumber3 = await entityLoader.loadByFieldEqualingAsync('intField', 3); + expect(entityResultNumber3).not.toBeNull(); + expect(entityResultNumber3!.enforceValue().getID()).toEqual(id2); + + const entityResultNumber4 = await entityLoader.loadByFieldEqualingAsync('intField', 4); + expect(entityResultNumber4).toBeNull(); + + const entityResultDuplicateValues = await entityLoader.loadManyByFieldEqualingManyAsync( + 'stringField', + ['huh', 'huh'], + ); + expect(entityResultDuplicateValues.size).toBe(1); + expect(entityResultDuplicateValues.get('huh')?.map((m) => m.enforceValue().getID())).toEqual([ + id1, + id2, + ]); + + await expect(entityLoader.loadByFieldEqualingAsync('stringField', 'huh')).rejects.toThrowError( + 'loadByFieldEqualing: Multiple entities of type TestEntity found for stringField=huh', + ); + + await expect(entityLoader.loadByIDNullableAsync(uuidv4())).resolves.toBeNull(); + await expect(entityLoader.loadByIDNullableAsync(id1)).resolves.not.toBeNull(); + + const nonExistentId = uuidv4(); + const manyIdResults = await entityLoader.loadManyByIDsNullableAsync([nonExistentId, id1]); + expect(manyIdResults.get(nonExistentId)).toBeNull(); + expect(manyIdResults.get(id1)).not.toBeNull(); + + await expect(enforceAsyncResult(entityLoader.loadByIDAsync(nonExistentId))).rejects.toThrow( + EntityNotFoundError, + ); + + await expect(entityLoader.loadByIDAsync('not-a-uuid')).rejects.toThrowError( + 'Entity field not valid: TestEntity (customIdField = not-a-uuid)', + ); + }); + + it('loads entities with loadManyByFieldEqualityConjunction', async () => { + const privacyPolicy = new TestEntityPrivacyPolicy(); + const spiedPrivacyPolicy = spy(privacyPolicy); + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + + const id1 = uuidv4(); + const id2 = uuidv4(); + const id3 = uuidv4(); + const databaseAdapter = new StubDatabaseAdapter( + testEntityConfiguration, + StubDatabaseAdapter.convertFieldObjectsToDataStore( + testEntityConfiguration, + new Map([ + [ + testEntityConfiguration.tableName, + [ + { + customIdField: id1, + stringField: 'huh', + intField: 4, + testIndexedField: '4', + dateField: new Date(), + nullableField: null, + }, + { + customIdField: id2, + stringField: 'huh', + intField: 4, + testIndexedField: '5', + dateField: new Date(), + nullableField: null, + }, + { + customIdField: id3, + stringField: 'huh2', + intField: 4, + testIndexedField: '6', + dateField: new Date(), + nullableField: null, + }, + ], + ], + ]), + ), + ); + const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); + const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); + const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); + const dataManager = new EntityDataManager( + databaseAdapter, + entityCache, + StubQueryContextProvider, + instance(mock()), + TestEntity.name, + ); + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManager, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManager, + metricsAdapter, + utils, + ); + const entityResults = await enforceResultsAsync( + entityLoader.loadManyByFieldEqualityConjunctionAsync([ + { + fieldName: 'stringField', + fieldValue: 'huh', + }, + { + fieldName: 'intField', + fieldValues: [4], + }, + ]), + ); + expect(entityResults).toHaveLength(2); + verify( + spiedPrivacyPolicy.authorizeReadAsync( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + anyOfClass(TestEntity), + anything(), + ), + ).twice(); + + await expect( + entityLoader.loadManyByFieldEqualityConjunctionAsync([ + { fieldName: 'customIdField', fieldValue: 'not-a-uuid' }, + ]), + ).rejects.toThrowError('Entity field not valid: TestEntity (customIdField = not-a-uuid)'); + }); + + it('loads entities with loadFirstByFieldEqualityConjunction', async () => { + const privacyPolicy = new TestEntityPrivacyPolicy(); + const spiedPrivacyPolicy = spy(privacyPolicy); + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + + const id1 = uuidv4(); + const id2 = uuidv4(); + const id3 = uuidv4(); + const databaseAdapter = new StubDatabaseAdapter( + testEntityConfiguration, + StubDatabaseAdapter.convertFieldObjectsToDataStore( + testEntityConfiguration, + new Map([ + [ + testEntityConfiguration.tableName, + [ + { + customIdField: id1, + stringField: 'huh', + intField: 4, + testIndexedField: '4', + dateField: new Date(), + nullableField: null, + }, + { + customIdField: id2, + stringField: 'huh', + intField: 4, + testIndexedField: '5', + dateField: new Date(), + nullableField: null, + }, + { + customIdField: id3, + stringField: 'huh2', + intField: 4, + testIndexedField: '6', + dateField: new Date(), + nullableField: null, + }, + ], + ], + ]), + ), + ); + const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); + const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); + const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); + const dataManager = new EntityDataManager( + databaseAdapter, + entityCache, + StubQueryContextProvider, + instance(mock()), + TestEntity.name, + ); + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManager, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManager, + metricsAdapter, + utils, + ); + const result = await entityLoader.loadFirstByFieldEqualityConjunctionAsync( + [ + { + fieldName: 'stringField', + fieldValue: 'huh', + }, + { + fieldName: 'intField', + fieldValue: 4, + }, + ], + { orderBy: [{ fieldName: 'testIndexedField', order: OrderByOrdering.DESCENDING }] }, + ); + expect(result).not.toBeNull(); + expect(result!.ok).toBe(true); + expect(result!.enforceValue().getField('testIndexedField')).toEqual('5'); + verify( + spiedPrivacyPolicy.authorizeReadAsync( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + anyOfClass(TestEntity), + anything(), + ), + ).once(); + }); + + it('loads entities with loadManyByRawWhereClauseAsync', async () => { + const privacyPolicy = new TestEntityPrivacyPolicy(); + const spiedPrivacyPolicy = spy(privacyPolicy); + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + + const dataManagerMock = mock>(EntityDataManager); + when( + dataManagerMock.loadManyByRawWhereClauseAsync( + queryContext, + anything(), + anything(), + anything(), + ), + ).thenResolve([ + { + customIdField: 'id', + stringField: 'huh', + intField: 4, + testIndexedField: '4', + dateField: new Date(), + nullableField: null, + }, + ]); + const dataManager = instance(dataManagerMock); + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManager, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManager, + metricsAdapter, + utils, + ); + const result = await entityLoader.loadManyByRawWhereClauseAsync('id = ?', [1], { + orderBy: [{ fieldName: 'testIndexedField', order: OrderByOrdering.DESCENDING }], + }); + expect(result).toHaveLength(1); + expect(result[0]).not.toBeNull(); + expect(result[0]!.ok).toBe(true); + expect(result[0]!.enforceValue().getField('testIndexedField')).toEqual('4'); + verify( + spiedPrivacyPolicy.authorizeReadAsync( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + anyOfClass(TestEntity), + anything(), + ), + ).once(); + }); + + it('authorizes loaded entities', async () => { + const privacyPolicy = new TestEntityPrivacyPolicy(); + const spiedPrivacyPolicy = spy(privacyPolicy); + + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + + const id1 = uuidv4(); + const databaseAdapter = new StubDatabaseAdapter( + testEntityConfiguration, + StubDatabaseAdapter.convertFieldObjectsToDataStore( + testEntityConfiguration, + new Map([ + [ + testEntityConfiguration.tableName, + [ + { + customIdField: id1, + stringField: 'huh', + testIndexedField: '1', + intField: 3, + dateField: new Date(), + nullableField: null, + }, + ], + ], + ]), + ), + ); + const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); + const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); + const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); + const dataManager = new EntityDataManager( + databaseAdapter, + entityCache, + StubQueryContextProvider, + instance(mock()), + TestEntity.name, + ); + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManager, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManager, + metricsAdapter, + utils, + ); + const entity = await enforceAsyncResult(entityLoader.loadByIDAsync(id1)); + verify( + spiedPrivacyPolicy.authorizeReadAsync( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + entity, + anything(), + ), + ).once(); + }); + + it('invalidates upon invalidate one', async () => { + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); + const dataManagerMock = mock>(); + const dataManagerInstance = instance(dataManagerMock); + + const id1 = uuidv4(); + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManagerInstance, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManagerInstance, + metricsAdapter, + utils, + ); + await entityLoader.utils.invalidateFieldsAsync({ customIdField: id1 } as any); + + verify( + dataManagerMock.invalidateObjectFieldsAsync(deepEqual({ customIdField: id1 } as any)), + ).once(); + }); + + it('invalidates upon invalidate by field', async () => { + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); + const dataManagerMock = mock>(); + const dataManagerInstance = instance(dataManagerMock); + + const id1 = uuidv4(); + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManagerInstance, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManagerInstance, + metricsAdapter, + utils, + ); + await entityLoader.utils.invalidateFieldsAsync({ customIdField: id1 } as any); + verify( + dataManagerMock.invalidateObjectFieldsAsync(deepEqual({ customIdField: id1 } as any)), + ).once(); + }); + + it('invalidates upon invalidate by entity', async () => { + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); + const dataManagerMock = mock>(); + const dataManagerInstance = instance(dataManagerMock); + + const id1 = uuidv4(); + const entityMock = mock(TestEntity); + when(entityMock.getAllDatabaseFields()).thenReturn({ customIdField: id1 } as any); + const entityInstance = instance(entityMock); + + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManagerInstance, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManagerInstance, + metricsAdapter, + utils, + ); + await entityLoader.utils.invalidateEntityAsync(entityInstance); + verify( + dataManagerMock.invalidateObjectFieldsAsync(deepEqual({ customIdField: id1 } as any)), + ).once(); + }); + + it('returns error result when not allowed', async () => { + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + const privacyPolicyMock = mock(TestEntityPrivacyPolicy); + const dataManagerMock = mock>(); + + const id1 = uuidv4(); + when( + dataManagerMock.loadManyByFieldEqualingAsync(anything(), anything(), anything()), + ).thenResolve(new Map().set(id1, [{ customIdField: id1 }])); + + const rejectionError = new Error(); + + when( + privacyPolicyMock.authorizeReadAsync( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + anyOfClass(TestEntity), + anything(), + ), + ).thenReject(rejectionError); + + const privacyPolicy = instance(privacyPolicyMock); + const dataManagerInstance = instance(dataManagerMock); + + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManagerInstance, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManagerInstance, + metricsAdapter, + utils, + ); + + const entityResult = await entityLoader.loadByIDAsync(id1); + expect(entityResult.ok).toBe(false); + expect(entityResult.reason).toEqual(rejectionError); + expect(entityResult.value).toBe(undefined); + }); + + it('throws upon database adapter error', async () => { + const viewerContext = instance(mock(ViewerContext)); + const privacyPolicyEvaluationContext = + instance( + mock>(), + ); + const metricsAdapter = instance(mock()); + const queryContext = StubQueryContextProvider.getQueryContext(); + const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); + const dataManagerMock = mock>(); + + const error = new Error(); + + when( + dataManagerMock.loadManyByFieldEqualingAsync(anything(), anything(), anything()), + ).thenReject(error); + + const dataManagerInstance = instance(dataManagerMock); + + const utils = new EntityLoaderUtils( + viewerContext, + queryContext, + privacyPolicyEvaluationContext, + testEntityConfiguration, + TestEntity, + /* entitySelectedFields */ undefined, + privacyPolicy, + dataManagerInstance, + metricsAdapter, + ); + const entityLoader = new AuthorizationResultBasedEntityLoader( + queryContext, + testEntityConfiguration, + TestEntity, + dataManagerInstance, + metricsAdapter, + utils, + ); + + const loadByValue = uuidv4(); + + await expect(entityLoader.loadByIDAsync(loadByValue)).rejects.toEqual(error); + await expect(entityLoader.loadManyByIDsAsync([loadByValue])).rejects.toEqual(error); + await expect(entityLoader.loadManyByIDsNullableAsync([loadByValue])).rejects.toEqual(error); + await expect( + entityLoader.loadManyByFieldEqualingAsync('customIdField', loadByValue), + ).rejects.toEqual(error); + await expect( + entityLoader.loadManyByFieldEqualingManyAsync('customIdField', [loadByValue]), + ).rejects.toEqual(error); + }); +}); diff --git a/packages/entity/src/__tests__/EntityLoader-test.ts b/packages/entity/src/__tests__/EntityLoader-test.ts index 7aeeefffa..84c757831 100644 --- a/packages/entity/src/__tests__/EntityLoader-test.ts +++ b/packages/entity/src/__tests__/EntityLoader-test.ts @@ -1,687 +1,37 @@ -import { enforceAsyncResult } from '@expo/results'; -import { mock, instance, verify, spy, deepEqual, anyOfClass, anything, when } from 'ts-mockito'; -import { v4 as uuidv4 } from 'uuid'; - -import { OrderByOrdering } from '../EntityDatabaseAdapter'; +import AuthorizationResultBasedEntityLoader from '../AuthorizationResultBasedEntityLoader'; +import EnforcingEntityLoader from '../EnforcingEntityLoader'; import EntityLoader from '../EntityLoader'; -import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy'; +import EntityLoaderUtils from '../EntityLoaderUtils'; import ViewerContext from '../ViewerContext'; -import { enforceResultsAsync } from '../entityUtils'; -import EntityNotFoundError from '../errors/EntityNotFoundError'; -import EntityDataManager from '../internal/EntityDataManager'; -import ReadThroughEntityCache from '../internal/ReadThroughEntityCache'; -import IEntityMetricsAdapter from '../metrics/IEntityMetricsAdapter'; -import TestEntity, { - TestFields, - TestEntityPrivacyPolicy, - testEntityConfiguration, -} from '../testfixtures/TestEntity'; -import { NoCacheStubCacheAdapterProvider } from '../utils/testing/StubCacheAdapter'; -import StubDatabaseAdapter from '../utils/testing/StubDatabaseAdapter'; -import StubQueryContextProvider from '../utils/testing/StubQueryContextProvider'; +import SimpleTestEntity from '../testfixtures/SimpleTestEntity'; +import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider'; describe(EntityLoader, () => { - it('loads entities', async () => { - const dateToInsert = new Date(); - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), + describe('enforcing', () => { + it('creates a new EnforcingEntityLoader', async () => { + const companionProvider = createUnitTestEntityCompanionProvider(); + const viewerContext = new ViewerContext(companionProvider); + expect(SimpleTestEntity.loader(viewerContext).enforcing()).toBeInstanceOf( + EnforcingEntityLoader, ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - - const id1 = uuidv4(); - const id2 = uuidv4(); - const databaseAdapter = new StubDatabaseAdapter( - testEntityConfiguration, - StubDatabaseAdapter.convertFieldObjectsToDataStore( - testEntityConfiguration, - new Map([ - [ - testEntityConfiguration.tableName, - [ - { - customIdField: id1, - testIndexedField: 'h1', - intField: 5, - stringField: 'huh', - dateField: dateToInsert, - nullableField: null, - }, - { - customIdField: id2, - testIndexedField: 'h2', - intField: 3, - stringField: 'huh', - dateField: dateToInsert, - nullableField: null, - }, - ], - ], - ]), - ), - ); - const privacyPolicy = new TestEntityPrivacyPolicy(); - const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); - const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); - const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); - const dataManager = new EntityDataManager( - databaseAdapter, - entityCache, - StubQueryContextProvider, - instance(mock()), - TestEntity.name, - ); - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManager, - metricsAdapter, - ); - const entity = await enforceAsyncResult( - entityLoader.withAuthorizationResults().loadByIDAsync(id1), - ); - expect(entity.getID()).toEqual(id1); - expect(entity.getField('dateField')).toEqual(dateToInsert); - - const entities = await enforceResultsAsync( - entityLoader.withAuthorizationResults().loadManyByFieldEqualingAsync('stringField', 'huh'), - ); - expect(entities.map((m) => m.getID())).toEqual([id1, id2]); - - const entityResultNumber3 = await entityLoader - .withAuthorizationResults() - .loadByFieldEqualingAsync('intField', 3); - expect(entityResultNumber3).not.toBeNull(); - expect(entityResultNumber3!.enforceValue().getID()).toEqual(id2); - - const entityResultNumber4 = await entityLoader - .withAuthorizationResults() - .loadByFieldEqualingAsync('intField', 4); - expect(entityResultNumber4).toBeNull(); - - const entityResultDuplicateValues = await entityLoader - .enforcing() - .loadManyByFieldEqualingManyAsync('stringField', ['huh', 'huh']); - expect(entityResultDuplicateValues.size).toBe(1); - expect(entityResultDuplicateValues.get('huh')?.map((m) => m.getID())).toEqual([id1, id2]); - - await expect( - entityLoader.withAuthorizationResults().loadByFieldEqualingAsync('stringField', 'huh'), - ).rejects.toThrowError( - 'loadByFieldEqualing: Multiple entities of type TestEntity found for stringField=huh', - ); - - await expect( - entityLoader.withAuthorizationResults().loadByIDNullableAsync(uuidv4()), - ).resolves.toBeNull(); - await expect( - entityLoader.withAuthorizationResults().loadByIDNullableAsync(id1), - ).resolves.not.toBeNull(); - - const nonExistentId = uuidv4(); - const manyIdResults = await entityLoader - .withAuthorizationResults() - .loadManyByIDsNullableAsync([nonExistentId, id1]); - expect(manyIdResults.get(nonExistentId)).toBeNull(); - expect(manyIdResults.get(id1)).not.toBeNull(); - - await expect( - enforceAsyncResult(entityLoader.withAuthorizationResults().loadByIDAsync(nonExistentId)), - ).rejects.toThrow(EntityNotFoundError); - - await expect( - entityLoader.withAuthorizationResults().loadByIDAsync('not-a-uuid'), - ).rejects.toThrowError('Entity field not valid: TestEntity (customIdField = not-a-uuid)'); + }); }); - it('loads entities with loadManyByFieldEqualityConjunction', async () => { - const privacyPolicy = new TestEntityPrivacyPolicy(); - const spiedPrivacyPolicy = spy(privacyPolicy); - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), + describe('withAuthorizationResults', () => { + it('creates a new AuthorizationResultBasedEntityLoader', async () => { + const companionProvider = createUnitTestEntityCompanionProvider(); + const viewerContext = new ViewerContext(companionProvider); + expect(SimpleTestEntity.loader(viewerContext).withAuthorizationResults()).toBeInstanceOf( + AuthorizationResultBasedEntityLoader, ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - - const id1 = uuidv4(); - const id2 = uuidv4(); - const id3 = uuidv4(); - const databaseAdapter = new StubDatabaseAdapter( - testEntityConfiguration, - StubDatabaseAdapter.convertFieldObjectsToDataStore( - testEntityConfiguration, - new Map([ - [ - testEntityConfiguration.tableName, - [ - { - customIdField: id1, - stringField: 'huh', - intField: 4, - testIndexedField: '4', - dateField: new Date(), - nullableField: null, - }, - { - customIdField: id2, - stringField: 'huh', - intField: 4, - testIndexedField: '5', - dateField: new Date(), - nullableField: null, - }, - { - customIdField: id3, - stringField: 'huh2', - intField: 4, - testIndexedField: '6', - dateField: new Date(), - nullableField: null, - }, - ], - ], - ]), - ), - ); - const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); - const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); - const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); - const dataManager = new EntityDataManager( - databaseAdapter, - entityCache, - StubQueryContextProvider, - instance(mock()), - TestEntity.name, - ); - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManager, - metricsAdapter, - ); - const entities = await enforceResultsAsync( - entityLoader.withAuthorizationResults().loadManyByFieldEqualityConjunctionAsync([ - { - fieldName: 'stringField', - fieldValue: 'huh', - }, - { - fieldName: 'intField', - fieldValues: [4], - }, - ]), - ); - expect(entities).toHaveLength(2); - verify( - spiedPrivacyPolicy.authorizeReadAsync( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - anyOfClass(TestEntity), - anything(), - ), - ).twice(); - - await expect( - entityLoader - .withAuthorizationResults() - .loadManyByFieldEqualityConjunctionAsync([ - { fieldName: 'customIdField', fieldValue: 'not-a-uuid' }, - ]), - ).rejects.toThrowError('Entity field not valid: TestEntity (customIdField = not-a-uuid)'); + }); }); - it('loads entities with loadFirstByFieldEqualityConjunction', async () => { - const privacyPolicy = new TestEntityPrivacyPolicy(); - const spiedPrivacyPolicy = spy(privacyPolicy); - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - - const id1 = uuidv4(); - const id2 = uuidv4(); - const id3 = uuidv4(); - const databaseAdapter = new StubDatabaseAdapter( - testEntityConfiguration, - StubDatabaseAdapter.convertFieldObjectsToDataStore( - testEntityConfiguration, - new Map([ - [ - testEntityConfiguration.tableName, - [ - { - customIdField: id1, - stringField: 'huh', - intField: 4, - testIndexedField: '4', - dateField: new Date(), - nullableField: null, - }, - { - customIdField: id2, - stringField: 'huh', - intField: 4, - testIndexedField: '5', - dateField: new Date(), - nullableField: null, - }, - { - customIdField: id3, - stringField: 'huh2', - intField: 4, - testIndexedField: '6', - dateField: new Date(), - nullableField: null, - }, - ], - ], - ]), - ), - ); - const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); - const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); - const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); - const dataManager = new EntityDataManager( - databaseAdapter, - entityCache, - StubQueryContextProvider, - instance(mock()), - TestEntity.name, - ); - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManager, - metricsAdapter, - ); - const result = await entityLoader - .withAuthorizationResults() - .loadFirstByFieldEqualityConjunctionAsync( - [ - { - fieldName: 'stringField', - fieldValue: 'huh', - }, - { - fieldName: 'intField', - fieldValue: 4, - }, - ], - { orderBy: [{ fieldName: 'testIndexedField', order: OrderByOrdering.DESCENDING }] }, - ); - expect(result).not.toBeNull(); - expect(result!.ok).toBe(true); - expect(result!.enforceValue().getField('testIndexedField')).toEqual('5'); - verify( - spiedPrivacyPolicy.authorizeReadAsync( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - anyOfClass(TestEntity), - anything(), - ), - ).once(); - }); - - it('loads entities with loadManyByRawWhereClauseAsync', async () => { - const privacyPolicy = new TestEntityPrivacyPolicy(); - const spiedPrivacyPolicy = spy(privacyPolicy); - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - - const dataManagerMock = mock>(EntityDataManager); - when( - dataManagerMock.loadManyByRawWhereClauseAsync( - queryContext, - anything(), - anything(), - anything(), - ), - ).thenResolve([ - { - customIdField: 'id', - stringField: 'huh', - intField: 4, - testIndexedField: '4', - dateField: new Date(), - nullableField: null, - }, - ]); - const dataManager = instance(dataManagerMock); - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManager, - metricsAdapter, - ); - const result = await entityLoader - .withAuthorizationResults() - .loadManyByRawWhereClauseAsync('id = ?', [1], { - orderBy: [{ fieldName: 'testIndexedField', order: OrderByOrdering.DESCENDING }], - }); - expect(result).toHaveLength(1); - expect(result[0]).not.toBeNull(); - expect(result[0]!.ok).toBe(true); - expect(result[0]!.enforceValue().getField('testIndexedField')).toEqual('4'); - verify( - spiedPrivacyPolicy.authorizeReadAsync( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - anyOfClass(TestEntity), - anything(), - ), - ).once(); - }); - - it('authorizes loaded entities', async () => { - const privacyPolicy = new TestEntityPrivacyPolicy(); - const spiedPrivacyPolicy = spy(privacyPolicy); - - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - - const id1 = uuidv4(); - const databaseAdapter = new StubDatabaseAdapter( - testEntityConfiguration, - StubDatabaseAdapter.convertFieldObjectsToDataStore( - testEntityConfiguration, - new Map([ - [ - testEntityConfiguration.tableName, - [ - { - customIdField: id1, - stringField: 'huh', - testIndexedField: '1', - intField: 3, - dateField: new Date(), - nullableField: null, - }, - ], - ], - ]), - ), - ); - const cacheAdapterProvider = new NoCacheStubCacheAdapterProvider(); - const cacheAdapter = cacheAdapterProvider.getCacheAdapter(testEntityConfiguration); - const entityCache = new ReadThroughEntityCache(testEntityConfiguration, cacheAdapter); - const dataManager = new EntityDataManager( - databaseAdapter, - entityCache, - StubQueryContextProvider, - instance(mock()), - TestEntity.name, - ); - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManager, - metricsAdapter, - ); - const entity = await enforceAsyncResult( - entityLoader.withAuthorizationResults().loadByIDAsync(id1), - ); - verify( - spiedPrivacyPolicy.authorizeReadAsync( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - entity, - anything(), - ), - ).once(); - }); - - it('invalidates upon invalidate one', async () => { - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); - const dataManagerMock = mock>(); - const dataManagerInstance = instance(dataManagerMock); - - const id1 = uuidv4(); - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManagerInstance, - metricsAdapter, - ); - await entityLoader.utils().invalidateFieldsAsync({ customIdField: id1 } as any); - - verify( - dataManagerMock.invalidateObjectFieldsAsync(deepEqual({ customIdField: id1 } as any)), - ).once(); - }); - - it('invalidates upon invalidate by field', async () => { - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); - const dataManagerMock = mock>(); - const dataManagerInstance = instance(dataManagerMock); - - const id1 = uuidv4(); - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManagerInstance, - metricsAdapter, - ); - await entityLoader.utils().invalidateFieldsAsync({ customIdField: id1 } as any); - verify( - dataManagerMock.invalidateObjectFieldsAsync(deepEqual({ customIdField: id1 } as any)), - ).once(); - }); - - it('invalidates upon invalidate by entity', async () => { - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); - const dataManagerMock = mock>(); - const dataManagerInstance = instance(dataManagerMock); - - const id1 = uuidv4(); - const entityMock = mock(TestEntity); - when(entityMock.getAllDatabaseFields()).thenReturn({ customIdField: id1 } as any); - const entityInstance = instance(entityMock); - - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManagerInstance, - metricsAdapter, - ); - await entityLoader.utils().invalidateEntityAsync(entityInstance); - verify( - dataManagerMock.invalidateObjectFieldsAsync(deepEqual({ customIdField: id1 } as any)), - ).once(); - }); - - it('returns error result when not allowed', async () => { - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - const privacyPolicyMock = mock(TestEntityPrivacyPolicy); - const dataManagerMock = mock>(); - - const id1 = uuidv4(); - when( - dataManagerMock.loadManyByFieldEqualingAsync(anything(), anything(), anything()), - ).thenResolve(new Map().set(id1, [{ customIdField: id1 }])); - - const rejectionError = new Error(); - - when( - privacyPolicyMock.authorizeReadAsync( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - anyOfClass(TestEntity), - anything(), - ), - ).thenReject(rejectionError); - - const privacyPolicy = instance(privacyPolicyMock); - const dataManagerInstance = instance(dataManagerMock); - - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManagerInstance, - metricsAdapter, - ); - - const entityResult = await entityLoader.withAuthorizationResults().loadByIDAsync(id1); - expect(entityResult.ok).toBe(false); - expect(entityResult.reason).toEqual(rejectionError); - expect(entityResult.value).toBe(undefined); - }); - - it('throws upon database adapter error', async () => { - const viewerContext = instance(mock(ViewerContext)); - const privacyPolicyEvaluationContext = - instance( - mock>(), - ); - const metricsAdapter = instance(mock()); - const queryContext = StubQueryContextProvider.getQueryContext(); - const privacyPolicy = instance(mock(TestEntityPrivacyPolicy)); - const dataManagerMock = mock>(); - - const error = new Error(); - - when( - dataManagerMock.loadManyByFieldEqualingAsync(anything(), anything(), anything()), - ).thenReject(error); - - const dataManagerInstance = instance(dataManagerMock); - - const entityLoader = new EntityLoader( - viewerContext, - queryContext, - privacyPolicyEvaluationContext, - testEntityConfiguration, - TestEntity, - /* entitySelectedFields */ undefined, - privacyPolicy, - dataManagerInstance, - metricsAdapter, - ); - - const loadByValue = uuidv4(); - - await expect( - entityLoader.withAuthorizationResults().loadByIDAsync(loadByValue), - ).rejects.toEqual(error); - await expect(entityLoader.enforcing().loadByIDAsync(loadByValue)).rejects.toEqual(error); - await expect( - entityLoader.withAuthorizationResults().loadManyByIDsAsync([loadByValue]), - ).rejects.toEqual(error); - await expect(entityLoader.enforcing().loadManyByIDsAsync([loadByValue])).rejects.toEqual(error); - await expect( - entityLoader.withAuthorizationResults().loadManyByIDsNullableAsync([loadByValue]), - ).rejects.toEqual(error); - await expect( - entityLoader.enforcing().loadManyByIDsNullableAsync([loadByValue]), - ).rejects.toEqual(error); - await expect( - entityLoader - .withAuthorizationResults() - .loadManyByFieldEqualingAsync('customIdField', loadByValue), - ).rejects.toEqual(error); - await expect( - entityLoader.enforcing().loadManyByFieldEqualingAsync('customIdField', loadByValue), - ).rejects.toEqual(error); - await expect( - entityLoader - .withAuthorizationResults() - .loadManyByFieldEqualingManyAsync('customIdField', [loadByValue]), - ).rejects.toEqual(error); - await expect( - entityLoader.enforcing().loadManyByFieldEqualingManyAsync('customIdField', [loadByValue]), - ).rejects.toEqual(error); + describe('utils', () => { + it('returns a instance of EntityLoaderUtils', async () => { + const companionProvider = createUnitTestEntityCompanionProvider(); + const viewerContext = new ViewerContext(companionProvider); + expect(SimpleTestEntity.loader(viewerContext).utils()).toBeInstanceOf(EntityLoaderUtils); + }); }); }); diff --git a/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts b/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts index 81e3508b1..e9b60f761 100644 --- a/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +++ b/packages/entity/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts @@ -4,6 +4,7 @@ import EntityConfiguration from '../EntityConfiguration'; import { UUIDField } from '../EntityFields'; import { EntityMutationType, EntityTriggerMutationInfo } from '../EntityMutationInfo'; import { EntityNonTransactionalMutationTrigger } from '../EntityMutationTriggerConfiguration'; +import EntityMutatorFactory from '../EntityMutatorFactory'; import EntityPrivacyPolicy from '../EntityPrivacyPolicy'; import ViewerContext from '../ViewerContext'; import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule'; @@ -87,7 +88,7 @@ class TestNonTransactionalMutationTrigger extends EntityNonTransactionalMutation } } -describe('EntityMutator', () => { +describe(EntityMutatorFactory, () => { test('cache consistency with post-commit callbacks', async () => { const companionProvider = createUnitTestEntityCompanionProvider(); const viewerContext = new ViewerContext(companionProvider); diff --git a/packages/entity/src/__tests__/EntityMutator-test.ts b/packages/entity/src/__tests__/EntityMutator-test.ts index 5f8d23aad..bdba7ea64 100644 --- a/packages/entity/src/__tests__/EntityMutator-test.ts +++ b/packages/entity/src/__tests__/EntityMutator-test.ts @@ -12,10 +12,10 @@ import { } from 'ts-mockito'; import { v4 as uuidv4 } from 'uuid'; +import AuthorizationResultBasedEntityLoader from '../AuthorizationResultBasedEntityLoader'; import EntityCompanionProvider from '../EntityCompanionProvider'; import EntityConfiguration from '../EntityConfiguration'; import EntityDatabaseAdapter from '../EntityDatabaseAdapter'; -import EntityLoader from '../EntityLoader'; import EntityLoaderFactory from '../EntityLoaderFactory'; import EntityLoaderUtils from '../EntityLoaderUtils'; import { @@ -577,7 +577,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id2), ); @@ -595,7 +594,6 @@ describe(EntityMutatorFactory, () => { const reloadedEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id2), ); expect(reloadedEntity.getAllFields()).toMatchObject(updatedEntity.getAllFields()); @@ -632,7 +630,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, { previousValue: null, cascadingDeleteCause: null }) - .withAuthorizationResults() .loadByIDAsync(id2), ); @@ -707,7 +704,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id2), ); @@ -780,7 +776,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id2), ); @@ -829,7 +824,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id1), ); @@ -845,7 +839,6 @@ describe(EntityMutatorFactory, () => { const reloadedEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id1), ); expect(reloadedEntity.getAllFields()).toMatchObject(existingEntity.getAllFields()); @@ -884,7 +877,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id1), ); expect(existingEntity).toBeTruthy(); @@ -897,7 +889,6 @@ describe(EntityMutatorFactory, () => { enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id1), ), ).rejects.toBeInstanceOf(Error); @@ -937,7 +928,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id1), ); @@ -990,7 +980,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id1), ); @@ -1047,7 +1036,6 @@ describe(EntityMutatorFactory, () => { const existingEntity = await enforceAsyncResult( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadByIDAsync(id1), ); @@ -1092,7 +1080,6 @@ describe(EntityMutatorFactory, () => { const entites1 = await enforceResultsAsync( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadManyByFieldEqualingAsync('stringField', 'huh'), ); expect(entites1).toHaveLength(1); @@ -1107,7 +1094,6 @@ describe(EntityMutatorFactory, () => { const entities2 = await enforceResultsAsync( entityLoaderFactory .forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext) - .withAuthorizationResults() .loadManyByFieldEqualingAsync('stringField', 'huh'), ); expect(entities2).toHaveLength(2); @@ -1170,17 +1156,16 @@ describe(EntityMutatorFactory, () => { }, }); - const entityLoaderMock = - mock< - EntityLoader< - SimpleTestFields, - string, - ViewerContext, - SimpleTestEntity, - SimpleTestEntityPrivacyPolicy, - keyof SimpleTestFields - > - >(EntityLoader); + const entityLoaderMock = mock< + AuthorizationResultBasedEntityLoader< + SimpleTestFields, + string, + ViewerContext, + SimpleTestEntity, + SimpleTestEntityPrivacyPolicy, + keyof SimpleTestFields + > + >(AuthorizationResultBasedEntityLoader); const entityLoaderUtilsMock = mock< EntityLoaderUtils< @@ -1193,7 +1178,7 @@ describe(EntityMutatorFactory, () => { > >(EntityLoaderUtils); when(entityLoaderUtilsMock.constructEntity(anything())).thenReturn(fakeEntity); - when(entityLoaderMock.utils()).thenReturn(instance(entityLoaderUtilsMock)); + when(entityLoaderMock.utils).thenReturn(instance(entityLoaderUtilsMock)); const entityLoader = instance(entityLoaderMock); const entityLoaderFactoryMock = @@ -1306,17 +1291,16 @@ describe(EntityMutatorFactory, () => { }, }); - const entityLoaderMock = - mock< - EntityLoader< - SimpleTestFields, - string, - ViewerContext, - SimpleTestEntity, - SimpleTestEntityPrivacyPolicy, - keyof SimpleTestFields - > - >(EntityLoader); + const entityLoaderMock = mock< + AuthorizationResultBasedEntityLoader< + SimpleTestFields, + string, + ViewerContext, + SimpleTestEntity, + SimpleTestEntityPrivacyPolicy, + keyof SimpleTestFields + > + >(AuthorizationResultBasedEntityLoader); const entityLoaderUtilsMock = mock< EntityLoaderUtils< @@ -1329,7 +1313,7 @@ describe(EntityMutatorFactory, () => { > >(EntityLoaderUtils); when(entityLoaderUtilsMock.constructEntity(anything())).thenReturn(fakeEntity); - when(entityLoaderMock.utils()).thenReturn(instance(entityLoaderUtilsMock)); + when(entityLoaderMock.utils).thenReturn(instance(entityLoaderUtilsMock)); const entityLoader = instance(entityLoaderMock); const entityLoaderFactoryMock = diff --git a/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts b/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts index c60f03497..893d98ac0 100644 --- a/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts +++ b/packages/entity/src/__tests__/EntitySecondaryCacheLoader-test.ts @@ -44,7 +44,7 @@ describe(EntitySecondaryCacheLoader, () => { const secondaryCacheLoader = new TestSecondaryRedisCacheLoader( secondaryEntityCache, - SimpleTestEntity.loader(vc1), + SimpleTestEntity.loader(vc1).withAuthorizationResults(), ); await secondaryCacheLoader.loadManyAsync([loadParams]); @@ -67,8 +67,8 @@ describe(EntitySecondaryCacheLoader, () => { ).thenResolve(new Map([[loadParams, createdEntity.getAllFields()]])); const secondaryEntityCache = instance(secondaryEntityCacheMock); - const loader = SimpleTestEntity.loader(vc1); - const spiedPrivacyPolicy = spy(loader['privacyPolicy']); + const loader = SimpleTestEntity.loader(vc1).withAuthorizationResults(); + const spiedPrivacyPolicy = spy(loader.utils['privacyPolicy']); const secondaryCacheLoader = new TestSecondaryRedisCacheLoader(secondaryEntityCache, loader); const result = await secondaryCacheLoader.loadManyAsync([loadParams]); @@ -96,7 +96,7 @@ describe(EntitySecondaryCacheLoader, () => { const secondaryEntityCacheMock = mock>(); const secondaryEntityCache = instance(secondaryEntityCacheMock); - const loader = SimpleTestEntity.loader(vc1); + const loader = SimpleTestEntity.loader(vc1).withAuthorizationResults(); const secondaryCacheLoader = new TestSecondaryRedisCacheLoader(secondaryEntityCache, loader); await secondaryCacheLoader.invalidateManyAsync([loadParams]); diff --git a/packages/entity/src/__tests__/ReadonlyEntity-test.ts b/packages/entity/src/__tests__/ReadonlyEntity-test.ts index bcfa16156..c80f9473c 100644 --- a/packages/entity/src/__tests__/ReadonlyEntity-test.ts +++ b/packages/entity/src/__tests__/ReadonlyEntity-test.ts @@ -127,7 +127,7 @@ describe(ReadonlyEntity, () => { }).toThrow(); }); - it('returns correct viewerCo}ntext from instantiation', () => { + it('returns correct viewerContext from instantiation', () => { const viewerContext = instance(mock(ViewerContext)); const data = { id: 'what', diff --git a/packages/entity/src/utils/EntityPrivacyUtils.ts b/packages/entity/src/utils/EntityPrivacyUtils.ts index f3f0f3d57..1cf4b8b3d 100644 --- a/packages/entity/src/utils/EntityPrivacyUtils.ts +++ b/packages/entity/src/utils/EntityPrivacyUtils.ts @@ -330,9 +330,8 @@ async function canViewerDeleteInternalAsync< edgeDeletionPermissionInferenceBehavior === EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL ) { - const singleEntityToTestForInboundEdge = await loader - .withAuthorizationResults() - .loadFirstByFieldEqualityConjunctionAsync( + const singleEntityResultToTestForInboundEdge = + await loader.loadFirstByFieldEqualityConjunctionAsync( [ { fieldName, @@ -343,18 +342,16 @@ async function canViewerDeleteInternalAsync< ], { orderBy: [] }, ); - entityResultsToCheckForInboundEdge = singleEntityToTestForInboundEdge - ? [singleEntityToTestForInboundEdge] + entityResultsToCheckForInboundEdge = singleEntityResultToTestForInboundEdge + ? [singleEntityResultToTestForInboundEdge] : []; } else { - const entityResultsForInboundEdge = await loader - .withAuthorizationResults() - .loadManyByFieldEqualingAsync( - fieldName, - association.associatedEntityLookupByField - ? sourceEntity.getField(association.associatedEntityLookupByField as any) - : sourceEntity.getID(), - ); + const entityResultsForInboundEdge = await loader.loadManyByFieldEqualingAsync( + fieldName, + association.associatedEntityLookupByField + ? sourceEntity.getField(association.associatedEntityLookupByField as any) + : sourceEntity.getID(), + ); entityResultsToCheckForInboundEdge = entityResultsForInboundEdge; }