From def69c1b74dea69cdb011a2f086ea20bbf98f322 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 16 Aug 2019 16:46:54 -0400 Subject: [PATCH] fixup! merge and add hasOne and repository acceptance --- .../belongs-to.relation.acceptance.ts | 23 +- ...has-many-without-di.relation.acceptance.ts | 79 ++-- .../has-many.relation.acceptance.ts | 40 +- .../acceptance/has-one.relation.acceptance.ts | 430 +++++++++--------- .../acceptance/repository.acceptance.ts | 223 +++++---- .../fixtures/models/address.model.ts | 16 +- .../fixtures/models/customer.model.ts | 6 +- .../relations/fixtures/models/order.model.ts | 6 +- .../fixtures/models/product.model.ts | 7 +- .../fixtures/models/shipment.model.ts | 6 +- .../repositories/address.repository.ts | 2 +- .../repositories/customer.repository.ts | 13 +- .../fixtures/repositories/order.repository.ts | 12 +- .../repository-tests/src/relations/helpers.ts | 30 ++ 14 files changed, 491 insertions(+), 402 deletions(-) create mode 100644 packages/repository-tests/src/relations/helpers.ts diff --git a/packages/repository-tests/src/relations/acceptance/belongs-to.relation.acceptance.ts b/packages/repository-tests/src/relations/acceptance/belongs-to.relation.acceptance.ts index 67af1768e227..226490c833a5 100644 --- a/packages/repository-tests/src/relations/acceptance/belongs-to.relation.acceptance.ts +++ b/packages/repository-tests/src/relations/acceptance/belongs-to.relation.acceptance.ts @@ -3,7 +3,6 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {juggler} from '@loopback/repository'; import {expect} from '@loopback/testlab'; import { deleteAllModelsInDefaultDataSource, @@ -21,6 +20,7 @@ import { OrderRepository, ShipmentRepository, } from '../fixtures/repositories'; +import {givenBoundCrudRepositories} from '../helpers'; export function belongsToRelationAcceptance( dataSourceOptions: DataSourceOptions, @@ -46,10 +46,11 @@ export function belongsToRelationAcceptance( } }); - await givenBoundCrudRepositories(ctx.dataSource); - await ctx.dataSource.automigrate(Customer.name); - await ctx.dataSource.automigrate(Order.name); - await ctx.dataSource.automigrate(Shipment.name); + ({customerRepo, orderRepo, shipmentRepo} = givenBoundCrudRepositories( + ctx.dataSource, + )); + + await ctx.dataSource.automigrate(models.map(m => m.name)); }), ); @@ -83,17 +84,5 @@ export function belongsToRelationAcceptance( const result = await orderRepo.shipment(order.id); expect(result).to.deepEqual(shipment); }); - - //--- HELPERS ---// - - async function givenBoundCrudRepositories(db: juggler.DataSource) { - orderRepo = new OrderRepository( - db, - async () => customerRepo, - async () => shipmentRepo, - ); - customerRepo = new repositoryClass(Customer, db) as CustomerRepository; - shipmentRepo = new repositoryClass(Shipment, db) as ShipmentRepository; - } }); } diff --git a/packages/repository-tests/src/relations/acceptance/has-many-without-di.relation.acceptance.ts b/packages/repository-tests/src/relations/acceptance/has-many-without-di.relation.acceptance.ts index a7f5fab3ec8a..28f4a6bbcf06 100644 --- a/packages/repository-tests/src/relations/acceptance/has-many-without-di.relation.acceptance.ts +++ b/packages/repository-tests/src/relations/acceptance/has-many-without-di.relation.acceptance.ts @@ -34,99 +34,88 @@ export function hasManyWithoutDIRelationAcceptance( describe('HasMany relation without di (acceptance)', () => { before(deleteAllModelsInDefaultDataSource); // Given a Customer and Order models - see definitions at the bottom - - // let existingCustomerId: number; + let existingCustomerId: string; let ds: juggler.DataSource; let customerRepo: CustomerRepository; let orderRepo: OrderRepository; before( withCrudCtx(async function setupRepository(ctx: CrudTestContext) { - givenDataSource(ctx.dataSource); + ds = ctx.dataSource; givenOrderRepository(); givenCustomerRepository(); - await ctx.dataSource.automigrate(Customer.name); - await ctx.dataSource.automigrate(Order.name); + await ctx.dataSource.automigrate([Customer.name, Order.name]); }), ); - // before(givenDataSource(ctx.dataSource)); - // before(givenOrderRepository); - // before(givenCustomerRepository); beforeEach(async () => { await orderRepo.deleteAll(); - //existingCustomerId = (await givenPersistedCustomerInstance()).id; + existingCustomerId = (await givenPersistedCustomerInstance()).id; }); - it('can create an instance of the related model (acceptance)', async () => { + it('can create an instance of the related model', async () => { async function createCustomerOrders( - customerId: number, + customerId: string, orderData: Partial, ): Promise { return customerRepo.orders(customerId).create(orderData); } - const customerA = await customerRepo.create({name: 'customer A'}); - const order = await createCustomerOrders(customerA.id, { - description: 'order 1', - }); - expect({ - customerId: order.id.valueOf(), - description: order.description, - }).to.eql({ - customerId: customerA.id, + const order = await createCustomerOrders(existingCustomerId, { description: 'order 1', }); + // do this to avoid type problems of BSON type of mongodb + expect(order.customerId.toString()).eql(existingCustomerId.toString()); + expect(order.description).to.eql('order 1'); const persisted = await orderRepo.findById(order.id); - console.log(persisted); + expect(persisted.toObject()).to.deepEqual(order.toObject()); }); it('can find instances of the related model (acceptance)', async () => { async function createCustomerOrders( - customerId: number, + customerId: string, orderData: Partial, ): Promise { return customerRepo.orders(customerId).create(orderData); } - async function findCustomerOrders(customerId: number) { + async function findCustomerOrders(customerId: string) { return customerRepo.orders(customerId).find(); } - const customerB = await customerRepo.create({name: 'customer B'}); - const customerC = await customerRepo.create({name: 'customer c'}); - const order = await createCustomerOrders(customerB.id, { + const order = await createCustomerOrders(existingCustomerId, { description: 'order 1', }); - console.log(typeof customerB.id); - console.log(customerB); - console.log(typeof order.id); - console.log(order); - const notMyOrder = await createCustomerOrders(customerC.id, { + const notMyOrder = await createCustomerOrders(existingCustomerId + 1, { description: 'order 2', }); - const orders = await findCustomerOrders(customerB.id); + const orders = await findCustomerOrders(existingCustomerId); expect(orders).to.containEql(order); expect(orders).to.not.containEql(notMyOrder); const persisted = await orderRepo.find({ - where: {customerId: customerB.id}, + where: {customerId: existingCustomerId}, }); expect(persisted).to.deepEqual(orders); }); //--- HELPERS ---// - @model() + // use strictObjectIDCoercion here to make sure mongo's happy + @model({ + settings: { + strictObjectIDCoercion: true, + }, + }) class Order extends Entity { @property({ type: features.idType, id: true, generated: true, }) - id: number; + id: string; @property({ type: 'string', @@ -138,17 +127,21 @@ export function hasManyWithoutDIRelationAcceptance( type: features.idType, required: true, }) - customerId: number; + customerId: string; } - @model() + @model({ + settings: { + strictObjectIDCoercion: true, + }, + }) class Customer extends Entity { @property({ type: features.idType, id: true, generated: true, }) - id: number; + id: string; @property({ type: 'string', @@ -191,10 +184,6 @@ export function hasManyWithoutDIRelationAcceptance( } } - function givenDataSource(db: juggler.DataSource) { - ds = db; - } - function givenOrderRepository() { orderRepo = new OrderRepository(ds); } @@ -203,8 +192,8 @@ export function hasManyWithoutDIRelationAcceptance( customerRepo = new CustomerRepository(ds, Getter.fromValue(orderRepo)); } - // async function givenPersistedCustomerInstance() { - // return customerRepo.create({name: 'a customer'}); - // } + async function givenPersistedCustomerInstance() { + return customerRepo.create({name: 'a customer'}); + } }); } diff --git a/packages/repository-tests/src/relations/acceptance/has-many.relation.acceptance.ts b/packages/repository-tests/src/relations/acceptance/has-many.relation.acceptance.ts index 083caf207be3..fe4861f1533e 100644 --- a/packages/repository-tests/src/relations/acceptance/has-many.relation.acceptance.ts +++ b/packages/repository-tests/src/relations/acceptance/has-many.relation.acceptance.ts @@ -3,7 +3,6 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {juggler} from '@loopback/repository'; import {expect} from '@loopback/testlab'; import * as _ from 'lodash'; import { @@ -18,6 +17,7 @@ import { } from '../../helpers.repository-tests'; import {Customer, Order} from '../fixtures/models'; import {CustomerRepository, OrderRepository} from '../fixtures/repositories'; +import {givenBoundCrudRepositories} from '../helpers'; export function hasManyRelationAcceptance( dataSourceOptions: DataSourceOptions, @@ -35,8 +35,12 @@ export function hasManyRelationAcceptance( before( withCrudCtx(async function setupRepository(ctx: CrudTestContext) { + ({customerRepo, orderRepo} = givenBoundCrudRepositories( + ctx.dataSource, + )); + const models = [Customer, Order]; - models.forEach(model => { + models.forEach(async model => { model.definition.properties.id.type = features.idType; if (model === Order) { model.definition.properties.customerId.type = features.idType; @@ -44,9 +48,7 @@ export function hasManyRelationAcceptance( } }); - await givenBoundCrudRepositoriesForCustomerAndOrder(ctx.dataSource); - await ctx.dataSource.automigrate(Customer.name); - await ctx.dataSource.automigrate(Order.name); + await ctx.dataSource.automigrate(models.map(m => m.name)); }), ); @@ -58,14 +60,16 @@ export function hasManyRelationAcceptance( existingCustomerId = (await givenPersistedCustomerInstance()).id; }); - it.only('can create an instance of the related model', async () => { + it('can create an instance of the related model', async () => { const order = await customerRepo.orders(existingCustomerId).create({ description: 'order 1', + // eslint-disable-next-line @typescript-eslint/camelcase + shipment_id: 1, }); - expect(order.toObject()).to.containDeep({ - customerId: existingCustomerId, - description: 'order 1', - }); + + // do this to avoid type problems of BSON type of mongodb + expect(order.customerId.toString()).to.eql(existingCustomerId.toString()); + expect(order.description).to.eql('order 1'); const persisted = await orderRepo.findById(order.id); expect(persisted.toObject()).to.deepEqual(order.toObject()); @@ -74,9 +78,13 @@ export function hasManyRelationAcceptance( it('can find instances of the related model', async () => { const order = await createCustomerOrders(existingCustomerId, { description: 'order 1', + // eslint-disable-next-line @typescript-eslint/camelcase + shipment_id: 1, }); const notMyOrder = await createCustomerOrders(existingCustomerId + 1, { description: 'order 2', + // eslint-disable-next-line @typescript-eslint/camelcase + shipment_id: 1, }); const foundOrders = await findCustomerOrders(existingCustomerId); expect(foundOrders).to.containEql(order); @@ -107,6 +115,11 @@ export function hasManyRelationAcceptance( await findCustomerOrders(existingCustomerId), d => _.pick(d, ['customerId', 'description', 'isShipped']), ); + // convert the id type for mongo + if (features.idType === 'string') { + // eslint-disable-next-line require-atomic-updates + existingCustomerId = existingCustomerId.toString(); + } expect(patchedData).to.eql([ { customerId: existingCustomerId, @@ -237,13 +250,6 @@ export function hasManyRelationAcceptance( return customerRepo.customers(customerId).find(); } - async function givenBoundCrudRepositoriesForCustomerAndOrder( - db: juggler.DataSource, - ) { - customerRepo = new CustomerRepository(db, async () => orderRepo); - orderRepo = new OrderRepository(db, async () => customerRepo); - } - async function givenPersistedCustomerInstance() { return customerRepo.create({name: 'a customer'}); } diff --git a/packages/repository-tests/src/relations/acceptance/has-one.relation.acceptance.ts b/packages/repository-tests/src/relations/acceptance/has-one.relation.acceptance.ts index f3e8f5010e55..40f1cc2b5bdd 100644 --- a/packages/repository-tests/src/relations/acceptance/has-one.relation.acceptance.ts +++ b/packages/repository-tests/src/relations/acceptance/has-one.relation.acceptance.ts @@ -3,259 +3,283 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Application} from '@loopback/core'; -import { - ApplicationWithRepositories, - EntityNotFoundError, - Filter, - juggler, - repository, - RepositoryMixin, -} from '@loopback/repository'; +import {EntityNotFoundError, Filter} from '@loopback/repository'; import {expect, toJSON} from '@loopback/testlab'; -import {Address} from '../fixtures/models'; +import { + CrudFeatures, + CrudRepositoryCtor, + CrudTestContext, + DataSourceOptions, +} from '../..'; +import { + deleteAllModelsInDefaultDataSource, + withCrudCtx, +} from '../../helpers.repository-tests'; +import {Address, Customer} from '../fixtures/models'; import {AddressRepository, CustomerRepository} from '../fixtures/repositories'; +import {givenBoundCrudRepositories} from '../helpers'; + +export function hasOneRelationAcceptance( + dataSourceOptions: DataSourceOptions, + repositoryClass: CrudRepositoryCtor, + features: CrudFeatures, +) { + describe('hasOne relation (acceptance)', () => { + let customerRepo: CustomerRepository; + let addressRepo: AddressRepository; + let existingCustomerId: number; + + before(deleteAllModelsInDefaultDataSource); + + before( + withCrudCtx(async function setupRepository(ctx: CrudTestContext) { + ({customerRepo, addressRepo} = givenBoundCrudRepositories( + ctx.dataSource, + )); + + const models = [Customer, Address]; + + models.forEach(model => { + if (model === Customer) { + model.definition.properties.id.type = features.idType; + model.definition.properties.parentId.type = features.idType; + } + if (model === Address) { + model.definition.properties.id.type = features.idType; + model.definition.properties.customerId.type = features.idType; + } + }); + + await ctx.dataSource.automigrate(models.map(m => m.name)); + }), + ); -describe('hasOne relation', () => { - // Given a Customer and Address models - see definitions at the bottom - - let app: ApplicationWithRepositories; - let controller: CustomerController; - let customerRepo: CustomerRepository; - let addressRepo: AddressRepository; - let existingCustomerId: number; - - before(givenApplicationWithMemoryDB); - before(givenBoundCrudRepositoriesForCustomerAndAddress); - before(givenCustomerController); - - beforeEach(async () => { - await addressRepo.deleteAll(); - existingCustomerId = (await givenPersistedCustomerInstance()).id; - }); - - it('can create an instance of the related model', async () => { - const address = await controller.createCustomerAddress(existingCustomerId, { - street: '123 test avenue', - }); - expect(address.toObject()).to.containDeep({ - customerId: existingCustomerId, - street: '123 test avenue', + beforeEach(async () => { + await addressRepo.deleteAll(); + existingCustomerId = (await givenPersistedCustomerInstance()).id; }); - const persisted = await addressRepo.findById(address.zipcode); - expect(persisted.toObject()).to.deepEqual(address.toObject()); - }); - - // We do not enforce referential integrity at the moment. It is up to - // our users to set up unique constraint(s) between related models at the - // database level - it.skip('refuses to create related model instance twice', async () => { - const address = await controller.createCustomerAddress(existingCustomerId, { - street: '123 test avenue', + it('can create an instance of the related model', async () => { + const address = await createCustomerAddress(existingCustomerId, { + street: '123 test avenue', + }); + expect(address.toObject()).to.containDeep({ + customerId: existingCustomerId, + street: '123 test avenue', + }); + + const persisted = await addressRepo.findById(address.id); + expect(persisted.toObject()).to.deepEqual(address.toObject()); }); - await expect( - controller.createCustomerAddress(existingCustomerId, { - street: '456 test street', - }), - ).to.be.rejectedWith(/Duplicate entry for Address.customerId/); - expect(address.toObject()).to.containDeep({ - customerId: existingCustomerId, - street: '123 test avenue', - }); - - const persisted = await addressRepo.findById(address.zipcode); - expect(persisted.toObject()).to.deepEqual(address.toObject()); - }); - it('can find instance of the related model', async () => { - const address = await controller.createCustomerAddress(existingCustomerId, { - street: '123 test avenue', + // We do not enforce referential integrity at the moment. It is up to + // our users to set up unique constraint(s) between related models at the + // database level + it.skip('refuses to create related model instance twice', async () => { + const address = await createCustomerAddress(existingCustomerId, { + street: '123 test avenue', + }); + await expect( + createCustomerAddress(existingCustomerId, { + street: '456 test street', + }), + ).to.be.rejectedWith(/Duplicate entry for Address.customerId/); + expect(address.toObject()).to.containDeep({ + customerId: existingCustomerId, + street: '123 test avenue', + }); + + const persisted = await addressRepo.findById(address.id); + expect(persisted.toObject()).to.deepEqual(address.toObject()); }); - const foundAddress = await controller.findCustomerAddress( - existingCustomerId, - ); - expect(foundAddress).to.containEql(address); - expect(toJSON(foundAddress)).to.deepEqual(toJSON(address)); - const persisted = await addressRepo.find({ - where: {customerId: existingCustomerId}, + it('can find instance of the related model', async () => { + const address = await createCustomerAddress(existingCustomerId, { + street: '123 test avenue', + }); + const foundAddress = await findCustomerAddress(existingCustomerId); + expect(foundAddress).to.containEql(address); + expect(toJSON(foundAddress)).to.deepEqual(toJSON(address)); + + const persisted = await addressRepo.find({ + where: {customerId: existingCustomerId}, + }); + expect(persisted[0]).to.deepEqual(foundAddress); }); - expect(persisted[0]).to.deepEqual(foundAddress); - }); - - // FIXME(b-admike): make sure the test fails with compiler error - it.skip('ignores where filter to find related model instance', async () => { - const foundAddress = await controller.findCustomerAddressWithFilter( - existingCustomerId, - // the compiler should complain that the where field is - // not accepted in the filter object for the get() method - // if the following line is uncommented - { - where: {street: '456 test road'}, - }, - ); - const persisted = await addressRepo.find({ - where: {customerId: existingCustomerId}, + // FIXME(b-admike): make sure the test fails with compiler error + it.skip('ignores where filter to find related model instance', async () => { + const foundAddress = await findCustomerAddressWithFilter( + existingCustomerId, + // the compiler should complain that the where field is + // not accepted in the filter object for the get() method + // if the following line is uncommented + { + where: {street: '456 test road'}, + }, + ); + + const persisted = await addressRepo.find({ + where: {customerId: existingCustomerId}, + }); + // TODO: make sure this test fails when where condition is supplied + // compiler should have errored out (?) + expect(persisted[0]).to.deepEqual(foundAddress); }); - // TODO: make sure this test fails when where condition is supplied - // compiler should have errored out (?) - expect(persisted[0]).to.deepEqual(foundAddress); - }); - it('reports EntityNotFound error when related model is deleted', async () => { - const address = await controller.createCustomerAddress(existingCustomerId, { - street: '123 test avenue', - }); - await addressRepo.deleteById(address.zipcode); + it('reports EntityNotFound error when related model is deleted', async () => { + const address = await createCustomerAddress(existingCustomerId, { + street: '123 test avenue', + }); + await addressRepo.deleteById(address.id); - await expect( - controller.findCustomerAddress(existingCustomerId), - ).to.be.rejectedWith(EntityNotFoundError); - }); + await expect(findCustomerAddress(existingCustomerId)).to.be.rejectedWith( + EntityNotFoundError, + ); + }); - it('can PATCH hasOne instances', async () => { - const address = await controller.createCustomerAddress(existingCustomerId, { - street: '1 Amedee Bonnet', - zipcode: '69740', - city: 'Genas', - province: 'Rhone', + it('can PATCH hasOne instances', async () => { + const address = await createCustomerAddress(existingCustomerId, { + street: '1 Amedee Bonnet', + zipcode: '69740', + city: 'Genas', + province: 'Rhone', + }); + + const patchObject = {city: 'Lyon-Genas'}; + const arePatched = await patchCustomerAddress( + existingCustomerId, + patchObject, + ); + + expect(arePatched).to.deepEqual({count: 1}); + const patchedData = await addressRepo.findById(address.id); + expect(toJSON(patchedData)).to.deepEqual({ + id: address.id, + customerId: existingCustomerId, + street: '1 Amedee Bonnet', + zipcode: '69740', + city: 'Lyon-Genas', + province: 'Rhone', + }); }); - const patchObject = {city: 'Lyon-Genas'}; - const arePatched = await controller.patchCustomerAddress( - existingCustomerId, - patchObject, - ); + it('patches the related instance only', async () => { + const bob = await customerRepo.create({name: 'Bob'}); + await customerRepo.address(bob.id).create({city: 'Paris'}); - expect(arePatched).to.deepEqual({count: 1}); - const patchedData = await addressRepo.findById(address.zipcode); - expect(toJSON(patchedData)).to.deepEqual({ - customerId: existingCustomerId, - street: '1 Amedee Bonnet', - zipcode: '69740', - city: 'Lyon-Genas', - province: 'Rhone', - }); - }); + const alice = await customerRepo.create({name: 'Alice'}); + await customerRepo.address(alice.id).create({city: 'London'}); - it('patches the related instance only', async () => { - const bob = await customerRepo.create({name: 'Bob'}); - await customerRepo.address(bob.id).create({city: 'Paris'}); + const result = await patchCustomerAddress(alice.id, { + city: 'New York', + }); - const alice = await customerRepo.create({name: 'Alice'}); - await customerRepo.address(alice.id).create({city: 'London'}); + expect(result).to.deepEqual({count: 1}); - const result = await controller.patchCustomerAddress(alice.id, { - city: 'New York', + const found = await customerRepo.address(bob.id).get(); + expect(toJSON(found)).to.containDeep({city: 'Paris'}); }); - expect(result).to.deepEqual({count: 1}); + it('throws an error when PATCH tries to change the foreignKey', async () => { + await expect( + patchCustomerAddress(existingCustomerId, { + customerId: existingCustomerId + 1, + }), + ).to.be.rejectedWith(/Property "customerId" cannot be changed!/); + }); - const found = await customerRepo.address(bob.id).get(); - expect(toJSON(found)).to.containDeep({city: 'Paris'}); - }); + it('can DELETE hasOne relation instances', async () => { + await createCustomerAddress(existingCustomerId, { + street: '1 Amedee Bonnet', + zipcode: '69740', + city: 'Genas', + province: 'Rhone', + }); - it('throws an error when PATCH tries to change the foreignKey', async () => { - await expect( - controller.patchCustomerAddress(existingCustomerId, { - customerId: existingCustomerId + 1, - }), - ).to.be.rejectedWith(/Property "customerId" cannot be changed!/); - }); + const areDeleted = await deleteCustomerAddress(existingCustomerId); + expect(areDeleted).to.deepEqual({count: 1}); - it('can DELETE hasOne relation instances', async () => { - await controller.createCustomerAddress(existingCustomerId, { - street: '1 Amedee Bonnet', - zipcode: '69740', - city: 'Genas', - province: 'Rhone', + await expect(findCustomerAddress(existingCustomerId)).to.be.rejectedWith( + EntityNotFoundError, + ); }); - const areDeleted = await controller.deleteCustomerAddress( - existingCustomerId, - ); - expect(areDeleted).to.deepEqual({count: 1}); - - await expect( - controller.findCustomerAddress(existingCustomerId), - ).to.be.rejectedWith(EntityNotFoundError); - }); - - it('deletes the related model instance only', async () => { - const bob = await customerRepo.create({name: 'Bob'}); - await customerRepo.address(bob.id).create({city: 'Paris'}); + it('deletes the related model instance only', async () => { + const bob = await customerRepo.create({name: 'Bob'}); + await customerRepo.address(bob.id).create({city: 'Paris'}); - const alice = await customerRepo.create({name: 'Alice'}); - await customerRepo.address(alice.id).create({city: 'London'}); + const alice = await customerRepo.create({name: 'Alice'}); + await customerRepo.address(alice.id).create({city: 'London'}); - const result = await controller.deleteCustomerAddress(alice.id); + const result = await deleteCustomerAddress(alice.id); - expect(result).to.deepEqual({count: 1}); + expect(result).to.deepEqual({count: 1}); - const found = await addressRepo.find(); - expect(found).to.have.length(1); - }); + const found = await addressRepo.find(); + expect(found).to.have.length(1); + }); - /*---------------- HELPERS -----------------*/ + /*---------------- HELPERS -----------------*/ - class CustomerController { - constructor( - @repository(CustomerRepository) - protected customerRepository: CustomerRepository, - ) {} + // class CustomerController { + // constructor( + // @repository(CustomerRepository) + // protected customerRepository: CustomerRepository, + // ) {} - async createCustomerAddress( + async function createCustomerAddress( customerId: number, addressData: Partial
, ): Promise
{ - return this.customerRepository.address(customerId).create(addressData); + return customerRepo.address(customerId).create(addressData); } - async findCustomerAddress(customerId: number) { - return this.customerRepository.address(customerId).get(); + async function findCustomerAddress(customerId: number) { + return customerRepo.address(customerId).get(); } - async findCustomerAddressWithFilter( + async function findCustomerAddressWithFilter( customerId: number, filter: Filter
, ) { - return this.customerRepository.address(customerId).get(filter); + return customerRepo.address(customerId).get(filter); } - async patchCustomerAddress( + async function patchCustomerAddress( customerId: number, addressData: Partial
, ) { - return this.customerRepository.address(customerId).patch(addressData); + return customerRepo.address(customerId).patch(addressData); } - async deleteCustomerAddress(customerId: number) { - return this.customerRepository.address(customerId).delete(); + async function deleteCustomerAddress(customerId: number) { + return customerRepo.address(customerId).delete(); } - } - - function givenApplicationWithMemoryDB() { - class TestApp extends RepositoryMixin(Application) {} - app = new TestApp(); - app.dataSource(new juggler.DataSource({name: 'db', connector: 'memory'})); - } - - async function givenBoundCrudRepositoriesForCustomerAndAddress() { - app.repository(CustomerRepository); - app.repository(AddressRepository); - customerRepo = await app.getRepository(CustomerRepository); - addressRepo = await app.getRepository(AddressRepository); - } - - async function givenCustomerController() { - app.controller(CustomerController); - controller = await app.get( - 'controllers.CustomerController', - ); - } - async function givenPersistedCustomerInstance() { - return customerRepo.create({name: 'a customer'}); - } -}); + // function givenApplicationWithGivenDB() { + // class TestApp extends RepositoryMixin(Application) {} + // app = new TestApp(); + // app.dataSource(new DS()); + // app.dataSource(new juggler.DataSource({name: 'db', connector: 'memory'})); + // } + + // async function sForCustomerAndAddress() { + // customerRepo = new CustomerRepository(ds); + // app.repository(CustomerRepository); + // app.repository(AddressRepository); + // customerRepo = await app.getRepository(CustomerRepository); + // addressRepo = await app.getRepository(AddressRepository); + // } + + // async function givenCustomerController() { + // app.controller(CustomerController); + // controller = await app.get( + // 'controllers.CustomerController', + // ); + // } + + async function givenPersistedCustomerInstance() { + return customerRepo.create({name: 'a customer'}); + } + }); +} diff --git a/packages/repository-tests/src/relations/acceptance/repository.acceptance.ts b/packages/repository-tests/src/relations/acceptance/repository.acceptance.ts index 1bafbd6d5e90..8f0a91f67fb6 100644 --- a/packages/repository-tests/src/relations/acceptance/repository.acceptance.ts +++ b/packages/repository-tests/src/relations/acceptance/repository.acceptance.ts @@ -10,119 +10,144 @@ import { model, property, } from '@loopback/repository'; +import {juggler} from '@loopback/repository/src'; import {expect} from '@loopback/testlab'; -import {DataSource} from 'loopback-datasource-juggler'; +import { + CrudFeatures, + CrudRepositoryCtor, + CrudTestContext, + DataSourceOptions, +} from '../..'; +import { + deleteAllModelsInDefaultDataSource, + withCrudCtx, +} from '../../helpers.repository-tests'; import {Product} from '../fixtures/models/product.model'; import {ProductRepository} from '../fixtures/repositories/product.repository'; -// This test shows the recommended way how to use @loopback/repository -// together with existing connectors when building LoopBack applications -describe('Repository in Thinking in LoopBack', () => { - let repo: ProductRepository; - beforeEach(givenProductRepository); - - it('counts models in empty database', async () => { - expect(await repo.count()).to.deepEqual({count: 0}); - }); - - it('creates a new model', async () => { - const p: Product = await repo.create({name: 'Ink Pen', slug: 'pen'}); - expect(p).instanceof(Product); - expect.exists(p.id); - }); - - it('can save a model', async () => { - const p = await repo.create({slug: 'pencil'}); +export function hasOneRelationAcceptance( + dataSourceOptions: DataSourceOptions, + repositoryClass: CrudRepositoryCtor, + features: CrudFeatures, +) { + // This test shows the recommended way how to use @loopback/repository + // together with existing connectors when building LoopBack applications + describe('Repository in Thinking in LoopBack', () => { + let repo: ProductRepository; + let db: juggler.DataSource; + + before(deleteAllModelsInDefaultDataSource); + + before( + withCrudCtx(async function setupRepository(ctx: CrudTestContext) { + db = ctx.dataSource; + Product.definition.properties.id.type = features.idType; + givenProductRepository(); + await ctx.dataSource.automigrate(Product.name); + }), + ); - p.name = 'Red Pencil'; - await repo.save(p); + beforeEach(() => repo.deleteAll()); - await repo.findById(p.id); - expect(p).to.have.properties({ - slug: 'pencil', - name: 'Red Pencil', + it('counts models in empty database', async () => { + expect(await repo.count()).to.deepEqual({count: 0}); }); - }); - - it('rejects extra model properties (defaults to strict mode)', async () => { - await expect( - repo.create({name: 'custom', extra: 'additional-data'} as AnyObject), - ).to.be.rejectedWith(/extra.*not defined/); - }); - it('allows models to allow additional properties', async () => { - // TODO(bajtos) Add syntactic sugar to allow the following usage: - // @model({strict: false}) - @model({settings: {strict: false}}) - class Flexible extends Entity { - @property({id: true}) - id: number; - } + it('creates a new model', async () => { + const p: Product = await repo.create({name: 'Ink Pen', slug: 'pen'}); + expect(p).instanceof(Product); + expect.exists(p.id); + }); - const flexibleRepo = new DefaultCrudRepository< - Flexible, - typeof Flexible.prototype.id - >(Flexible, new DataSource({connector: 'memory'})); + it('can save a model', async () => { + const p = await repo.create({slug: 'pencil'}); - const created = await flexibleRepo.create({ - extra: 'additional data', - } as AnyObject); - const stored = await flexibleRepo.findById(created.id); - expect(stored).to.containDeep({extra: 'additional data'}); - }); + p.name = 'Red Pencil'; + await repo.save(p); - it('allows models to allow nested model properties', async () => { - @model() - class Role extends Entity { - @property() - name: string; - } - - @model() - class Address extends Entity { - @property() - street: string; - } + await repo.findById(p.id); + expect(p).to.have.properties({ + slug: 'pencil', + name: 'Red Pencil', + }); + }); - @model() - class User extends Entity { - @property({ - type: 'number', - id: true, - }) - id: number; + it('rejects extra model properties (defaults to strict mode)', async () => { + await expect( + repo.create({name: 'custom', extra: 'additional-data'} as AnyObject), + ).to.be.rejectedWith(/extra.*not defined/); + }); - @property({type: 'string'}) - name: string; + it('allows models to allow additional properties', async () => { + // TODO(bajtos) Add syntactic sugar to allow the following usage: + // @model({strict: false}) + @model({settings: {strict: false}}) + class Flexible extends Entity { + @property({id: true}) + id: number; + } + + const flexibleRepo = new DefaultCrudRepository< + Flexible, + typeof Flexible.prototype.id + >(Flexible, db); + + const created = await flexibleRepo.create({ + extra: 'additional data', + } as AnyObject); + const stored = await flexibleRepo.findById(created.id); + expect(stored).to.containDeep({extra: 'additional data'}); + }); - @property.array(Role) - roles: Role[]; + it('allows models to allow nested model properties', async () => { + @model() + class Role extends Entity { + @property() + name: string; + } + + @model() + class Address extends Entity { + @property() + street: string; + } + + @model() + class User extends Entity { + @property({ + type: 'number', + id: true, + }) + id: number; + + @property({type: 'string'}) + name: string; + + @property.array(Role) + roles: Role[]; + + @property() + address: Address; + } + + const userRepo = new DefaultCrudRepository< + User, + typeof User.prototype.id + >(User, db); + + const user = { + name: 'foo', + roles: [{name: 'admin'}, {name: 'user'}], + address: {street: 'backstreet'}, + }; + const created = await userRepo.create(user); + + const stored = await userRepo.findById(created.id); + expect(stored).to.containDeep(user); + }); - @property() - address: Address; + function givenProductRepository() { + repo = new ProductRepository(db); } - - const userRepo = new DefaultCrudRepository( - User, - new DataSource({connector: 'memory'}), - ); - - const user = { - id: 1, - name: 'foo', - roles: [{name: 'admin'}, {name: 'user'}], - address: {street: 'backstreet'}, - }; - const created = await userRepo.create(user); - - const stored = await userRepo.findById(created.id); - expect(stored).to.containDeep(user); }); - - function givenProductRepository() { - const db = new DataSource({ - connector: 'memory', - }); - repo = new ProductRepository(db); - } -}); +} diff --git a/packages/repository-tests/src/relations/fixtures/models/address.model.ts b/packages/repository-tests/src/relations/fixtures/models/address.model.ts index ae7025a8296d..b758d551de16 100644 --- a/packages/repository-tests/src/relations/fixtures/models/address.model.ts +++ b/packages/repository-tests/src/relations/fixtures/models/address.model.ts @@ -6,23 +6,35 @@ import {belongsTo, Entity, model, property} from '@loopback/repository'; import {Customer, CustomerWithRelations} from './customer.model'; -@model() +@model({ + settings: { + strictObjectIDCoercion: true, + }, +}) export class Address extends Entity { + @property({ + type: 'string', + id: true, + generated: true, + }) + id: string; @property({ type: 'string', }) street: string; @property({ type: 'string', - id: true, + default: '12345', }) zipcode: string; @property({ type: 'string', + default: 'Toronto', }) city: string; @property({ type: 'string', + default: 'Ontario', }) province: string; diff --git a/packages/repository-tests/src/relations/fixtures/models/customer.model.ts b/packages/repository-tests/src/relations/fixtures/models/customer.model.ts index b6cb472fb2e8..5817fabdbb73 100644 --- a/packages/repository-tests/src/relations/fixtures/models/customer.model.ts +++ b/packages/repository-tests/src/relations/fixtures/models/customer.model.ts @@ -14,7 +14,11 @@ import { import {Address, AddressWithRelations} from './address.model'; import {Order, OrderWithRelations} from './order.model'; -@model() +@model({ + settings: { + strictObjectIDCoercion: true, + }, +}) export class Customer extends Entity { @property({ type: 'number', diff --git a/packages/repository-tests/src/relations/fixtures/models/order.model.ts b/packages/repository-tests/src/relations/fixtures/models/order.model.ts index ef88ebf3d5dc..27c08c267f15 100644 --- a/packages/repository-tests/src/relations/fixtures/models/order.model.ts +++ b/packages/repository-tests/src/relations/fixtures/models/order.model.ts @@ -7,7 +7,11 @@ import {belongsTo, Entity, model, property} from '@loopback/repository'; import {Customer, CustomerWithRelations} from './customer.model'; import {Shipment, ShipmentWithRelations} from './shipment.model'; -@model() +@model({ + settings: { + strictObjectIDCoercion: true, + }, +}) export class Order extends Entity { @property({ type: 'string', diff --git a/packages/repository-tests/src/relations/fixtures/models/product.model.ts b/packages/repository-tests/src/relations/fixtures/models/product.model.ts index 6211d856af0f..1b48e94060a9 100644 --- a/packages/repository-tests/src/relations/fixtures/models/product.model.ts +++ b/packages/repository-tests/src/relations/fixtures/models/product.model.ts @@ -5,11 +5,16 @@ import {Entity, model, property} from '@loopback/repository'; -@model() +@model({ + settings: { + strictObjectIDCoercion: true, + }, +}) export class Product extends Entity { @property({ type: 'number', id: true, + generated: true, description: 'The unique identifier for a product', }) id: number; diff --git a/packages/repository-tests/src/relations/fixtures/models/shipment.model.ts b/packages/repository-tests/src/relations/fixtures/models/shipment.model.ts index cb45950040b8..c3260899d492 100644 --- a/packages/repository-tests/src/relations/fixtures/models/shipment.model.ts +++ b/packages/repository-tests/src/relations/fixtures/models/shipment.model.ts @@ -6,7 +6,11 @@ import {Entity, hasMany, model, property} from '@loopback/repository'; import {Order, OrderWithRelations} from './order.model'; -@model() +@model({ + settings: { + strictObjectIDCoercion: true, + }, +}) export class Shipment extends Entity { @property({ type: 'number', diff --git a/packages/repository-tests/src/relations/fixtures/repositories/address.repository.ts b/packages/repository-tests/src/relations/fixtures/repositories/address.repository.ts index d2333880743c..59230dcbb119 100644 --- a/packages/repository-tests/src/relations/fixtures/repositories/address.repository.ts +++ b/packages/repository-tests/src/relations/fixtures/repositories/address.repository.ts @@ -15,7 +15,7 @@ import {CustomerRepository} from '../repositories'; export class AddressRepository extends DefaultCrudRepository< Address, - typeof Address.prototype.zipcode, + typeof Address.prototype.id, AddressRelations > { public readonly customer: BelongsToAccessor< diff --git a/packages/repository-tests/src/relations/fixtures/repositories/customer.repository.ts b/packages/repository-tests/src/relations/fixtures/repositories/customer.repository.ts index 596f2287aa1b..31b3a9ecc562 100644 --- a/packages/repository-tests/src/relations/fixtures/repositories/customer.repository.ts +++ b/packages/repository-tests/src/relations/fixtures/repositories/customer.repository.ts @@ -43,19 +43,18 @@ export class CustomerRepository extends DefaultCrudRepository< @repository.getter('OrderRepository') orderRepositoryGetter: Getter, @repository.getter('AddressRepository') - addressRepositoryGetter?: Getter, + addressRepositoryGetter: Getter, ) { super(Customer, db); this.orders = this.createHasManyRepositoryFactoryFor( 'orders', orderRepositoryGetter, ); - if (addressRepositoryGetter) { - this.address = this.createHasOneRepositoryFactoryFor( - 'address', - addressRepositoryGetter, - ); - } + this.address = this.createHasOneRepositoryFactoryFor( + 'address', + addressRepositoryGetter, + ); + this.customers = this.createHasManyRepositoryFactoryFor( 'customers', Getter.fromValue(this), diff --git a/packages/repository-tests/src/relations/fixtures/repositories/order.repository.ts b/packages/repository-tests/src/relations/fixtures/repositories/order.repository.ts index 07a0de2d309b..429e3681bc15 100644 --- a/packages/repository-tests/src/relations/fixtures/repositories/order.repository.ts +++ b/packages/repository-tests/src/relations/fixtures/repositories/order.repository.ts @@ -32,7 +32,7 @@ export class OrderRepository extends DefaultCrudRepository< @repository.getter('CustomerRepository') customerRepositoryGetter: Getter, @repository.getter('ShipmentRepository') - shipmentRepositoryGetter?: Getter, + shipmentRepositoryGetter: Getter, ) { super(Order, db); this.customer = this.createBelongsToAccessorFor( @@ -40,11 +40,9 @@ export class OrderRepository extends DefaultCrudRepository< customerRepositoryGetter, ); - if (shipmentRepositoryGetter) { - this.shipment = this.createBelongsToAccessorFor( - 'shipment', - shipmentRepositoryGetter, - ); - } + this.shipment = this.createBelongsToAccessorFor( + 'shipment', + shipmentRepositoryGetter, + ); } } diff --git a/packages/repository-tests/src/relations/helpers.ts b/packages/repository-tests/src/relations/helpers.ts new file mode 100644 index 000000000000..066b78ca71fc --- /dev/null +++ b/packages/repository-tests/src/relations/helpers.ts @@ -0,0 +1,30 @@ +import {juggler} from '@loopback/repository'; +import { + AddressRepository, + CustomerRepository, + OrderRepository, + ShipmentRepository, +} from './fixtures/repositories'; + +export function givenBoundCrudRepositories(db: juggler.DataSource) { + const customerRepo: CustomerRepository = new CustomerRepository( + db, + async () => orderRepo, + async () => addressRepo, + ); + const orderRepo: OrderRepository = new OrderRepository( + db, + async () => customerRepo, + async () => shipmentRepo, + ); + const shipmentRepo: ShipmentRepository = new ShipmentRepository( + db, + async () => orderRepo, + ); + const addressRepo: AddressRepository = new AddressRepository( + db, + async () => customerRepo, + ); + + return {customerRepo, orderRepo, shipmentRepo, addressRepo}; +}