From 608438eec4a2ea75ea838955473ce2e0af475794 Mon Sep 17 00:00:00 2001 From: biniam Date: Mon, 5 Nov 2018 01:44:41 -0500 Subject: [PATCH 1/2] feat(repository): initial AtomicCrudRepository implementation --- .../src/repositories/atomic.repository.ts | 69 ++++ packages/repository/src/repositories/index.ts | 1 + .../repositories/atomic.repository.unit.ts | 351 ++++++++++++++++++ 3 files changed, 421 insertions(+) create mode 100644 packages/repository/src/repositories/atomic.repository.ts create mode 100644 packages/repository/test/unit/repositories/atomic.repository.unit.ts diff --git a/packages/repository/src/repositories/atomic.repository.ts b/packages/repository/src/repositories/atomic.repository.ts new file mode 100644 index 000000000000..b341244dec1c --- /dev/null +++ b/packages/repository/src/repositories/atomic.repository.ts @@ -0,0 +1,69 @@ +import {Entity} from '../model'; +import {DataObject, Options, AnyObject} from '../common-types'; +import {Filter} from '../query'; +import {EntityCrudRepository} from './repository'; +import { + DefaultCrudRepository, + juggler, + ensurePromise, +} from './legacy-juggler-bridge'; +import * as assert from 'assert'; +import * as legacy from 'loopback-datasource-juggler'; + +export interface AtomicCrudRepository + extends EntityCrudRepository { + /** + * Finds one record matching the filter object. If not found, creates + * the object using the data provided as second argument. In this sense it is + * the same as `find`, but limited to one object. Returns an object, not + * collection. If you don't provide the filter object argument, it tries to + * locate an existing object that matches the `data` argument. + * + * @param filter Filter object used to match existing model instance + * @param entity Entity to be used for creating a new instance or match + * existing instance if filter is empty + * @param options Options for the operation + * @returns A promise that will be resolve with the created or found instance + * and a 'created' boolean value + */ + findOrCreate( + filter: Filter, + entity: DataObject, + options?: Options, + ): Promise<[T, boolean]>; +} + +export class DefaultAtomicCrudRepository + extends DefaultCrudRepository + implements AtomicCrudRepository { + constructor( + entityClass: typeof Entity & { + prototype: T; + }, + dataSource: juggler.DataSource, + ) { + assert( + dataSource.connector !== undefined, + `Connector instance must exist and support atomic operations`, + ); + super(entityClass, dataSource); + } + + async findOrCreate( + filter: Filter, + entity: DataObject, + options?: AnyObject | undefined, + ): Promise<[T, boolean]> { + if ( + this.dataSource.connector && + typeof this.dataSource.connector.findOrCreate === 'function' + ) { + const result = await ensurePromise( + this.modelClass.findOrCreate(filter as legacy.Filter, entity, options), + ); + return [this.toEntity(result[0]), result[1]]; + } else { + throw new Error('Method not implemented.'); + } + } +} diff --git a/packages/repository/src/repositories/index.ts b/packages/repository/src/repositories/index.ts index 60175cd87ba5..c85c4d267d27 100644 --- a/packages/repository/src/repositories/index.ts +++ b/packages/repository/src/repositories/index.ts @@ -8,3 +8,4 @@ export * from './legacy-juggler-bridge'; export * from './kv.repository.bridge'; export * from './repository'; export * from './constraint-utils'; +export * from './atomic.repository'; diff --git a/packages/repository/test/unit/repositories/atomic.repository.unit.ts b/packages/repository/test/unit/repositories/atomic.repository.unit.ts new file mode 100644 index 000000000000..09f290284ace --- /dev/null +++ b/packages/repository/test/unit/repositories/atomic.repository.unit.ts @@ -0,0 +1,351 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect} from '@loopback/testlab'; +import { + Entity, + EntityNotFoundError, + juggler, + ModelDefinition, + DefaultAtomicCrudRepository, +} from '../../..'; + +describe('AtomicCrudRepository', () => { + let ds: juggler.DataSource; + + class Note extends Entity { + static definition = new ModelDefinition({ + name: 'Note', + properties: { + title: 'string', + content: 'string', + id: {name: 'id', type: 'number', id: true}, + }, + }); + + title?: string; + content?: string; + id: number; + + constructor(data: Partial) { + super(data); + } + } + + beforeEach(() => { + ds = new juggler.DataSource({ + name: 'db', + connector: 'memory', + }); + }); + + context('constructor', () => { + class ShoppingList extends Entity { + static definition = new ModelDefinition({ + name: 'ShoppingList', + properties: { + id: { + type: 'number', + id: true, + }, + created: { + type: () => Date, + }, + toBuy: { + type: 'array', + itemType: 'string', + }, + toVisit: { + type: Array, + itemType: () => String, + }, + }, + }); + + created: Date; + toBuy: String[]; + toVisit: String[]; + } + + it('converts PropertyDefinition with array type', () => { + const originalPropertyDefinition = Object.assign( + {}, + ShoppingList.definition.properties, + ); + const listDefinition = new DefaultAtomicCrudRepository(ShoppingList, ds) + .modelClass.definition; + const jugglerPropertyDefinition = { + created: {type: Date}, + toBuy: { + type: [String], + }, + toVisit: { + type: [String], + }, + }; + + expect(listDefinition.properties).to.containDeep( + jugglerPropertyDefinition, + ); + expect(ShoppingList.definition.properties).to.containDeep( + originalPropertyDefinition, + ); + }); + + it('throws if a connector instance is not defined for a datasource', () => { + const dsWithoutConnector = new juggler.DataSource({ + name: 'ds2', + }); + let repo; + expect(() => { + repo = new DefaultAtomicCrudRepository( + ShoppingList, + dsWithoutConnector, + ); + }).to.throw( + /Connector instance must exist and support atomic operations/, + ); + }); + + it('shares the backing PersistedModel across repo instances', () => { + const model1 = new DefaultAtomicCrudRepository(Note, ds).modelClass; + const model2 = new DefaultAtomicCrudRepository(Note, ds).modelClass; + + expect(model1 === model2).to.be.true(); + }); + }); + + context('Non atomic CRUD operations', () => { + it('implements Repository.create()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note = await repo.create({title: 't3', content: 'c3'}); + const result = await repo.findById(note.id); + expect(result.toJSON()).to.eql(note.toJSON()); + }); + + it('implements Repository.createAll()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const notes = await repo.createAll([ + {title: 't3', content: 'c3'}, + {title: 't4', content: 'c4'}, + ]); + expect(notes.length).to.eql(2); + }); + + it('implements Repository.find()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.createAll([ + {title: 't1', content: 'c1'}, + {title: 't2', content: 'c2'}, + ]); + const notes = await repo.find({where: {title: 't1'}}); + expect(notes.length).to.eql(1); + }); + + it('implements Repository.findOne()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.createAll([ + {title: 't1', content: 'c1'}, + {title: 't1', content: 'c2'}, + ]); + const note = await repo.findOne({ + where: {title: 't1'}, + order: ['content DESC'], + }); + expect(note).to.not.be.null(); + expect(note && note.title).to.eql('t1'); + expect(note && note.content).to.eql('c2'); + }); + it('returns null if Repository.findOne() does not return a value', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.createAll([ + {title: 't1', content: 'c1'}, + {title: 't1', content: 'c2'}, + ]); + const note = await repo.findOne({ + where: {title: 't5'}, + order: ['content DESC'], + }); + expect(note).to.be.null(); + }); + + describe('findById', () => { + it('returns the correct instance', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note = await repo.create({ + title: 'a-title', + content: 'a-content', + }); + const result = await repo.findById(note.id); + expect(result && result.toJSON()).to.eql(note.toJSON()); + }); + + it('throws when the instance does not exist', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await expect(repo.findById(999999)).to.be.rejectedWith({ + code: 'ENTITY_NOT_FOUND', + message: 'Entity not found: Note with id 999999', + }); + }); + }); + + it('implements Repository.delete()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note = await repo.create({title: 't3', content: 'c3'}); + + await repo.delete(note); + + const found = await repo.find({where: {id: note.id}}); + expect(found).to.be.empty(); + }); + + it('implements Repository.deleteById()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note = await repo.create({title: 't3', content: 'c3'}); + + await repo.deleteById(note.id); + + const found = await repo.find({where: {id: note.id}}); + expect(found).to.be.empty(); + }); + + it('throws EntityNotFoundError when deleting an unknown id', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await expect(repo.deleteById(99999)).to.be.rejectedWith( + EntityNotFoundError, + ); + }); + + it('implements Repository.deleteAll()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.create({title: 't3', content: 'c3'}); + await repo.create({title: 't4', content: 'c4'}); + const result = await repo.deleteAll({title: 't3'}); + expect(result.count).to.eql(1); + }); + + it('implements Repository.updateById()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note = await repo.create({title: 't3', content: 'c3'}); + + const id = note.id; + const delta = {content: 'c4'}; + await repo.updateById(id, delta); + + const updated = await repo.findById(id); + expect(updated.toJSON()).to.eql(Object.assign(note.toJSON(), delta)); + }); + + it('throws EntityNotFound error when updating an unknown id', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await expect(repo.updateById(9999, {title: 't4'})).to.be.rejectedWith( + EntityNotFoundError, + ); + }); + + it('implements Repository.updateAll()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.create({title: 't3', content: 'c3'}); + await repo.create({title: 't4', content: 'c4'}); + const result = await repo.updateAll({content: 'c5'}, {}); + expect(result.count).to.eql(2); + const notes = await repo.find({where: {title: 't3'}}); + expect(notes[0].content).to.eql('c5'); + }); + + it('implements Repository.updateAll() without a where object', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.create({title: 't3', content: 'c3'}); + await repo.create({title: 't4', content: 'c4'}); + const result = await repo.updateAll({content: 'c5'}); + expect(result.count).to.eql(2); + const notes = await repo.find(); + const titles = notes.map(n => `${n.title}:${n.content}`); + expect(titles).to.deepEqual(['t3:c5', 't4:c5']); + }); + + it('implements Repository.count()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.create({title: 't3', content: 'c3'}); + await repo.create({title: 't4', content: 'c4'}); + const result = await repo.count(); + expect(result.count).to.eql(2); + }); + + it('implements Repository.save() without id', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note = await repo.save(new Note({title: 't3', content: 'c3'})); + const result = await repo.findById(note!.id); + expect(result.toJSON()).to.eql(note!.toJSON()); + }); + + it('implements Repository.save() with id', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note1 = await repo.create({title: 't3', content: 'c3'}); + note1.content = 'c4'; + const note = await repo.save(note1); + const result = await repo.findById(note!.id); + expect(result.toJSON()).to.eql(note1.toJSON()); + }); + + it('implements Repository.replaceById()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note = await repo.create({title: 't3', content: 'c3'}); + await repo.replaceById(note.id, {title: 't4', content: undefined}); + const result = await repo.findById(note.id); + expect(result.toJSON()).to.eql({ + id: note.id, + title: 't4', + content: undefined, + }); + }); + + it('throws EntityNotFound error when replacing an unknown id', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await expect(repo.replaceById(9999, {title: 't4'})).to.be.rejectedWith( + EntityNotFoundError, + ); + }); + + it('implements Repository.exists()', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const note1 = await repo.create({title: 't3', content: 'c3'}); + const ok = await repo.exists(note1.id); + expect(ok).to.be.true(); + }); + }); + + context('Atomic CRUD operations', () => { + // TODO: how can we test a connector that doesn't have findOrCreate? + it('uses findOrCreate to create an instance if not found', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + const result = await repo.findOrCreate( + {where: {title: 't1'}}, + {title: 'new t1', content: 'new c1'}, + ); + expect(result[0].toJSON()).to.containEql({ + title: 'new t1', + content: 'new c1', + }); + expect(result[1]).to.be.true(); + }); + it('uses findOrCreate to find an existing instance', async () => { + const repo = new DefaultAtomicCrudRepository(Note, ds); + await repo.createAll([ + {title: 't1', content: 'c1'}, + {title: 't2', content: 'c2'}, + ]); + const result = await repo.findOrCreate( + {where: {title: 't1'}}, + {title: 'new t1', content: 'new c1'}, + ); + expect(result[0].toJSON()).to.containEql({ + title: 't1', + content: 'c1', + }); + expect(result[1]).to.be.false(); + }); + }); +}); From d3cce33a1a32fbd99e9c59aa3940b5fd53d19932 Mon Sep 17 00:00:00 2001 From: biniam Date: Thu, 22 Nov 2018 01:18:43 -0500 Subject: [PATCH 2/2] fixup! apply feedback --- .../src/repositories/atomic.repository.ts | 28 +- .../repositories/atomic.repository.unit.ts | 252 ++---------------- 2 files changed, 44 insertions(+), 236 deletions(-) diff --git a/packages/repository/src/repositories/atomic.repository.ts b/packages/repository/src/repositories/atomic.repository.ts index b341244dec1c..5b7359ed7eb9 100644 --- a/packages/repository/src/repositories/atomic.repository.ts +++ b/packages/repository/src/repositories/atomic.repository.ts @@ -10,6 +10,11 @@ import { import * as assert from 'assert'; import * as legacy from 'loopback-datasource-juggler'; +export interface FindOrCreateResult { + entity: T; + found: boolean; +} + export interface AtomicCrudRepository extends EntityCrudRepository { /** @@ -30,7 +35,7 @@ export interface AtomicCrudRepository filter: Filter, entity: DataObject, options?: Options, - ): Promise<[T, boolean]>; + ): FindOrCreateResult; } export class DefaultAtomicCrudRepository @@ -53,17 +58,18 @@ export class DefaultAtomicCrudRepository filter: Filter, entity: DataObject, options?: AnyObject | undefined, - ): Promise<[T, boolean]> { - if ( - this.dataSource.connector && - typeof this.dataSource.connector.findOrCreate === 'function' - ) { - const result = await ensurePromise( - this.modelClass.findOrCreate(filter as legacy.Filter, entity, options), - ); - return [this.toEntity(result[0]), result[1]]; - } else { + ): Promise> { + const canRunAtomically = + typeof this.dataSource.connector!.findOrCreate === 'function'; + if (!canRunAtomically) { throw new Error('Method not implemented.'); + // FIXME add machine-readable `err.code` } + + const result = await ensurePromise( + this.modelClass.findOrCreate(filter as legacy.Filter, entity, options), + ); + + return {entity: this.toEntity(result[0]), found: !result[1]}; } } diff --git a/packages/repository/test/unit/repositories/atomic.repository.unit.ts b/packages/repository/test/unit/repositories/atomic.repository.unit.ts index 09f290284ace..243d1c06b6e9 100644 --- a/packages/repository/test/unit/repositories/atomic.repository.unit.ts +++ b/packages/repository/test/unit/repositories/atomic.repository.unit.ts @@ -10,6 +10,7 @@ import { juggler, ModelDefinition, DefaultAtomicCrudRepository, + DefaultCrudRepository, } from '../../..'; describe('AtomicCrudRepository', () => { @@ -69,31 +70,6 @@ describe('AtomicCrudRepository', () => { toVisit: String[]; } - it('converts PropertyDefinition with array type', () => { - const originalPropertyDefinition = Object.assign( - {}, - ShoppingList.definition.properties, - ); - const listDefinition = new DefaultAtomicCrudRepository(ShoppingList, ds) - .modelClass.definition; - const jugglerPropertyDefinition = { - created: {type: Date}, - toBuy: { - type: [String], - }, - toVisit: { - type: [String], - }, - }; - - expect(listDefinition.properties).to.containDeep( - jugglerPropertyDefinition, - ); - expect(ShoppingList.definition.properties).to.containDeep( - originalPropertyDefinition, - ); - }); - it('throws if a connector instance is not defined for a datasource', () => { const dsWithoutConnector = new juggler.DataSource({ name: 'ds2', @@ -108,212 +84,38 @@ describe('AtomicCrudRepository', () => { /Connector instance must exist and support atomic operations/, ); }); - - it('shares the backing PersistedModel across repo instances', () => { - const model1 = new DefaultAtomicCrudRepository(Note, ds).modelClass; - const model2 = new DefaultAtomicCrudRepository(Note, ds).modelClass; - - expect(model1 === model2).to.be.true(); - }); }); - context('Non atomic CRUD operations', () => { - it('implements Repository.create()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note = await repo.create({title: 't3', content: 'c3'}); - const result = await repo.findById(note.id); - expect(result.toJSON()).to.eql(note.toJSON()); - }); - - it('implements Repository.createAll()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const notes = await repo.createAll([ - {title: 't3', content: 'c3'}, - {title: 't4', content: 'c4'}, - ]); - expect(notes.length).to.eql(2); - }); - - it('implements Repository.find()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await repo.createAll([ - {title: 't1', content: 'c1'}, - {title: 't2', content: 'c2'}, - ]); - const notes = await repo.find({where: {title: 't1'}}); - expect(notes.length).to.eql(1); - }); - - it('implements Repository.findOne()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await repo.createAll([ - {title: 't1', content: 'c1'}, - {title: 't1', content: 'c2'}, - ]); - const note = await repo.findOne({ - where: {title: 't1'}, - order: ['content DESC'], - }); - expect(note).to.not.be.null(); - expect(note && note.title).to.eql('t1'); - expect(note && note.content).to.eql('c2'); - }); - it('returns null if Repository.findOne() does not return a value', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await repo.createAll([ - {title: 't1', content: 'c1'}, - {title: 't1', content: 'c2'}, - ]); - const note = await repo.findOne({ - where: {title: 't5'}, - order: ['content DESC'], - }); - expect(note).to.be.null(); - }); - - describe('findById', () => { - it('returns the correct instance', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note = await repo.create({ - title: 'a-title', - content: 'a-content', - }); - const result = await repo.findById(note.id); - expect(result && result.toJSON()).to.eql(note.toJSON()); - }); - - it('throws when the instance does not exist', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await expect(repo.findById(999999)).to.be.rejectedWith({ - code: 'ENTITY_NOT_FOUND', - message: 'Entity not found: Note with id 999999', - }); - }); - }); - - it('implements Repository.delete()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note = await repo.create({title: 't3', content: 'c3'}); - - await repo.delete(note); - - const found = await repo.find({where: {id: note.id}}); - expect(found).to.be.empty(); - }); - - it('implements Repository.deleteById()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note = await repo.create({title: 't3', content: 'c3'}); - - await repo.deleteById(note.id); - - const found = await repo.find({where: {id: note.id}}); - expect(found).to.be.empty(); - }); - - it('throws EntityNotFoundError when deleting an unknown id', async () => { + context('DefaultCrudRepository', () => { + it('Implements same functionalities as DefaultCrudRepo', async () => { const repo = new DefaultAtomicCrudRepository(Note, ds); - await expect(repo.deleteById(99999)).to.be.rejectedWith( - EntityNotFoundError, + expect(repo.create).to.equal(DefaultCrudRepository.prototype.create); + expect(repo.createAll).to.equal( + DefaultCrudRepository.prototype.createAll, ); - }); - - it('implements Repository.deleteAll()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await repo.create({title: 't3', content: 'c3'}); - await repo.create({title: 't4', content: 'c4'}); - const result = await repo.deleteAll({title: 't3'}); - expect(result.count).to.eql(1); - }); - - it('implements Repository.updateById()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note = await repo.create({title: 't3', content: 'c3'}); - - const id = note.id; - const delta = {content: 'c4'}; - await repo.updateById(id, delta); - - const updated = await repo.findById(id); - expect(updated.toJSON()).to.eql(Object.assign(note.toJSON(), delta)); - }); - - it('throws EntityNotFound error when updating an unknown id', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await expect(repo.updateById(9999, {title: 't4'})).to.be.rejectedWith( - EntityNotFoundError, + expect(repo.find).to.equal(DefaultCrudRepository.prototype.find); + expect(repo.findOne).to.equal(DefaultCrudRepository.prototype.findOne); + expect(repo.findById).to.equal(DefaultCrudRepository.prototype.findById); + expect(repo.delete).to.equal(DefaultCrudRepository.prototype.delete); + expect(repo.deleteAll).to.equal( + DefaultCrudRepository.prototype.deleteAll, ); - }); - - it('implements Repository.updateAll()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await repo.create({title: 't3', content: 'c3'}); - await repo.create({title: 't4', content: 'c4'}); - const result = await repo.updateAll({content: 'c5'}, {}); - expect(result.count).to.eql(2); - const notes = await repo.find({where: {title: 't3'}}); - expect(notes[0].content).to.eql('c5'); - }); - - it('implements Repository.updateAll() without a where object', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await repo.create({title: 't3', content: 'c3'}); - await repo.create({title: 't4', content: 'c4'}); - const result = await repo.updateAll({content: 'c5'}); - expect(result.count).to.eql(2); - const notes = await repo.find(); - const titles = notes.map(n => `${n.title}:${n.content}`); - expect(titles).to.deepEqual(['t3:c5', 't4:c5']); - }); - - it('implements Repository.count()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await repo.create({title: 't3', content: 'c3'}); - await repo.create({title: 't4', content: 'c4'}); - const result = await repo.count(); - expect(result.count).to.eql(2); - }); - - it('implements Repository.save() without id', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note = await repo.save(new Note({title: 't3', content: 'c3'})); - const result = await repo.findById(note!.id); - expect(result.toJSON()).to.eql(note!.toJSON()); - }); - - it('implements Repository.save() with id', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note1 = await repo.create({title: 't3', content: 'c3'}); - note1.content = 'c4'; - const note = await repo.save(note1); - const result = await repo.findById(note!.id); - expect(result.toJSON()).to.eql(note1.toJSON()); - }); - - it('implements Repository.replaceById()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note = await repo.create({title: 't3', content: 'c3'}); - await repo.replaceById(note.id, {title: 't4', content: undefined}); - const result = await repo.findById(note.id); - expect(result.toJSON()).to.eql({ - id: note.id, - title: 't4', - content: undefined, - }); - }); - - it('throws EntityNotFound error when replacing an unknown id', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - await expect(repo.replaceById(9999, {title: 't4'})).to.be.rejectedWith( - EntityNotFoundError, + expect(repo.deleteById).to.equal( + DefaultCrudRepository.prototype.deleteById, ); - }); - - it('implements Repository.exists()', async () => { - const repo = new DefaultAtomicCrudRepository(Note, ds); - const note1 = await repo.create({title: 't3', content: 'c3'}); - const ok = await repo.exists(note1.id); - expect(ok).to.be.true(); + expect(repo.update).to.equal(DefaultCrudRepository.prototype.update); + expect(repo.updateAll).to.equal( + DefaultCrudRepository.prototype.updateAll, + ); + expect(repo.updateById).to.equal( + DefaultCrudRepository.prototype.updateById, + ); + expect(repo.count).to.equal(DefaultCrudRepository.prototype.count); + expect(repo.save).to.equal(DefaultCrudRepository.prototype.save); + expect(repo.replaceById).to.equal( + DefaultCrudRepository.prototype.replaceById, + ); + expect(repo.exists).to.equal(DefaultCrudRepository.prototype.exists); }); });