UNPKG

@furystack/repository

Version:

Repository implementation for FuryStack

549 lines (477 loc) 23 kB
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-return */ import type { WithOptionalId } from '@furystack/core' import { InMemoryStore, addStore } from '@furystack/core' import { Injector } from '@furystack/inject' import { usingAsync } from '@furystack/utils' import { describe, expect, it, vi } from 'vitest' import type { AuthorizationResult, DataSetSettings } from './data-set-setting.js' import { getDataSetFor, getRepository } from './helpers.js' import { Repository } from './repository.js' class TestClass { public id = 1 public value = '' } describe('DataSet', () => { describe('Construction', () => { it('can be retrieved from an extension method with class', async () => { await usingAsync(new Injector(), async (i) => { addStore( i, new InMemoryStore({ model: TestClass, primaryKey: 'id', }), ) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') expect(dataSet.settings.physicalStore.model).toBe(TestClass) }) }) it('can be retrieved from an extension method with string', async () => { await usingAsync(new Injector(), async (i) => { addStore( i, new InMemoryStore({ model: TestClass, primaryKey: 'id', }), ) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') expect(dataSet.settings.physicalStore.model).toBe(TestClass) }) }) it('Should throw if dataset is not registered through extension', async () => { await usingAsync(new Injector(), async (i) => { expect(() => getDataSetFor(i, TestClass, 'id')).toThrowError(`No DataSet found for 'class TestClass { id = 1; value = ""; }'`) }) }) it('Should throw if dataset is not registered through service', async () => { await usingAsync(new Injector(), async (i) => { expect(() => i.getInstance(Repository).getDataSetFor(TestClass, 'id')) .toThrowError(`No DataSet found for 'class TestClass { id = 1; value = ""; }'`) }) }) it('Should throw error on mismatched primary key', async () => { await usingAsync(new Injector(), async (i) => { addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', {}) expect(() => getDataSetFor(i, TestClass, 'name' as any)).toThrowError() }) }) }) describe('Authorizers', () => { describe('Add', () => { it('should add an entity if no settings are provided', async () => { await usingAsync(new Injector(), async (i) => { addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.get(i, 1) expect(result && result.value).toBe('asd') }) }) it('should call the add async authorizer and add the entity on pass', async () => { await usingAsync(new Injector(), async (i) => { const authorizeAdd = vi.fn(async () => ({ isAllowed: true }) as AuthorizationResult) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeAdd }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) expect(authorizeAdd).toBeCalled() const added = await dataSet.get(i, 1) expect(added && added.value).toBe('asd') }) }) it('should throw if the add authorizer returns a non-valid result and should not add a value to the store', async () => { await usingAsync(new Injector(), async (i) => { const authorizeAdd = vi.fn(async () => ({ isAllowed: false, message: '...' }) as AuthorizationResult) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeAdd }) const dataSet = getDataSetFor(i, TestClass, 'id') try { await dataSet.add(i, { id: 1, value: 'asd' }) throw Error('Should throw') } catch (error) { /** */ expect(authorizeAdd).toBeCalled() const added = await dataSet.get(i, 1) expect(added).toBeUndefined() } }) }) it('should modify an entity on add, if modifyOnAdd is provided', async () => { await usingAsync(new Injector(), async (i) => { const modifyOnAdd = vi.fn( async (options: { injector: Injector; entity: WithOptionalId<TestClass, 'id'> }) => ({ ...options.entity, value: options.entity.value.toUpperCase(), }), ) as any addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { modifyOnAdd }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.get(i, 1) expect(modifyOnAdd).toBeCalled() expect(result && result.value).toBe('ASD') }) }) it('should call the onEntityAdded callback if an entity has been added', async () => { await usingAsync(new Injector(), async (i) => { expect.assertions(1) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', {}) getRepository(i) .getDataSetFor(TestClass, 'id') .subscribe('onEntityAdded', ({ entity }) => { expect(entity.value).toBe('asd') }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) }) }) }) describe('Update', () => { it('should update an entity if no settings are provided', async () => { await usingAsync(new Injector(), async (i) => { addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.update(i, 1, { id: 1, value: 'asd2' }) const result = await dataSet.get(i, 1) expect(result && result.value).toBe('asd2') }) }) it('should call the authorizeUpdate authorizer and add the entity on pass', async () => { await usingAsync(new Injector(), async (i) => { const authorizeUpdate = vi.fn(async () => ({ isAllowed: true }) as AuthorizationResult) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdate }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.update(i, 1, { id: 1, value: 'asd2' }) expect(authorizeUpdate).toBeCalled() const added = await dataSet.get(i, 1) expect(added && added.value).toBe('asd2') }) }) it('should throw if the authorizeUpdateEntity returns a non-valid result and should not update a value to the store', async () => { await usingAsync(new Injector(), async (i) => { const authorizeUpdateEntity = vi.fn(async () => ({ isAllowed: false, message: '...' }) as AuthorizationResult) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdateEntity }) const dataSet = getDataSetFor(i, TestClass, 'id') try { await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.update(i, 1, { id: 1, value: 'asd2' }) throw Error('Should throw') } catch (error) { /** */ expect(authorizeUpdateEntity).toBeCalled() const added = await dataSet.get(i, 1) expect(added && added.value).toBe('asd') } }) }) it('should call the authorizeUpdateEntity authorizer and add the entity on pass', async () => { await usingAsync(new Injector(), async (i) => { const authorizeUpdateEntity = vi.fn(async () => ({ isAllowed: true }) as AuthorizationResult) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdateEntity }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.update(i, 1, { id: 1, value: 'asd2' }) expect(authorizeUpdateEntity).toBeCalled() const added = await dataSet.get(i, 1) expect(added && added.value).toBe('asd2') }) }) it('should throw if the authorizeUpdate returns a non-valid result and should not update a value to the store', async () => { await usingAsync(new Injector(), async (i) => { const authorizeUpdate = vi.fn(async () => ({ isAllowed: false, message: '...' }) as AuthorizationResult) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdate }) const dataSet = getDataSetFor(i, TestClass, 'id') try { await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.update(i, 1, { id: 1, value: 'asd2' }) throw Error('Should throw') } catch (error) { /** */ expect(authorizeUpdate).toBeCalled() const added = await dataSet.get(i, 1) expect(added && added.value).toBe('asd') } }) }) it('should modify an entity on update, if modifyOnAdd is provided', async () => { await usingAsync(new Injector(), async (i) => { const modifyOnUpdate: DataSetSettings<TestClass, 'id'>['modifyOnUpdate'] = vi.fn(async (options) => ({ ...options.entity, value: options.entity.value?.toUpperCase(), })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { modifyOnUpdate }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.update(i, 1, { id: 1, value: 'asd2' }) const result = await dataSet.get(i, 1) expect(modifyOnUpdate).toBeCalled() expect(result && result.value).toBe('ASD2') }) }) it('should publish to the onEntityUpdated observable if an entity has been updated', async () => { await usingAsync(new Injector(), async (i) => { expect.assertions(1) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') getRepository(i) .getDataSetFor(TestClass, 'id') .subscribe('onEntityUpdated', ({ change }) => { expect(change).toEqual({ id: 1, value: 'asd2' }) }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.update(i, 1, { id: 1, value: 'asd2' }) }) }) }) describe('Count', () => { it('should return the count if no settings are provided', async () => { await usingAsync(new Injector(), async (i) => { addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.count(i) expect(result).toBe(1) }) }) it('should return the count if authorizeGet returns valid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGet = vi.fn(async () => ({ isAllowed: true, message: '' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGet }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.count(i) expect(result).toBe(1) }) }) it('should throw if authorizeGet returns invalid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGet = vi.fn(async () => ({ isAllowed: false, message: ':(' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGet }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) try { await dataSet.count(i) throw Error('Should throw') } catch (error) { /** */ } }) }) }) }) describe('filter', () => { it('should return the unfiltered result if no settings are provided', async () => { await usingAsync(new Injector(), async (i) => { addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.find(i, {}) expect(result.length).toBe(1) }) }) it('should return the unfiltered result if authorizeGet returns valid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGet = vi.fn(async () => ({ isAllowed: true, message: '' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGet }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.find(i, {}) expect(result.length).toBe(1) }) }) it('should throw if authorizeGet returns invalid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGet = vi.fn(async () => ({ isAllowed: false, message: ':(' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGet }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) try { await dataSet.find(i, {}) throw Error('Should throw') } catch (error) { /** */ } }) }) }) describe('get', () => { it('should return the entity if no settings are provided', async () => { await usingAsync(new Injector(), async (i) => { addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.get(i, 1) expect(result && result.id).toBe(1) }) }) it('should return the entity if authorizeGet returns valid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGet = vi.fn(async () => ({ isAllowed: true, message: '' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGet }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.get(i, 1) expect(result && result.id).toBe(1) }) }) it('should throw if authorizeGet returns invalid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGet = vi.fn(async () => ({ isAllowed: false, message: ':(' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGet }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) try { await dataSet.get(i, 1) throw Error('Should throw') } catch (error) { /** */ } }) }) it('should return the entity if authorizeGetEntity returns valid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGetEntity = vi.fn(async () => ({ isAllowed: true, message: '' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGetEntity }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) const result = await dataSet.get(i, 1) expect(result && result.id).toBe(1) }) }) it('should throw if authorizeGetEntity returns invalid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeGetEntity = vi.fn(async () => ({ isAllowed: false, message: ':(' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeGetEntity }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) try { await dataSet.get(i, 1) throw Error('Should throw') } catch (error) { /** */ } }) }) }) describe('remove', () => { it('should remove the entity if no settings are provided', async () => { await usingAsync(new Injector(), async (i) => { addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.remove(i, 1) const countValue = await dataSet.count(i) expect(countValue).toBe(0) }) }) it('should remove the entity if authorizeRemove returns valid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeRemove = vi.fn(async () => ({ isAllowed: true, message: '' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeRemove }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.remove(i, 1) const count = await dataSet.count(i) expect(count).toBe(0) }) }) it('should throw if authorizeRemove returns invalid result', async () => { await usingAsync(new Injector(), async (i) => { const authorizeRemove = vi.fn(async () => ({ isAllowed: false, message: ':(' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authorizeRemove }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) try { await dataSet.remove(i, 1) throw Error('Should throw') } catch (error) { /** */ } const count = await dataSet.count(i) expect(count).toBe(1) }) }) it('should remove the entity if authroizeRemoveEntity returns valid result', async () => { await usingAsync(new Injector(), async (i) => { const authroizeRemoveEntity = vi.fn(async () => ({ isAllowed: true, message: '' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authroizeRemoveEntity }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.remove(i, 1) const count = await dataSet.count(i) expect(count).toBe(0) }) }) it('should throw if authroizeRemoveEntity returns invalid result', async () => { await usingAsync(new Injector(), async (i) => { const authroizeRemoveEntity = vi.fn(async () => ({ isAllowed: false, message: ':(' })) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id', { authroizeRemoveEntity }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) try { await dataSet.remove(i, 1) throw Error('Should throw') } catch (error) { /** */ } const count = await dataSet.count(i) expect(count).toBe(1) }) }) it('should publish to the onEntityRemoved observable if an entity has been removed', async () => { await usingAsync(new Injector(), async (i) => { expect.assertions(1) addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' })) getRepository(i).createDataSet(TestClass, 'id') getRepository(i) .getDataSetFor(TestClass, 'id') .subscribe('onEntityRemoved', ({ key }) => { expect(key).toEqual(1) }) const dataSet = getDataSetFor(i, TestClass, 'id') await dataSet.add(i, { id: 1, value: 'asd' }) await dataSet.remove(i, 1) }) }) }) })