UNPKG

@furystack/core

Version:
480 lines 30.3 kB
import { usingAsync } from '@furystack/utils'; import { Injector } from '@furystack/inject'; import { describe, it, expect, vi } from 'vitest'; export class TestClass { } let idIndex = 0; export const createMockEntity = (part) => ({ id: idIndex++, stringValue1: 'foo', stringValue2: 'bar', numberValue1: Math.round(Math.random() * 1000), numberValue2: Math.round(Math.random() * 10000) / 100, booleanValue: true, dateValue: new Date(), ...part, }); export const createStoreTest = (options) => { describe(`Standard Physical Store tests for '${options.typeName}'`, () => { describe('General CRUD', () => { it('Should be created with empty by default', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const count = await store.count(); expect(count).toBe(0); }); }); it('Should be able to store an entity', async () => { await usingAsync(new Injector(), async (i) => { const onAddListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityAdded', onAddListener); const entity = createMockEntity(); await store.add(entity); const count = await store.count(); expect(count).toBe(1); expect(onAddListener).toHaveBeenCalledTimes(1); expect(onAddListener).toHaveBeenCalledWith({ entity }); }); }); it('Should be able to store an entity without providing an unique Id', async () => { await usingAsync(new Injector(), async (i) => { const onAddListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityAdded', onAddListener); const { id, ...entityWithoutId } = createMockEntity(); const { created } = await store.add(entityWithoutId); expect(created.length).toBe(1); const count = await store.count(); expect(count).toBe(1); const retrieved = await store.get(created[0].id); expect(retrieved).toEqual(created[0]); expect(onAddListener).toHaveBeenCalledTimes(1); expect(onAddListener).toHaveBeenCalledWith({ entity: created[0] }); }); }); it('Should be able to store multiple entities', async () => { await usingAsync(new Injector(), async (i) => { const onAddListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityAdded', onAddListener); const entity1 = createMockEntity(); const entity2 = createMockEntity(); await store.add(entity1, entity2); const count = await store.count(); expect(count).toBe(2); expect(onAddListener).toHaveBeenCalledTimes(2); expect(onAddListener).toHaveBeenCalledWith({ entity: entity1 }); expect(onAddListener).toHaveBeenCalledWith({ entity: entity2 }); }); }); it('Add should throw and skip adding on duplicate IDs', async () => { await usingAsync(new Injector(), async (i) => { const onAddListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityAdded', onAddListener); const entity = createMockEntity(); await store.add(entity); await expect(store.add(entity)).rejects.toThrow(); const count = await store.count(); expect(count).toBe(1); expect(onAddListener).toHaveBeenCalledTimes(1); expect(onAddListener).toHaveBeenCalledWith({ entity }); }); }); it('Should return undefined if no entry has been found', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const entity = await store.get(1); expect(entity).toBeUndefined(); }); }); it('Should be able to retrieve an added entity', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const entity = createMockEntity(); await store.add(entity); const retrieved = await store.get(entity.id); expect(retrieved).toEqual(entity); }); }); it('Should be able to retrieve an added entity with projection', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const entity = createMockEntity(); await store.add(entity); const retrieved = await store.get(entity.id, ['id', 'stringValue1']); expect(retrieved).not.toEqual(entity); expect(retrieved).to.have.all.keys('id', 'stringValue1'); expect(retrieved?.id).toBe(entity.id); expect(retrieved?.stringValue1).toBe(entity.stringValue1); }); }); it('Should be able to update an added entity', async () => { await usingAsync(new Injector(), async (i) => { const updateListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityUpdated', updateListener); const entity = createMockEntity(); await store.add(entity); await store.update(entity.id, { stringValue1: 'modified' }); const retrieved = await store.get(entity.id); expect(retrieved?.stringValue1).toEqual('modified'); expect(updateListener).toHaveBeenCalledTimes(1); expect(updateListener).toHaveBeenCalledWith({ id: entity.id, change: { stringValue1: 'modified' } }); }); }); it('Update should throw an error if the entity does not exists', async () => { await usingAsync(new Injector(), async (i) => { const updateListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityUpdated', updateListener); const entity = createMockEntity(); await expect(store.update(entity.id, entity)).rejects.toThrow('Entity not found'); expect(updateListener).not.toHaveBeenCalled(); }); }); it('Should remove an entity', async () => { await usingAsync(new Injector(), async (i) => { const removeListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityRemoved', removeListener); const entity = createMockEntity(); await store.add(entity); const count = await store.count(); expect(count).toBe(1); await store.remove(entity.id); const countAferDelete = await store.count(); expect(countAferDelete).toBe(0); expect(removeListener).toHaveBeenCalledTimes(1); }); }); it('Should remove multiple entities at once', async () => { await usingAsync(new Injector(), async (i) => { const removeListener = vi.fn(); const store = options.createStore(i); store.addListener('onEntityRemoved', removeListener); const entity1 = createMockEntity(); const entity2 = createMockEntity(); const entity3 = createMockEntity(); await store.add(entity1, entity2, entity3); const count = await store.count(); expect(count).toBe(3); await store.remove(entity1.id, entity2.id); const countAferDelete = await store.count(); expect(countAferDelete).toBe(1); expect(removeListener).toHaveBeenCalledTimes(2); expect(removeListener).toHaveBeenCalledWith({ key: entity1.id }); expect(removeListener).toHaveBeenCalledWith({ key: entity2.id }); await store.remove(entity3.id); const countAferDeleteAll = await store.count(); expect(countAferDeleteAll).toBe(0); expect(removeListener).toHaveBeenCalledTimes(3); expect(removeListener).toHaveBeenCalledWith({ key: entity3.id }); }); }); }); describe('Top, skip', () => { it('Should respect top and skip', async () => { await usingAsync(new Injector(), async (injector) => { const store = options.createStore(injector); for (let i = 0; i < 10; i++) { await store.add(createMockEntity({ id: i })); } const zeroToThree = await store.find({ top: 4, select: ['id'] }); expect(zeroToThree).toEqual([{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]); const fiveToEight = await store.find({ skip: 5, top: 4, select: ['id'] }); expect(fiveToEight).toEqual([{ id: 5 }, { id: 6 }, { id: 7 }, { id: 8 }]); const eightNine = await store.find({ skip: 8, select: ['id'] }); expect(eightNine).toEqual([{ id: 8 }, { id: 9 }]); }); }); }); describe('Ordering', () => { it('Should sort by numeric values', async () => { await usingAsync(new Injector(), async (injector) => { const store = options.createStore(injector); for (let i = 0; i < 10; i++) { await store.add(createMockEntity({ id: i, numberValue1: Math.random(), numberValue2: Math.random() })); } // For equality await store.add(createMockEntity({ id: 20, numberValue1: 0, numberValue2: 0 })); await store.add(createMockEntity({ id: 21, numberValue1: 0, numberValue2: 0 })); const orderByValue1Asc = await store.find({ order: { numberValue1: 'ASC' } }); let min = 0; for (const currentValue of orderByValue1Asc) { if (min > currentValue.numberValue1) { throw Error('Order failed!'); } min = currentValue.numberValue1; } const orderByValue1Desc = await store.find({ order: { numberValue1: 'DESC' } }); let max = Number.MAX_SAFE_INTEGER; for (const currentValue of orderByValue1Desc) { if (max < currentValue.numberValue1) { throw Error('Order failed!'); } max = currentValue.numberValue1; } }); }); }); describe('Filtering', () => { it('should filter strings with $eq', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'def' }), createMockEntity({ id: 3, stringValue1: 'def' })); const result = await store.find({ filter: { stringValue1: { $eq: 'def' } } }); expect(result.length).toBe(2); }); }); it('should filter numbers with $eq', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, numberValue1: 1 }), createMockEntity({ id: 2, numberValue1: 2 }), createMockEntity({ id: 3, numberValue1: 2 })); const result = await store.find({ filter: { numberValue1: { $eq: 2 } } }); expect(result.length).toBe(2); }); }); it('filter should return the corresponding entries for multiple props', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'def', stringValue2: 'def' }), createMockEntity({ id: 3, stringValue1: 'def' })); const result = await store.find({ filter: { stringValue1: { $eq: 'def' }, stringValue2: { $eq: 'def' } } }); expect(result.length).toBe(1); }); }); it('filter should return the corresponding entries with $in statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ stringValue1: 'asd' }), createMockEntity({ stringValue1: 'def' }), createMockEntity({ stringValue1: 'sdf' })); const result = await store.find({ filter: { stringValue1: { $in: ['asd', 'def'] } } }); expect(result.length).toBe(2); expect(result.map((r) => r.stringValue1)).toEqual(['asd', 'def']); }); }); it('filter should return the corresponding entries with $nin statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'def' }), createMockEntity({ id: 3, stringValue1: 'sdf' })); const result = await store.find({ filter: { stringValue1: { $nin: ['asd', 'def'] } } }); expect(result.length).toBe(1); expect(result.map((r) => r.stringValue1)).toEqual(['sdf']); }); }); it('filter should return the corresponding entries with $ne statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'def' }), createMockEntity({ id: 3, stringValue1: 'sdf' })); const result = await store.find({ filter: { stringValue1: { $ne: 'asd' } } }); expect(result.length).toBe(2); expect(result.map((r) => r.stringValue1)).toEqual(['def', 'sdf']); }); }); it('filter should return the corresponding entries with $lt statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1 }), createMockEntity({ id: 2, numberValue1: 2 }), createMockEntity({ id: 3, numberValue1: 3 })); const result = await store.find({ filter: { numberValue1: { $lt: 2 } } }); expect(result.length).toBe(1); expect(result).toEqual([created[0]]); }); }); it('filter should return the corresponding entries with $lte statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1 }), createMockEntity({ id: 2, numberValue1: 2 }), createMockEntity({ id: 3, numberValue1: 3 })); const result = await store.find({ filter: { numberValue1: { $lte: 2 } } }); expect(result.length).toBe(2); expect(result).toEqual([created[0], created[1]]); }); }); it('filter should return the corresponding entries with $gt statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1 }), createMockEntity({ id: 2, numberValue1: 2 }), createMockEntity({ id: 3, numberValue1: 3 })); const result = await store.find({ filter: { numberValue1: { $gt: 2 } } }); expect(result.length).toBe(1); expect(result).toEqual([created[2]]); }); }); it('filter should return the corresponding entries with $gte statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1 }), createMockEntity({ id: 2, numberValue1: 2 }), createMockEntity({ id: 3, numberValue1: 3 })); const result = await store.find({ filter: { numberValue1: { $gte: 2 } } }); expect(result.length).toBe(2); expect(result).toEqual([created[1], created[2]]); }); }); it('filter should return the corresponding entries with $in AND $eq statement', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'def' }), createMockEntity({ id: 3, stringValue1: 'sdf' })); const result = await store.find({ filter: { stringValue1: { $in: ['asd', 'def'], $eq: 'asd' } } }); expect(result.length).toBe(1); expect(result.map((r) => r.stringValue1)).toEqual(['asd']); }); }); describe('logical $and statements', () => { it('should filter $and logical statements with $eq statements', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1, numberValue2: 1 }), createMockEntity({ id: 2, numberValue1: 2, numberValue2: 1 }), createMockEntity({ id: 3, numberValue1: 3, numberValue2: 1 })); const result = await store.find({ filter: { $and: [{ numberValue1: { $eq: 2 } }, { numberValue2: { $eq: 1 } }] }, }); expect(result.length).toBe(1); expect(result[0]).toEqual(created[1]); }); }); it('should filter $and logical statements with $ne statements', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1, numberValue2: 2 }), createMockEntity({ id: 2, numberValue1: 2, numberValue2: 3 }), createMockEntity({ id: 3, numberValue1: 3, numberValue2: 1 })); const result = await store.find({ filter: { $and: [{ numberValue1: { $ne: 2 } }, { numberValue2: { $ne: 1 } }] }, }); expect(result.length).toBe(1); expect(result[0]).toEqual(created[0]); }); }); it('should filter $and logical statements with $lt/$gt statements', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1, numberValue2: 2 }), createMockEntity({ id: 2, numberValue1: 2, numberValue2: 3 }), createMockEntity({ id: 3, numberValue1: 3, numberValue2: 1 })); const result = await store.find({ filter: { $and: [{ numberValue1: { $lt: 3 } }, { numberValue2: { $gt: 2 } }] }, }); expect(result.length).toBe(1); expect(result[0]).toEqual(created[1]); }); }); it('should filter $and logical statements with $lte/$gte statements', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1, numberValue2: 1 }), createMockEntity({ id: 2, numberValue1: 2, numberValue2: 2 }), createMockEntity({ id: 3, numberValue1: 3, numberValue2: 3 })); const result = await store.find({ filter: { $and: [{ numberValue1: { $lte: 2 } }, { numberValue2: { $gte: 2 } }] }, }); expect(result.length).toBe(1); expect(result[0]).toEqual(created[1]); }); }); }); describe('logical $or statements', () => { it('should filter logical $or statements with $eq statements', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'aaa' }), createMockEntity({ id: 3, stringValue1: 'bbb' })); const result = await store.find({ filter: { $or: [{ stringValue1: { $eq: 'aaa' } }, { stringValue1: { $eq: 'bbb' } }] }, }); expect(result.length).toBe(2); expect(result).toEqual([created[1], created[2]]); }); }); it('should filter logical $or statements with $neq statements', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'aaa' }), createMockEntity({ id: 3, stringValue1: 'bbb' })); const result = await store.find({ filter: { $or: [{ stringValue1: { $ne: 'aaa' } }, { stringValue1: { $ne: 'bbb' } }] }, }); expect(result.length).toBe(3); expect(result).toEqual(created); }); }); }); describe('Nested $or and $and logical operators', () => { it('should filter $and operators inside $or-s', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); const { created } = await store.add(createMockEntity({ id: 1, numberValue1: 1, numberValue2: 3, booleanValue: true }), createMockEntity({ id: 2, numberValue1: 2, numberValue2: 2, booleanValue: false }), createMockEntity({ id: 3, numberValue1: 3, numberValue2: 1, booleanValue: true })); const result = await store.find({ filter: { $or: [ { $and: [{ numberValue1: { $ne: 2 } }, { numberValue2: { $eq: 1 } }], }, { $and: [{ numberValue1: { $ne: 3 } }, { booleanValue: { $ne: true } }], }, ], }, }); expect(result.length).toBe(2); expect(result).toEqual([created[1], created[2]]); }); }); }); if (!options.skipRegexTests) { it('filter should return the corresponding entries with $regex', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'aaa' }), createMockEntity({ id: 3, stringValue1: 'bbb' })); const result = await store.find({ filter: { stringValue1: { $regex: '([a])' } } }); expect(result.length).toBe(2); expect(result.map((r) => r.stringValue1)).toEqual(['asd', 'aaa']); }); }); } if (!options.skipStringTests) { it('filter should return the corresponding entries with $startsWith', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'aaa' }), createMockEntity({ id: 3, stringValue1: 'bbb' })); const result = await store.find({ filter: { stringValue1: { $startsWith: 'aa' } } }); expect(result.length).toBe(1); expect(result.map((r) => r.stringValue1)).toEqual(['aaa']); }); }); it('filter should return the corresponding entries with $endsWith', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'aaa' }), createMockEntity({ id: 3, stringValue1: 'bbb' })); const result = await store.find({ filter: { stringValue1: { $endsWith: 'bb' } } }); expect(result.length).toBe(1); expect(result.map((r) => r.stringValue1)).toEqual(['bbb']); }); }); it('filter should return the corresponding entries with $like', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ id: 1, stringValue1: 'asd' }), createMockEntity({ id: 2, stringValue1: 'aaa' }), createMockEntity({ id: 3, stringValue1: 'bbb' })); const result = await store.find({ filter: { stringValue1: { $like: '%a%' } } }); expect(result.length).toBe(2); expect(result.map((r) => r.stringValue1)).toEqual(['asd', 'aaa']); const endsWithAResult = await store.find({ filter: { stringValue1: { $like: '%a' } } }); expect(endsWithAResult.length).toBe(1); expect(endsWithAResult.map((r) => r.stringValue1)).toEqual(['aaa']); const startsWithAResult = await store.find({ filter: { stringValue1: { $like: 'a%' } } }); expect(startsWithAResult.length).toBe(2); expect(startsWithAResult.map((r) => r.stringValue1)).toEqual(['asd', 'aaa']); }); }); } }); describe('Count', () => { it('Should return the count', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity(), createMockEntity(), createMockEntity()); const count = await store.count(); expect(count).toBe(3); }); }); it('Should respect filters', async () => { await usingAsync(new Injector(), async (i) => { const store = options.createStore(i); await store.add(createMockEntity({ numberValue1: 1 }), createMockEntity({ numberValue1: 1 }), createMockEntity({ numberValue1: 2 })); const count = await store.count({ numberValue1: { $eq: 1 } }); expect(count).toBe(2); const count2 = await store.count({ numberValue1: { $eq: 2 } }); expect(count2).toBe(1); }); }); }); }); }; //# sourceMappingURL=create-physical-store-tests.js.map