Skip to content

Commit

Permalink
fixup! apply feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
biniam committed Nov 22, 2018
1 parent 608438e commit d3cce33
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 236 deletions.
28 changes: 17 additions & 11 deletions packages/repository/src/repositories/atomic.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import {
import * as assert from 'assert';
import * as legacy from 'loopback-datasource-juggler';

export interface FindOrCreateResult<T extends Entity> {
entity: T;
found: boolean;
}

export interface AtomicCrudRepository<T extends Entity, ID>
extends EntityCrudRepository<T, ID> {
/**
Expand All @@ -30,7 +35,7 @@ export interface AtomicCrudRepository<T extends Entity, ID>
filter: Filter<T>,
entity: DataObject<T>,
options?: Options,
): Promise<[T, boolean]>;
): FindOrCreateResult<T>;
}

export class DefaultAtomicCrudRepository<T extends Entity, ID>
Expand All @@ -53,17 +58,18 @@ export class DefaultAtomicCrudRepository<T extends Entity, ID>
filter: Filter<T>,
entity: DataObject<T>,
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<FindOrCreateResult<T>> {
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]};
}
}
252 changes: 27 additions & 225 deletions packages/repository/test/unit/repositories/atomic.repository.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
juggler,
ModelDefinition,
DefaultAtomicCrudRepository,
DefaultCrudRepository,
} from '../../..';

describe('AtomicCrudRepository', () => {
Expand Down Expand Up @@ -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',
Expand All @@ -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);
});
});

Expand Down

0 comments on commit d3cce33

Please sign in to comment.