import nql from '@tryghost/nql'; import assert from 'assert/strict'; import {Knex} from 'knex'; import {BookshelfRepository, ModelClass, ModelInstance} from '../src/index'; type SimpleEntity = { id: string; deleted: boolean; name: string; age: number; birthday: string; } class SimpleBookshelfRepository extends BookshelfRepository { protected modelToEntity(model: ModelInstance): SimpleEntity { return { id: model.id, deleted: false, name: model.get('name') as string, age: model.get('age') as number, birthday: model.get('birthday') as string }; } protected toPrimitive(entity: SimpleEntity): object { return { id: entity.id, name: entity.name, age: entity.age, birthday: entity.birthday }; } protected getFieldToColumnMap(): Record { return { id: 'id', deleted: 'deleted', name: 'name', age: 'age', birthday: 'birthday' }; } } class Model implements ModelClass { items: ModelInstance[] = []; orderRaw?: string; limit?: number; offset?: number; returnCount = false; constructor() { this.items = []; } destroy(data: {id: string;}): Promise { this.items = this.items.filter(item => item.id !== data.id); return Promise.resolve(); } findOne(data: {id: string;}, options?: {require?: boolean | undefined;} | undefined): Promise | null> { const item = this.items.find(i => i.id === data.id); if (!item && options?.require) { throw new Error('Not found'); } return Promise.resolve(item ?? null); } fetchAll(): Promise[]> { const sorted = this.items.slice().sort((a, b) => { for (const order of this.orderRaw?.split(',') ?? []) { const [field, direction] = order.split(' '); const aValue = a.get(field as string) as number; const bValue = b.get(field as string) as number; if (aValue < bValue) { return direction === 'asc' ? -1 : 1; } else if (aValue > bValue) { return direction === 'asc' ? 1 : -1; } } return 0; }); return Promise.resolve(sorted.slice(this.offset ?? 0, (this.offset ?? 0) + (this.limit ?? sorted.length))); } add(data: object): Promise> { const item = { id: (data as any).id, ...data, get(field: string): unknown { return (data as any)[field]; }, set(d: object|string, value?: unknown): void { if (typeof d === 'string') { (data as any)[d] = value; } else { Object.assign(data, d); } }, save(properties: object): Promise> { Object.assign(data, properties); return Promise.resolve(item); } }; this.items.push(item); return Promise.resolve(item); } getFilteredCollection({filter, mongoTransformer}: {filter?: string, mongoTransformer?: unknown}) { // Filter all items by filter and mongoTransformer if (!filter) { return this; } const n = nql(filter, { transformer: mongoTransformer }); const duplicate = new Model(); duplicate.items = this.items.filter(item => n.queryJSON(item)); return duplicate; } count() { return Promise.resolve(this.items.length); } // eslint-disable-next-line no-unused-vars query(f?: (q: Knex.QueryBuilder) => void): Knex.QueryBuilder { const builder = { limit: (limit: number) => { this.limit = limit; return builder; }, offset: (offset: number) => { this.offset = offset; return builder; }, orderByRaw: (order: string) => { this.orderRaw = order; return builder; }, select: () => { return builder; }, count: () => { return builder; }, groupBy: (field: string) => { return Promise.resolve([ { [field]: 5, count: 5 } ]); } } as any as Knex.QueryBuilder; if (f) { f(builder); } return builder; } } describe('BookshelfRepository', function () { it('Can save, retrieve, update and delete entities', async function () { const repository = new SimpleBookshelfRepository(new Model()); checkRetrieving: { const entity = { id: '1', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }; await repository.save(entity); const result = await repository.getById('1'); assert(result); assert(result.name === 'John'); assert(result.age === 30); assert(result.id === '1'); break checkRetrieving; } checkUpdating: { const entity = { id: '2', deleted: false, name: 'John', age: 24, birthday: new Date('2000-01-01').toISOString() }; await repository.save(entity); entity.name = 'Kym'; await repository.save(entity); const result = await repository.getById('2'); assert(result); assert.equal(result.name, 'Kym'); assert.equal(result.age, 24); assert.equal(result.id, '2'); break checkUpdating; } checkDeleting: { const entity = { id: '3', deleted: false, name: 'Egg', age: 180, birthday: new Date('2010-01-01').toISOString() }; await repository.save(entity); assert(await repository.getById('3')); entity.deleted = true; await repository.save(entity); assert(!await repository.getById('3')); break checkDeleting; } }); it('Can save and retrieve all entities', async function () { const repository = new SimpleBookshelfRepository(new Model()); const entities = [{ id: '1', deleted: false, name: 'Kym', age: 24, birthday: new Date('2000-01-01').toISOString() }, { id: '2', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }, { id: '3', deleted: false, name: 'Kevin', age: 5, birthday: new Date('2000-01-01').toISOString() }]; for (const entity of entities) { await repository.save(entity); } const result = await repository.getAll({ order: [{ field: 'age', direction: 'desc' }] }); assert(result); assert(result.length === 3); assert(result[0].age === 30); assert(result[1].age === 24); assert(result[2].age === 5); }); it('Can retrieve page', async function () { const repository = new SimpleBookshelfRepository(new Model()); const entities = [{ id: '1', deleted: false, name: 'Kym', age: 24, birthday: new Date('2000-01-01').toISOString() }, { id: '2', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }, { id: '3', deleted: false, name: 'Kevin', age: 5, birthday: new Date('2000-01-01').toISOString() }]; for (const entity of entities) { await repository.save(entity); } const result = await repository.getPage({ order: [{ field: 'age', direction: 'desc' }], limit: 5, page: 1 }); assert(result); assert(result.length === 3); assert(result[0].age === 30); assert(result[1].age === 24); assert(result[2].age === 5); }); it('Can retrieve page without order', async function () { const repository = new SimpleBookshelfRepository(new Model()); const entities = [{ id: '1', deleted: false, name: 'Kym', age: 24, birthday: new Date('2000-01-01').toISOString() }, { id: '2', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }, { id: '3', deleted: false, name: 'Kevin', age: 5, birthday: new Date('2000-01-01').toISOString() }]; for (const entity of entities) { await repository.save(entity); } const result = await repository.getPage({ order: [], limit: 5, page: 1 }); assert(result); assert(result.length === 3); }); it('Cannot retrieve zero page number', async function () { const repository = new SimpleBookshelfRepository(new Model()); const entities = [{ id: '1', deleted: false, name: 'Kym', age: 24, birthday: new Date('2000-01-01').toISOString() }, { id: '2', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }, { id: '3', deleted: false, name: 'Kevin', age: 5, birthday: new Date('2000-01-01').toISOString() }]; for (const entity of entities) { await repository.save(entity); } const result = repository.getPage({ order: [], limit: 5, page: 0 }); await assert.rejects(result, /page/); }); it('Cannot retrieve zero limit', async function () { const repository = new SimpleBookshelfRepository(new Model()); const entities = [{ id: '1', deleted: false, name: 'Kym', age: 24, birthday: new Date('2000-01-01').toISOString() }, { id: '2', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }, { id: '3', deleted: false, name: 'Kevin', age: 5, birthday: new Date('2000-01-01').toISOString() }]; for (const entity of entities) { await repository.save(entity); } const result = repository.getPage({ order: [], limit: 0, page: 5 }); await assert.rejects(result, /limit/); }); it('Can retrieve count', async function () { const repository = new SimpleBookshelfRepository(new Model()); const entities = [{ id: '1', deleted: false, name: 'Kym', age: 24, birthday: new Date('2000-01-01').toISOString() }, { id: '2', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }, { id: '3', deleted: false, name: 'Kevin', age: 5, birthday: new Date('2000-01-01').toISOString() }]; for (const entity of entities) { await repository.save(entity); } const result = await repository.getCount({}); assert(result === 3); }); it('Can retrieve grouped count', async function () { const repository = new SimpleBookshelfRepository(new Model()); const entities = [{ id: '1', deleted: false, name: 'Kym', age: 24, birthday: new Date('2000-01-01').toISOString() }, { id: '2', deleted: false, name: 'John', age: 30, birthday: new Date('2000-01-01').toISOString() }, { id: '3', deleted: false, name: 'Kevin', age: 5, birthday: new Date('2000-01-01').toISOString() }]; for (const entity of entities) { await repository.save(entity); } const result = await repository.getGroupedCount({groupBy: 'age'}); assert(result.length === 1); assert(result[0].age === 5); assert(result[0].count === 5); }); });