UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

964 lines (903 loc) 23.6 kB
import setup from '../../test/setup'; describe('sdk/Datastore/import', () => { let config; let mongodb; let models; let app; let sdk; let instance; beforeEach(async () => { [config, mongodb, models, app, , sdk, , instance] = await setup.startApi({ mode: 'development', features: { api: { admin: true } }, }); }); afterEach(async () => { await setup.stopApi(instance); jest.restoreAllMocks(); await setup.teardownDb(mongodb); }); describe('#import', () => { const properties = { firstname: { type: 'string', }, }; const modelConfigs = { users: { is_enabled: true, db: 'datastore', name: 'users', correlation_field: 'user_id', schema: { model: { type: 'object', additionalProperties: true, properties: properties, }, events: { CREATED: { '0_0_0': { additionalProperties: true, properties, }, }, UPDATED: { '0_0_0': { additionalProperties: true, properties, }, }, }, }, }, }; beforeEach(async () => { await sdk.createModel(modelConfigs.users); sdk.create = jest.spyOn(sdk, 'create'); sdk.update = jest.spyOn(sdk, 'update'); }); afterEach(() => { jest.restoreAllMocks(); }); it('imports a single entity', async () => { const entities = await sdk.import([ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ]); expect(sdk.create).toHaveBeenCalledTimes(1); expect(sdk.update).toHaveBeenCalledTimes(0); expect(entities.size).toEqual(1); expect(Array.from(entities.keys())).toEqual(['users/john']); }); it('performs noop if `dryRun=true` on entity creation', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ], {}, { dryRun: true, }, ); expect(sdk.create).toHaveBeenCalledTimes(0); expect(sdk.update).toHaveBeenCalledTimes(0); expect(entities.size).toEqual(1); expect(Array.from(entities.keys())).toEqual(['users/john']); }); it('updates a single entity during import if already exist', async () => { await sdk.import([ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ]); sdk.create.mockReset(); const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'jack', }, }, ], modelConfigs, ); expect(sdk.create).toHaveBeenCalledTimes(0); expect(sdk.update).toHaveBeenCalledTimes(1); expect(entities.size).toEqual(1); expect(Array.from(entities.keys())).toEqual(['users/john']); }); it('stores the diff on entity update', async () => { await sdk.import([ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ]); sdk.create.mockReset(); const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'jack', }, }, ], modelConfigs, ); expect(entities.get('users/john').__update__).toEqual([ { op: 'replace', path: '/firstname', value: 'jack', }, ]); expect(entities.get('users/john').__rollback__).toEqual([ { op: 'replace', path: '/firstname', value: 'john', }, ]); }); it('performs noop if `dryRun=true` on entity update', async () => { await sdk.import([ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ]); sdk.create.mockReset(); const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'jack', }, }, ], modelConfigs, { dryRun: true, }, ); expect(sdk.create).toHaveBeenCalledTimes(0); expect(sdk.update).toHaveBeenCalledTimes(0); expect(entities.size).toEqual(1); expect(Array.from(entities.keys())).toEqual(['users/john']); }); it('does nothing if no change has to be applied', async () => { await sdk.import([ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ]); sdk.create = jest.fn(); sdk.update = jest.spyOn(sdk, 'update'); const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ], modelConfigs, ); expect(sdk.create).toHaveBeenCalledTimes(0); expect(sdk.update).toHaveBeenCalledTimes(0); expect(entities.size).toEqual(1); expect(Array.from(entities.keys())).toEqual(['users/john']); }); it('updates a single entity omitting keys on update', async () => { await sdk.import([ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, ]); sdk.create = jest.fn(); const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', removed_on_update: true, }, omit_on_update: ['removed_on_update'], }, ], modelConfigs, ); expect(sdk.create).toHaveBeenCalledTimes(0); expect(entities.size).toEqual(1); expect(entities.get('users/john')).not.toHaveProperty( 'removed_on_update', ); }); it('updates an entity with idempotency defined by a link result', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { brother_id: null, }, links: [ { model: 'users', is_idempotency_condition: true, idempotency: { firstname: 'john', }, map: { brother_id: 'user_id', }, }, ], entity: { firstname: 'william', brother_id: null, }, }, ], modelConfigs, ); expect(entities.size).toEqual(2); expect(Array.from(entities.keys())).toEqual([ 'users/john', 'users/william', ]); expect(entities.get('users/william')).toMatchObject({ brother_id: entities.get('users/john').user_id, }); }); it('updates an entity with idempotency defined by a link result in a nested object', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/joe', idempotency: { firstname: 'joe', }, entity: { firstname: 'joe', }, }, { model: 'users', id: 'users/william', idempotency: { firstname: 'william', }, entity: { firstname: 'william', }, }, { model: 'users', id: 'users/jack', idempotency: { firstname: 'jack', }, entity: { firstname: 'jack', }, }, { model: 'users', id: 'users/averell', idempotency: { brother_id: null, }, links: [ { model: 'users', is_idempotency_condition: true, idempotency: { firstname: 'joe', }, map: { brothers: { brother_id: 'user_id', age: 25, }, }, }, { model: 'users', is_idempotency_condition: true, idempotency: { firstname: 'william', }, map: { brothers: { brother_id: 'user_id', age: 24, }, }, }, { model: 'users', is_idempotency_condition: true, idempotency: { firstname: 'jack', }, map: { brothers: { brother_id: 'user_id', age: 24, }, }, }, ], entity: { firstname: 'william', brothers: [], }, }, ], modelConfigs, ); expect(entities.size).toEqual(4); expect(Array.from(entities.keys())).toEqual([ 'users/joe', 'users/william', 'users/jack', 'users/averell', ]); expect(entities.get('users/averell')).toMatchObject({ brothers: [ { brother_id: entities.get('users/joe').user_id, age: 25 }, { brother_id: entities.get('users/william').user_id, age: 24 }, { brother_id: entities.get('users/jack').user_id, age: 24 }, ], }); }); it('updates an entity with idempotency defined by a link result and string template', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { brother_id: null, }, links: [ { model: 'users', is_idempotency_condition: true, idempotency: { firstname: 'john', }, map: { brother_id: 'user_id', }, }, { model: 'users', idempotency: { firstname: 'john', }, map: { firstname: 'Brother of ${firstname}', }, }, ], entity: { firstname: '__not_defined__', brother_id: null, }, }, ], modelConfigs, ); expect(entities.get('users/william')).toMatchObject({ firstname: 'Brother of john', }); }); it('updates an entity with idempotency defined by a link result and nested string template', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', location: { address: 'Rue de la Paix', }, }, }, { model: 'users', id: 'users/william', idempotency: { brother_id: null, }, links: [ { model: 'users', is_idempotency_condition: true, idempotency: { firstname: 'john', }, map: { brother_id: 'user_id', }, }, { model: 'users', idempotency: { firstname: 'john', }, map: { firstname: 'Brother of ${firstname} living in ${location.address}', }, }, ], entity: { firstname: '__not_defined__', brother_id: null, }, }, ], modelConfigs, ); expect(entities.get('users/william')).toMatchObject({ firstname: 'Brother of john living in Rue de la Paix', }); }); it('allows to import entities with relationships at the root level', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { firstname: 'william', }, links: [ { model: 'users', idempotency: { firstname: 'john', }, map: { brother_id: 'user_id', }, }, ], entity: { firstname: 'william', brother_id: null, }, }, ], modelConfigs, ); expect(entities.size).toEqual(2); expect(Array.from(entities.keys())).toEqual([ 'users/john', 'users/william', ]); expect(entities.get('users/william')).toMatchObject({ brother_id: entities.get('users/john').user_id, }); }); it('allows to import entities with relationships referencing them with their ids', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { firstname: 'william', }, links: [ { id: 'users/john', map: { brother_id: 'user_id', }, }, ], entity: { firstname: 'william', brother_id: null, }, }, ], modelConfigs, ); expect(entities.size).toEqual(2); expect(Array.from(entities.keys())).toEqual([ 'users/john', 'users/william', ]); expect(entities.get('users/william')).toMatchObject({ brother_id: entities.get('users/john').user_id, }); }); it('allows to import entities with relationships deeply', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { firstname: 'william', }, links: [ { model: 'users', idempotency: { firstname: 'john', }, map: { brother: { user_id: 'user_id', }, }, }, ], entity: { firstname: 'william', brother: { user_id: null, }, }, }, ], modelConfigs, ); expect(entities.size).toEqual(2); expect(Array.from(entities.keys())).toEqual([ 'users/john', 'users/william', ]); expect(entities.get('users/william')).toMatchObject({ brother: { user_id: entities.get('users/john').user_id, }, }); }); it('allows to import entities with relationships in arrays', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { firstname: 'william', }, links: [ { model: 'users', idempotency: { firstname: 'john', }, map: { brothers: [ { user_id: 'user_id', }, ], }, }, ], entity: { firstname: 'william', brothers: [], }, }, ], modelConfigs, ); expect(entities.size).toEqual(2); expect(Array.from(entities.keys())).toEqual([ 'users/john', 'users/william', ]); expect(entities.get('users/william')).toMatchObject({ brothers: [ { user_id: entities.get('users/john').user_id, }, ], }); }); it('allows to import entities by their ids with relationships in arrays', async () => { const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { firstname: 'william', }, links: [ { model: 'users', idempotency: { firstname: 'john', }, map: { brothers: 'user_id', }, }, ], entity: { firstname: 'william', brothers: [], }, }, ], modelConfigs, ); expect(entities.size).toEqual(2); expect(Array.from(entities.keys())).toEqual([ 'users/john', 'users/william', ]); expect(entities.get('users/william')).toMatchObject({ brothers: [entities.get('users/john').user_id], }); }); it('allows to upsert entities with correlation ID already defined', async () => { const userId = `user_id_${Date.now()}`; const entities = await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', user_id: userId, }, entity: { firstname: 'john', }, }, ], modelConfigs, ); expect(entities.size).toEqual(1); expect(Array.from(entities.keys())).toEqual(['users/john']); expect(entities.get('users/john')).toMatchObject({ firstname: 'john', user_id: userId, }); }); it('throws an exception if the idempotency condition does not lead to at most one unique result', async () => { let error; try { await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, entity: { firstname: 'john', }, }, { model: 'users', id: 'users/william', idempotency: { firstname: 'william', }, entity: { firstname: 'william', }, }, { model: 'users', id: 'users/invalid', idempotency: { // firstname: 'william', }, entity: { firstname: 'william', brothers: [], }, }, ], modelConfigs, ); } catch (err) { error = err; } expect(error).toBeInstanceOf(Error); expect(error.message).toEqual('Idempotency condition violation'); }); it('throws an idempotency exception if a link can not be resolved', async () => { let error; try { await sdk.import( [ { model: 'users', id: 'users/john', idempotency: { firstname: 'john', }, links: [ { model: 'users', idempotency: { firstname: 'john', }, map: { brothers: 'user_id', }, }, ], entity: { firstname: 'john', }, }, ], modelConfigs, ); } catch (err) { error = err; } expect(error).toBeInstanceOf(Error); expect(error.message).toEqual('[Link] Idempotency condition violation'); }); }); });