UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

1,061 lines (968 loc) 24.6 kB
import type { Services } from '../typings'; import setup from '../../test/setup'; import { init } from '.'; describe('models', () => { let services: Services; const DEFAULT_MODEL = { is_enabled: true, db: 'datastore', retry_duration: 0, indexes: [], schema: { model: { properties: { is_readonly: { type: 'boolean', }, }, }, }, }; beforeAll(async () => { const app = await setup.build(); services = app.services; }); afterEach(async () => { jest.restoreAllMocks(); }); afterAll(async () => { await setup.teardownDb(services.mongodb); }); describe('#getGraph', () => { it('returns default graph', () => { const models = init( { models: [], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [], edges: [], }); }); it('keeps graph in instance cache', () => { const models = init( { models: [], }, services, ); const graph = models.getGraph(); expect(models.GRAPH).not.toEqual(null); expect(graph).toEqual(models.GRAPH); }); it('returns default graph from instance cache', () => { const models = init( { models: [], }, services, ); models.getGraph(); const graph = models.getGraph(); expect(graph).toEqual(models.GRAPH); }); it('returns graph with custom properties', () => { const models = init( { models: [], }, services, ); const graph = models.getGraph({ fields: { edges: 'links', }, }); expect(graph).toEqual({ nodes: [], links: [], }); }); it('returns one node per active model', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, ], edges: [], }); }); it('filters links on `_id` without active model', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: { model: { properties: { account_id: { type: 'string', }, }, }, }, }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, ], edges: [], }); }); it('filters auto links on `_id`', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: { model: { properties: { user_id: { type: 'string', }, }, }, }, }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, ], edges: [], }); }); it('returns zero edge if no property is defined', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: {}, }, { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', schema: {}, }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'accounts', }, ], edges: [], }); }); it('returns one edge on fields ending with `_id`', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: { model: { properties: { account_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'accounts', }, ], edges: [ { source: 'users', target: 'accounts', key: 'account_id', value: 1, correlation_field: 'account_id', }, ], }); }); it('returns one edge on fields ending with `_id` but with an `s`', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: { model: { properties: { flexes_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'flexes', correlation_field: 'flex_id', }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'flexes', }, ], edges: [ { source: 'users', target: 'flexes', key: 'flexes_id', value: 1, correlation_field: 'flex_id', }, ], }); }); it('returns one edge on fields listed in links', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', links: { my_account_id: 'accounts', }, schema: { model: { properties: { my_account_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'accounts', }, ], edges: [ { source: 'users', target: 'accounts', key: 'my_account_id', value: 1, correlation_field: 'account_id', }, ], }); }); it('filters edges on unknown models', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', links: { my_account_id: 'unknown', }, schema: { model: { properties: { my_account_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'accounts', }, ], edges: [], }); }); it('returns one edge on fields listed in links via external enum', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', links: { entity_id: 'entity_type', }, schema: { model: { properties: { entity_type: { type: 'string', enum: ['accounts', 'devices'], }, entity_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', }, { ...DEFAULT_MODEL, name: 'devices', correlation_field: 'device_id', }, ], }, services, ); const graph = models.getGraph({ mustDiscover: false, }); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'accounts', }, { group: 2, id: 'devices', }, ], edges: [ { source: 'users', target: 'accounts', key: 'entity_id', value: 1, correlation_field: 'account_id', }, { source: 'users', target: 'devices', key: 'entity_id', value: 1, correlation_field: 'device_id', }, ], }); }); it('returns zero edge on fields listed in links via external enum but without enum defined', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', links: { entity_id: 'entity_type', }, schema: { model: { properties: { entity_type: { type: 'string', }, entity_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', }, { ...DEFAULT_MODEL, name: 'devices', correlation_field: 'device_id', }, ], }, services, ); const graph = models.getGraph({ mustDiscover: false, }); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'accounts', }, { group: 2, id: 'devices', }, ], edges: [], }); }); it('returns one edge based on correlation discovery', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: { model: { properties: { entity_type: { type: 'string', enum: ['accounts', 'devices'], }, entity_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', }, { ...DEFAULT_MODEL, name: 'devices', correlation_field: 'device_id', }, ], }, services, ); const graph = models.getGraph(); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'accounts', }, { group: 2, id: 'devices', }, ], edges: [ { source: 'users', target: 'accounts', key: 'entity_id', value: 1, correlation_field: 'account_id', }, { source: 'users', target: 'devices', key: 'entity_id', value: 1, correlation_field: 'device_id', }, ], }); }); it('does not add edge on not found node', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: { model: { properties: { entity_type: { type: 'string', enum: ['accounts', 'devices'], }, entity_id: { type: 'string', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'devices', correlation_field: 'device_id', }, ], }, services, ); const graph = models.getGraph({ mustDiscover: true, }); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, { group: 1, id: 'devices', }, ], edges: [ { source: 'users', target: 'devices', key: 'entity_id', value: 1, correlation_field: 'device_id', }, ], }); }); it('does not add edge on models without constraint on enum', () => { const models = init( { models: [ { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', schema: { model: { properties: { entity_type: { type: 'string', }, entity_id: { type: 'string', }, }, }, }, }, ], }, services, ); const graph = models.getGraph({ mustDiscover: true, }); expect(graph).toEqual({ nodes: [ { group: 0, id: 'users', }, ], edges: [], }); }); }); describe('#setGraph', () => { it('sets the instance graph', () => { const models = init( { models: [], }, services, ); models.getGraph(); models.setGraph(null); expect(models.GRAPH).toEqual(null); }); }); describe('#getEntitiesFromGraph', () => { let models; beforeEach(() => { models = init( { models: [ { ...DEFAULT_MODEL, name: 'accounts', correlation_field: 'account_id', }, { ...DEFAULT_MODEL, name: 'users', correlation_field: 'user_id', links: { account_id: 'accounts', }, schema: { model: { properties: { firstname: { type: 'string', }, account_id: { type: 'string', }, is_readonly: { type: 'boolean', }, }, }, }, }, { ...DEFAULT_MODEL, name: 'devices', correlation_field: 'device_id', links: { entity_id: 'entity_type', owner_id: 'accounts', }, schema: { model: { properties: { name: { type: 'string', }, entity_type: { type: 'string', enum: ['accounts', 'users'], }, entity_id: { type: 'string', }, owner_id: { type: 'string', }, is_readonly: { type: 'boolean', }, }, }, }, }, ], }, services, ); services.models = models; }); it('returns only children entities', async () => { const account = await models.factory('accounts').create({}); const user = await models.factory('users').create({ firstname: 'John', account_id: account.state.account_id, }); const entities = await models.getEntitiesFromGraph('users', { user_id: user.state.user_id, }); expect(Array.from(entities.values())).toEqual([user.state]); }); it('returns the all children entities including nested levels', async () => { const account = await models.factory('accounts').create({}); const user = await models .factory('users') .create({ firstname: 'John', account_id: account.state.account_id }); const userDevice = await models.factory('devices').create({ name: 'John device', entity_type: 'users', entity_id: user.state.user_id, }); const entities = await models.getEntitiesFromGraph('accounts', { account_id: account.state.account_id, }); expect(Array.from(entities.values())).toEqual([ account.state, user.state, userDevice.state, ]); }); it('returns only the correlation field if requested', async () => { const account = await models.factory('accounts').create({}); const user = await models .factory('users') .create({ firstname: 'John', account_id: account.state.account_id }); const userDevice = await models.factory('devices').create({ name: 'John device', entity_type: 'users', entity_id: user.state.user_id, }); const entities = await models.getEntitiesFromGraph( 'accounts', { account_id: account.state.account_id, }, { withCorrelationFieldOnly: true, }, ); expect(Array.from(entities.values())).toEqual([ { account_id: account.state.account_id }, { user_id: user.state.user_id }, { device_id: userDevice.state.device_id }, ]); }); it('returns required models', async () => { const account = await models.factory('accounts').create({}); const user = await models .factory('users') .create({ firstname: 'John', account_id: account.state.account_id }); const userDevice = await models.factory('devices').create({ name: 'John device', entity_type: 'users', entity_id: user.state.user_id, }); const entities = await models.getEntitiesFromGraph( 'accounts', { account_id: account.state.account_id, }, { models: ['accounts', 'users'], }, ); expect(Array.from(entities.values())).toEqual([ account.state, user.state, ]); }); it('stops walking the graph on cyclic dependencies', async () => { const account = await models.factory('accounts').create({}); const differentOwnerAccount = await models.factory('accounts').create({}); const user = await models .factory('users') .create({ firstname: 'John', account_id: account.state.account_id }); const userDevice = await models.factory('devices').create({ name: 'John device', entity_type: 'users', entity_id: user.state.user_id, owner_id: account.state.account_id, }); const userDevice2 = await models.factory('devices').create({ name: 'John device 2', entity_type: 'users', entity_id: user.state.user_id, owner_id: differentOwnerAccount.state.account_id, }); const entities = await models.getEntitiesFromGraph('accounts', { account_id: account.state.account_id, }); expect(Array.from(entities.values())).toEqual([ account.state, user.state, userDevice.state, userDevice2.state, ]); }); it('allows to perform an action on every visited entity', async () => { const account = await models.factory('accounts').create({}); const user = await models .factory('users') .create({ firstname: 'John', account_id: account.state.account_id }); const userDevice = await models.factory('devices').create({ name: 'John device', entity_type: 'users', entity_id: user.state.user_id, }); const handler = async (services, Model, entity) => { const isReadonlyProperty: string = Model.getIsReadonlyProperty(); const e = new Model(services, entity[Model.getCorrelationField()]); await e.update({ [isReadonlyProperty]: true, }); return e.state; }; const entities = await models.getEntitiesFromGraph( 'accounts', { account_id: account.state.account_id, }, { handler, }, ); expect(Array.from(entities.values())).toMatchObject([ { account_id: account.state.account_id, version: 1, is_readonly: true, }, { user_id: user.state.user_id, version: 1, is_readonly: true, }, { device_id: userDevice.state.device_id, version: 1, is_readonly: true, }, ]); // Check the effective application let error; try { await models.getEntitiesFromGraph( 'accounts', { account_id: account.state.account_id, }, { handler: async (services, Model, entity) => { const e = new Model( services, entity[Model.getCorrelationField()], ); await e.update({ test: 2, }); return e.state; }, }, ); } catch (err) { error = err; } expect(error.message).toEqual('Entity is readonly'); }); }); });