diff --git a/packages/cli/test/fixtures/relation/index.js b/packages/cli/test/fixtures/relation/index.js index 8f00fa1205dd..5c1f42baf1ed 100644 --- a/packages/cli/test/fixtures/relation/index.js +++ b/packages/cli/test/fixtures/relation/index.js @@ -6,6 +6,97 @@ const CONFIG_PATH = '.'; const DUMMY_CONTENT = '--DUMMY VALUE--'; const fs = require('fs'); +const SourceEntries = { + CustomerModel: { + path: MODEL_APP_PATH, + file: 'customer.model.ts', + content: readSourceFile('./models/customer.model.ts'), + }, + CustomerModelWithOrdersProperty: { + path: MODEL_APP_PATH, + file: 'customer.model.ts', + content: readSourceFile('./models/customer5.model.ts'), + }, + CustomerRepository: { + path: REPOSITORY_APP_PATH, + file: 'customer.repository.ts', + content: readSourceFile('./repositories/customer.repository.ts'), + }, + + CustomerClassModel: { + path: MODEL_APP_PATH, + file: 'customer-class.model.ts', + content: readSourceFile('./models/customer-class.model.ts'), + }, + CustomerClassRepository: { + path: REPOSITORY_APP_PATH, + file: 'customer-class.repository.ts', + content: readSourceFile('./repositories/customer-class.repository.ts'), + }, + + CustomerClassTypeModel: { + path: MODEL_APP_PATH, + file: 'customer-class-type.model.ts', + content: readSourceFile('./models/customer-class-type.model.ts'), + }, + CustomerClassTypeRepository: { + path: REPOSITORY_APP_PATH, + file: 'customer-class-type.repository.ts', + content: readSourceFile('./repositories/customer-class-type.repository.ts'), + }, + + OrderModel: { + path: MODEL_APP_PATH, + file: 'order.model.ts', + content: readSourceFile('./models/order.model.ts'), + }, + OrderModelModelWithCustomerIdProperty: { + path: MODEL_APP_PATH, + file: 'order.model.ts', + content: readSourceFile('./models/order-with-fk.model.ts'), + }, + OrderRepository: { + path: REPOSITORY_APP_PATH, + file: 'order.repository.ts', + content: readSourceFile('./repositories/order.repository.ts'), + }, + + OrderClassModel: { + path: MODEL_APP_PATH, + file: 'order-class.model.ts', + content: readSourceFile('./models/order-class.model.ts'), + }, + OrderClassRepository: { + path: REPOSITORY_APP_PATH, + file: 'order-class.repository.ts', + content: readSourceFile('./repositories/order-class.repository.ts'), + }, + + OrderClassTypeModel: { + path: MODEL_APP_PATH, + file: 'order-class-type.model.ts', + content: readSourceFile('./models/order-class-type.model.ts'), + }, + OrderClassTypeRepository: { + path: REPOSITORY_APP_PATH, + file: 'order-class-type.repository.ts', + content: readSourceFile('./repositories/order-class-type.repository.ts'), + }, + + NoKeyModel: { + path: MODEL_APP_PATH, + file: 'nokey.model.ts', + content: readSourceFile('./models/nokey.model.ts'), + }, + + IndexOfControllers: { + path: CONTROLLER_PATH, + file: 'index.ts', + content: readSourceFile('./controllers/index.ts'), + }, +}; +exports.SourceEntries = SourceEntries; + exports.SANDBOX_FILES = [ { path: CONFIG_PATH, @@ -70,596 +161,67 @@ exports.SANDBOX_FILES = [ file: 'sqlite3.datasource.ts', content: DUMMY_CONTENT, }, - { - path: REPOSITORY_APP_PATH, - file: 'customer.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer-class.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer-class.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order-class.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order-class.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - - { - path: REPOSITORY_APP_PATH, - file: 'customer-class-type.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer-class-type.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order-class-type.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order-class-type.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, + SourceEntries.CustomerRepository, + SourceEntries.OrderRepository, + SourceEntries.CustomerClassRepository, + SourceEntries.OrderClassRepository, + SourceEntries.CustomerClassTypeRepository, + SourceEntries.OrderClassTypeRepository, { path: DATASOURCE_APP_PATH, file: 'restdb.datasource.ts', content: DUMMY_CONTENT, }, - { - path: MODEL_APP_PATH, - file: 'customer.model.ts', - content: fs.readFileSync(require.resolve('./models/customer.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'order.model.ts', - content: fs.readFileSync(require.resolve('./models/order.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'nokey.model.ts', - content: fs.readFileSync(require.resolve('./models/nokey.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'customer-class.model.ts', - content: fs.readFileSync( - require.resolve('./models/customer-class.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: MODEL_APP_PATH, - file: 'order-class.model.ts', - content: fs.readFileSync(require.resolve('./models/order-class.model.ts'), { - encoding: 'utf-8', - }), - }, - - { - path: MODEL_APP_PATH, - file: 'customer-class-type.model.ts', - content: fs.readFileSync( - require.resolve('./models/customer-class-type.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: MODEL_APP_PATH, - file: 'order-class-type.model.ts', - content: fs.readFileSync( - require.resolve('./models/order-class-type.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, + SourceEntries.CustomerModel, + SourceEntries.OrderModel, + SourceEntries.NoKeyModel, + SourceEntries.CustomerClassModel, + SourceEntries.OrderClassModel, + SourceEntries.CustomerClassTypeModel, + SourceEntries.OrderClassTypeModel, ]; -exports.SANDBOX_FILES2 = [ - { - path: CONTROLLER_PATH, - file: 'customer.controller.ts', - content: fs.readFileSync( - require.resolve('./controllers/customer.controller.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer-class.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer-class.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order-class.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order-class.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer-class-type.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer-class-type.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order-class-type.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order-class-type.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: DATASOURCE_APP_PATH, - file: 'restdb.datasource.ts', - content: DUMMY_CONTENT, - }, - { - path: MODEL_APP_PATH, - file: 'customer.model.ts', - content: fs.readFileSync(require.resolve('./models/customer.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'order.model.ts', - content: fs.readFileSync(require.resolve('./models/order.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'nokey.model.ts', - content: fs.readFileSync(require.resolve('./models/nokey.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'customer-class.model.ts', - content: fs.readFileSync( - require.resolve('./models/customer-class.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: MODEL_APP_PATH, - file: 'order-class.model.ts', - content: fs.readFileSync(require.resolve('./models/order-class.model.ts'), { - encoding: 'utf-8', - }), - }, - - { - path: MODEL_APP_PATH, - file: 'customer-class-type.model.ts', - content: fs.readFileSync( - require.resolve('./models/customer-class-type.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: MODEL_APP_PATH, - file: 'order-class-type.model.ts', - content: fs.readFileSync( - require.resolve('./models/order-class-type.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: CONTROLLER_PATH, - file: 'index.ts', - content: fs.readFileSync(require.resolve('./controllers/index.ts'), { - encoding: 'utf-8', - }), - }, -]; - -exports.SANDBOX_FILES3 = []; - -exports.SANDBOX_FILES4 = [ - { - path: CONTROLLER_PATH, - file: 'order-customer.controller.ts', - content: fs.readFileSync( - require.resolve('./controllers/order-customer.controller.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: CONTROLLER_PATH, - file: 'index.ts', - content: fs.readFileSync(require.resolve('./controllers/index4.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'customer.model.ts', - content: fs.readFileSync(require.resolve('./models/customer.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'order.model.ts', - content: fs.readFileSync(require.resolve('./models/order.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, -]; - -exports.SANDBOX_FILES5 = [ - { - path: MODEL_APP_PATH, - file: 'customer.model.ts', - content: fs.readFileSync(require.resolve('./models/customer5.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'order.model.ts', - content: fs.readFileSync(require.resolve('./models/order.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, -]; exports.SANDBOX_FILES2 = [ - { - path: REPOSITORY_APP_PATH, - file: 'customer.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer-class.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer-class.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order-class.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order-class.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, + SourceEntries.CustomerRepository, + SourceEntries.OrderRepository, + SourceEntries.CustomerClassRepository, + SourceEntries.OrderClassRepository, + SourceEntries.CustomerClassTypeRepository, + SourceEntries.OrderClassTypeRepository, - { - path: REPOSITORY_APP_PATH, - file: 'customer-class-type.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer-class-type.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order-class-type.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order-class-type.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, { path: DATASOURCE_APP_PATH, file: 'restdb.datasource.ts', content: DUMMY_CONTENT, }, - { - path: MODEL_APP_PATH, - file: 'customer.model.ts', - content: fs.readFileSync(require.resolve('./models/customer.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'order.model.ts', - content: fs.readFileSync(require.resolve('./models/order.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'nokey.model.ts', - content: fs.readFileSync(require.resolve('./models/nokey.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'customer-class.model.ts', - content: fs.readFileSync( - require.resolve('./models/customer-class.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: MODEL_APP_PATH, - file: 'order-class.model.ts', - content: fs.readFileSync(require.resolve('./models/order-class.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'customer-class-type.model.ts', - content: fs.readFileSync( - require.resolve('./models/customer-class-type.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: MODEL_APP_PATH, - file: 'order-class-type.model.ts', - content: fs.readFileSync( - require.resolve('./models/order-class-type.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: CONTROLLER_PATH, - file: 'index.ts', - content: fs.readFileSync(require.resolve('./controllers/index.ts'), { - encoding: 'utf-8', - }), - }, -]; + SourceEntries.CustomerModel, + SourceEntries.OrderModel, + SourceEntries.NoKeyModel, + SourceEntries.CustomerClassModel, + SourceEntries.OrderClassModel, + SourceEntries.CustomerClassTypeModel, -exports.SANDBOX_FILES3 = []; + SourceEntries.IndexOfControllers, +]; exports.SANDBOX_FILES4 = [ { path: CONTROLLER_PATH, file: 'order-customer.controller.ts', - content: fs.readFileSync( - require.resolve('./controllers/order-customer.controller.ts'), - { - encoding: 'utf-8', - }, - ), + content: readSourceFile('./controllers/order-customer.controller.ts'), }, { path: CONTROLLER_PATH, file: 'index.ts', - content: fs.readFileSync(require.resolve('./controllers/index4.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'customer.model.ts', - content: fs.readFileSync(require.resolve('./models/customer.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'order.model.ts', - content: fs.readFileSync(require.resolve('./models/order.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order.repository.ts'), - { - encoding: 'utf-8', - }, - ), + content: readSourceFile('./controllers/index4.ts'), }, + SourceEntries.CustomerModel, + SourceEntries.OrderModel, + SourceEntries.CustomerRepository, + SourceEntries.OrderRepository, ]; -exports.SANDBOX_FILES6 = [ - { - path: MODEL_APP_PATH, - file: 'customer.model.ts', - content: fs.readFileSync(require.resolve('./models/customer5.model.ts'), { - encoding: 'utf-8', - }), - }, - { - path: MODEL_APP_PATH, - file: 'order.model.ts', - content: fs.readFileSync( - require.resolve('./models/order-with-fk.model.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'customer.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/customer.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, - { - path: REPOSITORY_APP_PATH, - file: 'order.repository.ts', - content: fs.readFileSync( - require.resolve('./repositories/order.repository.ts'), - { - encoding: 'utf-8', - }, - ), - }, -]; +function readSourceFile(relativePath) { + return fs.readFileSync(require.resolve(relativePath), {encoding: 'utf-8'}); +} diff --git a/packages/cli/test/integration/generators/belongsto.relation.integration.js b/packages/cli/test/integration/generators/belongsto.relation.integration.js deleted file mode 100644 index 4ffc2149586a..000000000000 --- a/packages/cli/test/integration/generators/belongsto.relation.integration.js +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -'use strict'; - -const path = require('path'); -const assert = require('yeoman-assert'); -const testlab = require('@loopback/testlab'); -const fs = require('fs'); - -const TestSandbox = testlab.TestSandbox; - -const generator = path.join(__dirname, '../../../generators/relation'); -const SANDBOX_FILES = require('../../fixtures/relation').SANDBOX_FILES; -const testUtils = require('../../test-utils'); - -// Test Sandbox -const SANDBOX_PATH = path.resolve(__dirname, '..', '.sandbox'); -const MODEL_APP_PATH = 'src/models'; -const CONTROLLER_PATH = 'src/controllers'; -const REPOSITORY_APP_PATH = 'src/repositories'; -const sandbox = new TestSandbox(SANDBOX_PATH); - -const sourceFileName = [ - 'order.model.ts', - 'order-class.model.ts', - 'order-class-type.model.ts', -]; -const controllerFileName = [ - 'order-customer.controller.ts', - 'order-class-customer-class.controller.ts', - 'order-class-type-customer-class-type.controller.ts', -]; -const repositoryFileName = [ - 'order.repository.ts', - 'order-class.repository.ts', - 'order-class-type.repository.ts', -]; - -describe('lb4 relation', function() { - // eslint-disable-next-line no-invalid-this - this.timeout(50000); - - beforeEach('reset sandbox', async () => { - await sandbox.reset(); - }); - - // special cases regardless of the repository type - context('generate model relation - ', () => { - const expectedImport = /import {Entity, model, property, belongsTo} from \'\@loopback\/repository\';\n/; - const expectedDecoretor = [ - /@belongsTo\(\(\) => Customer\)\n {2}myCustomer: number;\n/, - /@belongsTo\(\(\) => CustomerClass\)\n {2}myCustomer: number;\n/, - /@belongsTo\(\(\) => CustomerClassType\)\n {2}myCustomer: number;\n/, - ]; - const promptArray = [ - { - relationType: 'belongsTo', - sourceModel: 'Order', - destinationModel: 'Customer', - relationName: 'myCustomer', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClass', - destinationModel: 'CustomerClass', - relationName: 'myCustomer', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClassType', - destinationModel: 'CustomerClassType', - relationName: 'myCustomer', - }, - ]; - - promptArray.forEach(function(multiItemPrompt, i) { - it('add import belongsTo, import for target model and belongsTo decorator ', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[i], - ); - - assert.file(expectedSourceFile); - assert.fileContent(expectedSourceFile, expectedImport); - assert.fileContent(expectedSourceFile, expectedDecoretor[i]); - }); - }); - }); - - context('generate model relation with custom relation name - ', () => { - const expectedDecoretor = [ - /@belongsTo\(\(\) => Customer\)\n {2}customerPK: number;\n/, - /@belongsTo\(\(\) => CustomerClass\)\n {2}customerPK: number;\n/, - /@belongsTo\(\(\) => CustomerClassType\)\n {2}customerPK: number;\n/, - ]; - - const promptArray = [ - { - relationType: 'belongsTo', - sourceModel: 'Order', - destinationModel: 'Customer', - relationName: 'customerPK', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClass', - destinationModel: 'CustomerClass', - relationName: 'customerPK', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClassType', - destinationModel: 'CustomerClassType', - relationName: 'customerPK', - }, - ]; - promptArray.forEach(function(multiItemPrompt, i) { - it('relation name should be customerPK', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[i], - ); - - assert.fileContent(expectedSourceFile, expectedDecoretor[i]); - }); - }); - }); - - context('generate model relation with default relation name', () => { - const expectedDecoretor = [ - /@belongsTo\(\(\) => Customer\)\n {2}customerId: number;\n/, - /@belongsTo\(\(\) => CustomerClass\)\n {2}customerClassCustNumber: number;\n/, - /@belongsTo\(\(\) => CustomerClassType\)\n {2}customerClassTypeCustNumber: number;\n/, - ]; - const defaultRelationName = [ - 'customerId', - 'customerClassCustNumber', - 'customerClassTypeCustNumber', - ]; - - const promptArray = [ - { - relationType: 'belongsTo', - sourceModel: 'Order', - destinationModel: 'Customer', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClass', - destinationModel: 'CustomerClass', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClassType', - destinationModel: 'CustomerClassType', - }, - ]; - promptArray.forEach(function(multiItemPrompt, i) { - it('relation name should be ' + defaultRelationName[i], async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[i], - ); - - assert.fileContent(expectedSourceFile, expectedDecoretor[i]); - }); - }); - }); -}); -context('check if the controller file created ', () => { - const promptArray = [ - { - relationType: 'belongsTo', - sourceModel: 'Order', - destinationModel: 'Customer', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClass', - destinationModel: 'CustomerClass', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClassType', - destinationModel: 'CustomerClassType', - }, - ]; - const controllerClass = [ - /class OrderCustomerController/, - /class OrderClassCustomerClassController/, - /class OrderClassTypeCustomerClassTypeController/, - ]; - const controllerConstructor = [ - /constructor\(\n {4}\@repository\(OrderRepository\)\n {4}public orderRepository: OrderRepository,\n {2}\) \{ \}\n/, - /constructor\(\n {4}\@repository\(OrderClassRepository\)\n {4}public orderClassRepository: OrderClassRepository,\n {2}\) \{ \}\n/, - /constructor\(\n {4}\@repository\(OrderClassTypeRepository\)\n {4}public orderClassTypeRepository: OrderClassTypeRepository,\n {2}\) \{ \}\n/, - ]; - const indexExport = [ - /export \* from '.\/order-customer.controller';/, - /export \* from '.\/order-class-customer-class.controller';/, - /export \* from '.\/order-class-type-customer-class-type.controller';/, - ]; - const sourceClassnames = ['Customer', 'CustomerClass', 'CustomerClassType']; - const targetClassnames = ['Order', 'OrderClass', 'OrderClassType']; - promptArray.forEach(function(multiItemPrompt, i) { - it('new controller file created', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - assert.file(expectedControllerFile); - }).timeout(10000); - it('controller with belongsTo class and constructor', async () => { - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - assert.fileContent(expectedControllerFile, controllerClass[i]); - assert.fileContent(expectedControllerFile, controllerConstructor[i]); - }); - it('the new controller file added to index.ts file', async () => { - const expectedControllerIndexFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - 'index.ts', - ); - - assert.fileContent(expectedControllerIndexFile, indexExport[i]); - }); - it( - 'controller GET Array of ' + - targetClassnames[i] + - "'s belonging to " + - sourceClassnames[i], - async () => { - const getOrdersByCustomerIdRegEx = [ - /\@get\('\/orders\/{id}\/customer', \{\n {4}responses: \{\n {6}'200': \{\n/, - /content: \{\n {10}'application\/json': \{\n/, - /async getCustomer\(\n {4}\@param\.path\.number\('id'\) id: typeof Order\.prototype\.id,\n/, - /\)\: Promise \{\n/, - /return this\.orderRepository\.customer\(id\);\n {2}\}\n/, - ]; - const getOrdersClassByCustomerClassIdRegEx = [ - /\@get\('\/order-classes\/{id}\/customer-class', \{\n {4}responses: \{\n {6}'200': \{\n/, - /content: \{\n {10}'application\/json': \{\n/, - /async getCustomerClass\(\n {4}\@param\.path\.number\('id'\) id: typeof OrderClass\.prototype\.orderNumber,\n/, - /\)\: Promise \{\n/, - /return this\.orderClassRepository\.customerClass\(id\);\n {2}\}\n/, - ]; - - const getOrdersClassTypeByCustomerClassTypeIdRegEx = [ - /\@get\('\/order-class-types\/{id}\/customer-class-type', \{\n {4}responses: \{\n {6}'200': \{\n/, - /content: \{\n {10}'application\/json': \{\n/, - /async getCustomerClassType\(\n {4}\@param\.path\.string\('id'\) id: typeof OrderClassType\.prototype\.orderString,\n/, - /\)\: Promise \{\n/, - /return this\.orderClassTypeRepository\.customerClassType\(id\);\n {2}\}\n/, - ]; - - const getRegEx = [ - getOrdersByCustomerIdRegEx, - getOrdersClassByCustomerClassIdRegEx, - getOrdersClassTypeByCustomerClassTypeIdRegEx, - ]; - - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - getRegEx[i].forEach(regex => { - assert.fileContent(expectedControllerFile, regex); - }); - }, - ); - }); -}); - -context('check source class repository ', () => { - const promptArray = [ - { - relationType: 'belongsTo', - sourceModel: 'Order', - destinationModel: 'Customer', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClass', - destinationModel: 'CustomerClass', - }, - { - relationType: 'belongsTo', - sourceModel: 'OrderClassType', - destinationModel: 'CustomerClassType', - }, - ]; - - const sourceClassnames = ['Order', 'OrderClass', 'OrderClassType']; - - promptArray.forEach(function(multiItemPrompt, i) { - it(sourceClassnames[i] + ' repostitory has all imports', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const repositoryBasicImports = [ - /import \{DefaultCrudRepository, repository, BelongsToAccessor\} from \'@loopback\/repository\';\n/, - /import \{inject, Getter\} from '\@loopback\/core';/, - ]; - - const repositoryClassImport = [ - /import \{CustomerRepository\} from '\.\/customer\.repository';/, - /import \{Order, Customer\} from '\.\.\/models';/, - ]; - const repositoryMultiWordClassImport = [ - /import \{CustomerClassRepository\} from '\.\/customer-class\.repository';/, - /import \{OrderClass, CustomerClass\} from '\.\.\/models';/, - ]; - - const repositoryTypeClassImport = [ - /import \{CustomerClassTypeRepository\} from '\.\/customer-class-type\.repository';/, - /import \{OrderClassType, CustomerClassType\} from '\.\.\/models';/, - ]; - - const sourceRepositoryFile = path.join( - SANDBOX_PATH, - REPOSITORY_APP_PATH, - repositoryFileName[i], - ); - - repositoryBasicImports.forEach(regex => { - assert.fileContent(sourceRepositoryFile, regex); - }); - - const importRegEx = [ - repositoryClassImport, - repositoryMultiWordClassImport, - repositoryTypeClassImport, - ]; - - importRegEx[i].forEach(regex => { - assert.fileContent(sourceRepositoryFile, regex); - }); - }).timeout(10000); - - it('repository has updated constructor', async () => { - const singleWordClassConstractor = [ - /public readonly customer: BelongsToAccessor;\n/, - /constructor\(@inject\('datasources\.db'\) dataSource: DbDataSource, @repository\.getter\('CustomerRepository'\) protected customerRepositoryGetter: Getter,\) \{\n/, - /super\(Order, dataSource\);\n {4}this\.customer = this\.createBelongsToAccessorFor\('customer', customerRepositoryGetter,\);\n {2}\}\n/, - ]; - - const multiWordClassConstractor = [ - /public readonly customerClass: BelongsToAccessor;\n/, - /constructor\(@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, @repository\.getter\('CustomerClassRepository'\) protected customerClassRepositoryGetter: Getter,\) \{\n/, - /super\(OrderClass, dataSource\);\n {4}this\.customerClass = this\.createBelongsToAccessorFor\('customerClassCustNumber', customerClassRepositoryGetter,\);\n {2}\}\n/, - ]; - - const typeClassConstractor = [ - /public readonly customerClassType: BelongsToAccessor;\n/, - /constructor\(@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, @repository\.getter\('CustomerClassTypeRepository'\) protected customerClassTypeRepositoryGetter: Getter,\) \{\n/, - /super\(OrderClassType, dataSource\);\n {4}this\.customerClassType = this\.createBelongsToAccessorFor\('customerClassTypeCustNumber', customerClassTypeRepositoryGetter,\);\n {2}\}\n/, - ]; - - const sourceRepositoryFile = path.join( - SANDBOX_PATH, - REPOSITORY_APP_PATH, - repositoryFileName[i], - ); - - const updateConstructorRegEx = [ - singleWordClassConstractor, - multiWordClassConstractor, - typeClassConstractor, - ]; - updateConstructorRegEx[i].forEach(regex => { - assert.fileContent(sourceRepositoryFile, regex); - }); - }); - }); - // Verify is property name that already exist will overwriting. - context('generate model relation - ', () => { - const expectedDecoretor = [ - /@belongsTo\(\(\) => Customer\)\n {2}myCustomer: number;\n/, - /@belongsTo\(\(\) => CustomerClass\)\n {2}myCustomer: number;\n/, - /@belongsTo\(\(\) => CustomerClassType\)\n {2}myCustomer: number;\n/, - ]; - const promptList = [ - { - relationType: 'belongsTo', - sourceModel: 'Order', - destinationModel: 'Customer', - relationName: 'myCustomer', - }, - ]; - - it('Verify is property name that already exist will overwriting ', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(promptList[0]); - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(promptList[0]); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[0], - ); - - assert.file(expectedSourceFile); - assert.fileContent(expectedSourceFile, expectedDecoretor[0]); - - fs.readFile(expectedSourceFile, (err, data) => { - if (err) throw err; - const indexOfFirstRelation = data.indexOf('@belongsTo'); - const lastIndexOfRelation = data.lastIndexOf('@belongsTo'); - assert.equal(indexOfFirstRelation, lastIndexOfRelation); - }); - }).timeout(20000); - }); -}); diff --git a/packages/cli/test/integration/generators/hasmany.relation.integration.js b/packages/cli/test/integration/generators/hasmany.relation.integration.js deleted file mode 100644 index f627674bf7c1..000000000000 --- a/packages/cli/test/integration/generators/hasmany.relation.integration.js +++ /dev/null @@ -1,720 +0,0 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -'use strict'; - -const path = require('path'); -const assert = require('yeoman-assert'); -const testlab = require('@loopback/testlab'); -const TestSandbox = testlab.TestSandbox; - -const generator = path.join(__dirname, '../../../generators/relation'); -const SANDBOX_FILES = require('../../fixtures/relation').SANDBOX_FILES; -const testUtils = require('../../test-utils'); - -// Test Sandbox -const SANDBOX_PATH = path.resolve(__dirname, '..', '.sandbox'); -const MODEL_APP_PATH = 'src/models'; -const CONTROLLER_PATH = 'src/controllers'; -const REPOSITORY_APP_PATH = 'src/repositories'; - -const sandbox = new TestSandbox(SANDBOX_PATH); - -const hasManyrImportRegEx = /import \{Entity, model, property, hasMany\} from '@loopback\/repository';\n/; -const sourceFileName = [ - 'customer.model.ts', - 'customer-class.model.ts', - 'customer-class-type.model.ts', -]; -const targetFileName = [ - 'order.model.ts', - 'order-class.model.ts', - 'order-class-type.model.ts', -]; -const controllerFileName = [ - 'customer-order.controller.ts', - 'customer-class-order-class.controller.ts', - 'customer-class-type-order-class-type.controller.ts', -]; -const repositoryFileName = [ - 'customer.repository.ts', - 'customer-class.repository.ts', - 'customer-class-type.repository.ts', -]; - -describe('lb4 relation', function() { - // eslint-disable-next-line no-invalid-this - this.timeout(30000); - - beforeEach('reset sandbox', async () => { - await sandbox.reset(); - }); - - // special cases regardless of the repository type - context('generate model relation', () => { - const promptArray = [ - { - relationType: 'hasMany', - sourceModel: 'Customer', - destinationModel: 'Order', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClass', - destinationModel: 'OrderClass', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClassType', - destinationModel: 'OrderClassType', - }, - ]; - - const expectedImport = [ - /import \{Order\} from '\.\/order\.model';\n/, - /import \{OrderClass\} from '\.\/order-class\.model';\n/, - /import \{OrderClassType\} from '\.\/order-class-type\.model';\n/, - ]; - const expectedDecoretor = [ - /\@hasMany\(\(\) => Order\)\n {2}orders: Order\[\];\n/, - /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'customerClassCustNumber'\}\)/, - /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'customerClassTypeCustNumber'\}\)/, - ]; - - promptArray.forEach(function(multiItemPrompt, i) { - it('add import hasMany, import for target model and hasMany decorator ', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[i], - ); - - assert.file(expectedSourceFile); - assert.fileContent(expectedSourceFile, hasManyrImportRegEx); - assert.fileContent(expectedSourceFile, expectedImport[i]); - - assert.fileContent(expectedSourceFile, expectedDecoretor[i]); - }); - }); - }); - - context('generate model relation with custom relation name', () => { - const promptArray = [ - { - relationType: 'hasMany', - sourceModel: 'Customer', - destinationModel: 'Order', - relationName: 'myOrders', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClass', - destinationModel: 'OrderClass', - relationName: 'myOrders', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClassType', - destinationModel: 'OrderClassType', - relationName: 'myOrders', - }, - ]; - - const expectedImport = [ - /import \{Order\} from '\.\/order\.model';\n/, - /import \{OrderClass\} from '\.\/order-class\.model';\n/, - /import \{OrderClassType\} from '\.\/order-class-type\.model';\n/, - ]; - const expectedDecoretor = [ - /\@hasMany\(\(\) => Order\)\n {2}myOrders: Order\[\];\n/, - /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'customerClassCustNumber'\}\)/, - /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'customerClassTypeCustNumber'\}\)/, - ]; - const expectedProperty = [ - /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}customerId\?\: number;\n/, - /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}customerClassCustNumber\?\: number;\n/, - /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}customerClassTypeCustNumber\?\: number;\n/, - ]; - - promptArray.forEach(function(multiItemPrompt, i) { - it('relation name should be myOrders', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[i], - ); - const expectedTargetFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - targetFileName[i], - ); - - assert.file(expectedSourceFile); - assert.fileContent(expectedSourceFile, hasManyrImportRegEx); - - assert.fileContent(expectedSourceFile, expectedImport[i]); - - assert.fileContent(expectedSourceFile, expectedDecoretor[i]); - - assert.fileContent(expectedTargetFile, expectedProperty[i]); - }); - }); - }); - - context('generate model relation with custom foreignKey', () => { - const promptArray = [ - { - relationType: 'hasMany', - sourceModel: 'Customer', - destinationModel: 'Order', - foreignKeyName: 'mykey', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClass', - destinationModel: 'OrderClass', - foreignKeyName: 'mykey', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClassType', - destinationModel: 'OrderClassType', - foreignKeyName: 'mykey', - }, - ]; - - const expectedImport = [ - /import \{Order\} from '\.\/order\.model';\n/, - /import \{OrderClass\} from '\.\/order-class\.model';\n/, - /import \{OrderClassType\} from '\.\/order-class-type\.model';\n/, - ]; - const expectedDecoretor = [ - /\@hasMany\(\(\) => Order ,\{keyTo: 'mykey'\}\)\n {2}orders: Order\[\];\n/, - /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'mykey'\}\)\n {2}orderClasses: OrderClass\[\];\n/, - /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'mykey'\}\)\n {2}orderClassTypes: OrderClassType\[\];\n/, - ]; - const expectedProperty = /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}mykey\?\: number;\n/; - - promptArray.forEach(function(multiItemPrompt, i) { - it('add the keyTo to the source model', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[i], - ); - const expectedTargetFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - targetFileName[i], - ); - - assert.file(expectedSourceFile); - assert.fileContent(expectedSourceFile, hasManyrImportRegEx); - - assert.fileContent(expectedSourceFile, expectedImport[i]); - - assert.fileContent(expectedSourceFile, expectedDecoretor[i]); - - assert.fileContent(expectedTargetFile, expectedProperty); - }); - }); - }); - - context('generate model relation with default relation name', () => { - const promptArray = [ - { - relationType: 'hasMany', - sourceModel: 'Customer', - destinationModel: 'Order', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClass', - destinationModel: 'OrderClass', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClassType', - destinationModel: 'OrderClassType', - }, - ]; - - const expectedDecoretor = [ - /\@hasMany\(\(\) => Order\)\n {2}orders: Order\[\];\n/, - /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'customerClassCustNumber'\}\)\n/, - /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'customerClassTypeCustNumber'\}\)\n/, - ]; - const defaultRelationName = ['orders', 'orderClasses', 'orderClassTypes']; - promptArray.forEach(function(multiItemPrompt, i) { - it('relation name should be ' + defaultRelationName[i], async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedSourceFile = path.join( - SANDBOX_PATH, - MODEL_APP_PATH, - sourceFileName[i], - ); - - assert.fileContent(expectedSourceFile, expectedDecoretor[i]); - }); - }); - }); -}); - -context('check if the controller file created ', () => { - const promptArray = [ - { - relationType: 'hasMany', - sourceModel: 'Customer', - destinationModel: 'Order', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClass', - destinationModel: 'OrderClass', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClassType', - destinationModel: 'OrderClassType', - }, - ]; - const controllerClass = [ - /class CustomerOrderController/, - /class CustomerClassOrderClassController/, - /class CustomerClassTypeOrderClassTypeController/, - ]; - const controllerConstructor = [ - /constructor\(\n {4}\@repository\(CustomerRepository\) protected customerRepository: CustomerRepository,\n {2}\) \{ \}\n/, - /constructor\(\n {4}\@repository\(CustomerClassRepository\) protected customerClassRepository: CustomerClassRepository,\n {2}\) \{ \}\n/, - /constructor\(\n {4}\@repository\(CustomerClassTypeRepository\) protected customerClassTypeRepository: CustomerClassTypeRepository,\n {2}\) \{ \}\n/, - ]; - - const indexExport = [ - /export \* from '\.\/customer-order\.controller';/, - /export \* from '\.\/customer-class-order-class\.controller';/, - /export \* from '\.\/customer-class-type-order-class-type\.controller';/, - ]; - const sourceClassnames = ['Customer', 'CustomerClass', 'CustomerClassType']; - const targetClassnames = ['Order', 'OrderClass', 'OrderClassType']; - - promptArray.forEach(function(multiItemPrompt, i) { - it('new controller file created', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - assert.file(expectedControllerFile); - }).timeout(10000); - - it('controller with hasMany class and constractor', async () => { - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - assert.fileContent(expectedControllerFile, controllerClass[i]); - assert.fileContent(expectedControllerFile, controllerConstructor[i]); - }); - - it('the new controller file added to index.ts file', async () => { - const expectedControllerIndexFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - 'index.ts', - ); - - assert.fileContent(expectedControllerIndexFile, indexExport[i]); - }); - - it( - 'controller GET Array of ' + - targetClassnames[i] + - "'s belonging to " + - sourceClassnames[i], - async () => { - const getOrdersByCustomerIdRegEx = [ - /\@get\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'Array of Order\\'s belonging to Customer',\n/, - /content: {\n {10}'application\/json': {\n/, - /schema: {type: 'array', items: getModelSchemaRef\(Order\)},/, - /},\n . {6}},\n . {4}},\n . {2}},\n {2}}\)\n/, - /async find\(\n . {2}\@param.path.number\('id'\) id: number,\n/, - /\@param.query.object\('filter'\) filter\?: Filter,\n/, - /\)\: Promise {\n/, - /return this\.customerRepository\.orders\(id\)\.find\(filter\);\n {2}}\n/, - ]; - const getOrdersClassByCustomerClassIdRegEx = [ - /\@get\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'Array of OrderClass\\'s belonging to CustomerClass',\n/, - /content: {\n {10}'application\/json': {\n/, - /schema: {type: 'array', items: getModelSchemaRef\(OrderClass\)},/, - /},\n . {6}},\n . {4}},\n . {2}},\n {2}}\)\n/, - /async find\(\n . {2}\@param.path.number\('id'\) id: number,\n/, - /\@param.query.object\('filter'\) filter\?: Filter,\n/, - /\)\: Promise {\n/, - /return this\.customerClassRepository\.orderClasses\(id\)\.find\(filter\);\n {2}}\n/, - ]; - const getOrdersClassTypeByCustomerClassTypeIdRegEx = [ - /\@get\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'Array of OrderClassType\\'s belonging to CustomerClassType',\n/, - /content: {\n {10}'application\/json': {\n/, - /schema: {type: 'array', items: getModelSchemaRef\(OrderClassType\)},/, - /},\n . {6}},\n . {4}},\n . {2}},\n {2}}\)\n/, - /async find\(\n . {2}\@param.path.number\('id'\) id: number,\n/, - /\@param.query.object\('filter'\) filter\?: Filter,\n/, - /\)\: Promise {\n/, - /return this\.customerClassTypeRepository\.orderClassTypes\(id\).find\(filter\);\n {2}}\n/, - ]; - - const getRegEx = [ - getOrdersByCustomerIdRegEx, - getOrdersClassByCustomerClassIdRegEx, - getOrdersClassTypeByCustomerClassTypeIdRegEx, - ]; - - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - getRegEx[i].forEach(regex => { - assert.fileContent(expectedControllerFile, regex); - }); - }, - ); - - it( - 'controller POST ' + targetClassnames[i] + ' to ' + sourceClassnames[i], - async () => { - const postClassCreateRegEx = [ - /\@post\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'Customer model instance',\n/, - /content: {'application\/json': {schema: getModelSchemaRef\(Order\)}},\n/, - /},\n . {2}},\n .}\)\n {2}async create\(\n/, - /\@param\.path\.number\('id'\) id: typeof Customer\.prototype\.id,\n/, - /\@requestBody\(\{\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(Order, {\n/, - /exclude: \['id'\],\n/, - /optional: \['customerId'\]\n/, - /}\),\s+},\s+},\s+}\) order: Omit,\n/, - /\): Promise {\n/, - /return this\.customerRepository\.orders\(id\)\.create\(order\);\n {2}}\n/, - ]; - const postMultiWordClassCreateRegEx = [ - /\@post\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'CustomerClass model instance',\n/, - /content: {'application\/json': {schema: getModelSchemaRef\(OrderClass\)}},\n/, - /},\n . {2}},\n .}\)\n {2}async create\(\n/, - /\@param\.path\.number\('id'\) id: typeof CustomerClass\.prototype\.custNumber,\n/, - /\@requestBody\(\{\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClass, {\n/, - /exclude: \['orderNumber'\],\n/, - /optional: \['customerClassCustNumber'\]\n/, - /}\),\s+},\s+},\s+}\) orderClass: Omit,\n/, - /\): Promise {\n/, - /return this\.customerClassRepository\.orderClasses\(id\)\.create\(orderClass\);\n {2}}\n/, - ]; - const postTypeClassCreateRegEx = [ - /\@post\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'CustomerClassType model instance',\n/, - /content: {'application\/json': {schema: getModelSchemaRef\(OrderClassType\)}},\n/, - /},\n . {2}},\n .}\)\n {2}async create\(\n/, - /\@param\.path\.number\('id'\) id: typeof CustomerClassType\.prototype\.custNumber,\n/, - /\@requestBody\(\{\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClassType, {\n/, - /exclude: \['orderString'\],\n/, - /optional: \['customerClassTypeCustNumber'\]\n/, - /}\),\s+},\s+},\s+}\) orderClassType: Omit,\n/, - /\): Promise {\n/, - /return this\.customerClassTypeRepository\.orderClassTypes\(id\)\.create\(orderClassType\);\n {2}}\n/, - ]; - - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - - const postRegEx = [ - postClassCreateRegEx, - postMultiWordClassCreateRegEx, - postTypeClassCreateRegEx, - ]; - - postRegEx[i].forEach(regex => { - assert.fileContent(expectedControllerFile, regex); - }); - }, - ); - - it( - 'controller ' + - targetClassnames[i] + - ' PATCH by ' + - sourceClassnames[i] + - ' id', - async () => { - const updateOrderByCustomerIdRegEx = [ - /\@patch\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'Customer.Order PATCH success count',\n/, - /content: {'application\/json': {schema: CountSchema}},\n/, - /},\n {4}},\n {2}}\)\n {2}async patch\(\n/, - /\@param\.path\.number\('id'\) id: number,\n {4}/, - /\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(Order, {partial: true}\),\s+},\s+},\s+}\)\s+order: Partial,\n/, - /\@param\.query\.object\('where', getWhereSchemaFor\(Order\)\) where\?: Where,\n/, - /\): Promise {\n/, - /return this\.customerRepository\.orders\(id\).patch\(order, where\);\n {2}}\n/, - ]; - - const updateOrderClassByCustomerClassIdRegEx = [ - /\@patch\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'CustomerClass.OrderClass PATCH success count',\n/, - /content: {'application\/json': {schema: CountSchema}},\n/, - /},\n {4}},\n {2}}\)\n {2}async patch\(\n/, - /\@param\.path\.number\('id'\) id: number,\n {4}/, - /\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClass, {partial: true}\),\s+},\s+},\s+}\)\s+orderClass: Partial,\n/, - /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClass\)\) where\?: Where,\n/, - /\): Promise {\n/, - /return this\.customerClassRepository\.orderClasses\(id\)\.patch\(orderClass, where\);\n {2}}\n/, - ]; - - const updateOrderClassByCustomerClassTypeIdRegEx = [ - /\@patch\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'CustomerClassType.OrderClassType PATCH success count',\n/, - /content: {'application\/json': {schema: CountSchema}},\n/, - /},\n {4}},\n {2}}\)\n {2}async patch\(\n/, - /\@param\.path\.number\('id'\) id: number,\n {4}/, - /\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClassType, {partial: true}\),\s+},\s+},\s+}\)\s+orderClassType: Partial,\n/, - /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClassType\)\) where\?: Where,\n/, - /\): Promise {\n/, - /return this\.customerClassTypeRepository\.orderClassTypes\(id\).patch\(orderClassType, where\);\n {2}}\n/, - ]; - - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - - const updateRegEx = [ - updateOrderByCustomerIdRegEx, - updateOrderClassByCustomerClassIdRegEx, - updateOrderClassByCustomerClassTypeIdRegEx, - ]; - - updateRegEx[i].forEach(regex => { - assert.fileContent(expectedControllerFile, regex); - }); - }, - ); - - it( - 'controller ' + - targetClassnames[i] + - ' DELETE by ' + - sourceClassnames[i] + - ' id', - async () => { - const deleteOrderByCustomerIdRegEx = [ - /\@del\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'Customer.Order DELETE success count',\n/, - /content: {'application\/json': {schema: CountSchema}},\n/, - /},\n {4}},\n {2}}\)\n {2}async delete\(\n/, - /\@param\.path\.number\('id'\) id: number,\n /, - /\@param\.query\.object\('where', getWhereSchemaFor\(Order\)\) where\?: Where,\n/, - /\): Promise {\n/, - /return this\.customerRepository\.orders\(id\)\.delete\(where\);\n {2}}\n}\n/, - ]; - - const deleteOrderClassByCustomerClassIdRegEx = [ - /\@del\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'CustomerClass.OrderClass DELETE success count',\n/, - /content: {'application\/json': {schema: CountSchema}},\n/, - /},\n {4}},\n {2}}\)\n {2}async delete\(\n/, - /\@param\.path\.number\('id'\) id: number,\n /, - /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClass\)\) where\?: Where,\n/, - /\): Promise {\n/, - /return this\.customerClassRepository\.orderClasses\(id\)\.delete\(where\);\n {2}}\n}\n/, - ]; - - const deleteOrderClassTypeByCustomerClassTypeIdRegEx = [ - /\@del\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, - /description: 'CustomerClassType.OrderClassType DELETE success count',\n/, - /content: {'application\/json': {schema: CountSchema}},\n/, - /},\n {4}},\n {2}}\)\n {2}async delete\(\n/, - /\@param\.path\.number\('id'\) id: number,\n /, - /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClassType\)\) where\?: Where,\n/, - /\): Promise {\n/, - /return this\.customerClassTypeRepository\.orderClassTypes\(id\)\.delete\(where\);\n {2}}\n}\n/, - ]; - - const expectedControllerFile = path.join( - SANDBOX_PATH, - CONTROLLER_PATH, - controllerFileName[i], - ); - - const deleteRegEx = [ - deleteOrderByCustomerIdRegEx, - deleteOrderClassByCustomerClassIdRegEx, - deleteOrderClassTypeByCustomerClassTypeIdRegEx, - ]; - - deleteRegEx[i].forEach(regex => { - assert.fileContent(expectedControllerFile, regex); - }); - }, - ); - }); -}); - -context('check source class repository ', () => { - const promptArray = [ - { - relationType: 'hasMany', - sourceModel: 'Customer', - destinationModel: 'Order', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClass', - destinationModel: 'OrderClass', - }, - { - relationType: 'hasMany', - sourceModel: 'CustomerClassType', - destinationModel: 'OrderClassType', - }, - ]; - - const sourceClassnames = ['Customer', 'CustomerClass', 'CustomerClassType']; - - promptArray.forEach(function(multiItemPrompt, i) { - it(sourceClassnames[i] + ' repostitory has all imports', async () => { - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(multiItemPrompt); - - const repositoryBasicImports = [ - /repository, HasManyRepositoryFactory} from '\@loopback\/repository';\n/, - /import \{inject, Getter\} from '\@loopback\/core';/, - ]; - - const repositoryClassImport = [ - /import \{OrderRepository\} from '\.\/order\.repository';/, - /import \{Customer, Order\} from '\.\.\/models';/, - ]; - const repositoryMultiWordClassImport = [ - /import \{OrderClassRepository\} from '\.\/order-class\.repository';/, - /import \{CustomerClass, OrderClass\} from '\.\.\/models';/, - ]; - const repositoryTypeClassImport = [ - /import \{OrderClassTypeRepository\} from '\.\/order-class-type\.repository';/, - /import \{CustomerClassType, OrderClassType\} from '\.\.\/models';/, - ]; - - const sourceRepositoryFile = path.join( - SANDBOX_PATH, - REPOSITORY_APP_PATH, - repositoryFileName[i], - ); - - repositoryBasicImports.forEach(regex => { - assert.fileContent(sourceRepositoryFile, regex); - }); - - const importRegEx = [ - repositoryClassImport, - repositoryMultiWordClassImport, - repositoryTypeClassImport, - ]; - - importRegEx[i].forEach(regex => { - assert.fileContent(sourceRepositoryFile, regex); - }); - }).timeout(10000); - - it('repository has updated constructor', async () => { - const singleWordClassConstractor = [ - /public readonly orders: HasManyRepositoryFactory;\n/, - /constructor\(\@inject\('datasources\.db'\) dataSource: DbDataSource, \@repository\.getter\('OrderRepository'\) protected orderRepositoryGetter: Getter,\) \{\n/, - /super\(Customer, dataSource\);\n {4}this.orders = this.createHasManyRepositoryFactoryFor\('orders', orderRepositoryGetter,\);\n {2}\}\n/, - ]; - - const multiWordClassConstractor = [ - /public readonly orderClasses: HasManyRepositoryFactory;\n/, - /constructor\(\@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, \@repository\.getter\('OrderClassRepository'\) protected orderClassRepositoryGetter: Getter,\) \{\n/, - /super\(CustomerClass, dataSource\);\n {4}this\.orderClasses = this\.createHasManyRepositoryFactoryFor\('orderClasses', orderClassRepositoryGetter,\);\n {2}\}\n/, - ]; - const typeClassConstractor = [ - /public readonly orderClassTypes: HasManyRepositoryFactory;\n/, - /constructor\(@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, @repository\.getter\('OrderClassTypeRepository'\) protected orderClassTypeRepositoryGetter: Getter,\) \{\n/, - /super\(CustomerClassType, dataSource\);\n {4}this\.orderClassTypes = this\.createHasManyRepositoryFactoryFor\('orderClassTypes', orderClassTypeRepositoryGetter,\);\n {2}\}/, - ]; - - const sourceRepositoryFile = path.join( - SANDBOX_PATH, - REPOSITORY_APP_PATH, - repositoryFileName[i], - ); - - const updateConstructorRegEx = [ - singleWordClassConstractor, - multiWordClassConstractor, - typeClassConstractor, - ]; - updateConstructorRegEx[i].forEach(regex => { - assert.fileContent(sourceRepositoryFile, regex); - }); - }); - }); -}); diff --git a/packages/cli/test/integration/generators/relation.belongs-to.integration.js b/packages/cli/test/integration/generators/relation.belongs-to.integration.js new file mode 100644 index 000000000000..b161cf4fa3c8 --- /dev/null +++ b/packages/cli/test/integration/generators/relation.belongs-to.integration.js @@ -0,0 +1,587 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/cli +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +const path = require('path'); +const assert = require('yeoman-assert'); +const {expect, TestSandbox} = require('@loopback/testlab'); +const fs = require('fs'); + +const generator = path.join(__dirname, '../../../generators/relation'); +const {SANDBOX_FILES, SourceEntries} = require('../../fixtures/relation'); +const testUtils = require('../../test-utils'); + +// Test Sandbox +const SANDBOX_PATH = path.resolve(__dirname, '..', '.sandbox'); +const MODEL_APP_PATH = 'src/models'; +const CONTROLLER_PATH = 'src/controllers'; +const REPOSITORY_APP_PATH = 'src/repositories'; +const sandbox = new TestSandbox(SANDBOX_PATH); + +const sourceFileName = [ + 'order.model.ts', + 'order-class.model.ts', + 'order-class-type.model.ts', +]; +const controllerFileName = [ + 'order-customer.controller.ts', + 'order-class-customer-class.controller.ts', + 'order-class-type-customer-class-type.controller.ts', +]; +const repositoryFileName = [ + 'order.repository.ts', + 'order-class.repository.ts', + 'order-class-type.repository.ts', +]; + +describe('lb4 relation', function() { + // eslint-disable-next-line no-invalid-this + this.timeout(50000); + + it("rejects relation when destination model doesn't have primary Key", async () => { + await sandbox.reset(); + const prompt = { + relationType: 'belongsTo', + sourceModel: 'Customer', + destinationModel: 'Nokey', + }; + + return expect( + testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(prompt), + ).to.be.rejectedWith(/Target model primary key does not exist/); + }); + + it('rejects relation when models does not exist', async () => { + await sandbox.reset(); + const prompt = { + relationType: 'belongsTo', + sourceModel: 'Customer', + destinationModel: 'Nokey', + }; + + return expect( + testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: [ + // no model/repository files in this project + ], + }), + ) + .withPrompts(prompt), + ).to.be.rejectedWith(/No models found/); + }); + + it('updates property decorator when property already exist in the model', async () => { + await sandbox.reset(); + const prompt = { + relationType: 'belongsTo', + sourceModel: 'Order', + destinationModel: 'Customer', + }; + + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: [ + SourceEntries.CustomerModelWithOrdersProperty, + SourceEntries.OrderModelModelWithCustomerIdProperty, + SourceEntries.CustomerRepository, + SourceEntries.OrderRepository, + ], + }), + ) + .withPrompts(prompt); + + const expectedFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + 'order.model.ts', + ); + + const relationalPropertyRegEx = /\@belongsTo\(\(\) \=\> Customer\)/; + assert.fileContent(expectedFile, relationalPropertyRegEx); + }); + + context('generate model relation', () => { + const expectedImport = /import {Entity, model, property, belongsTo} from \'\@loopback\/repository\';\n/; + const expectedDecoretor = [ + /@belongsTo\(\(\) => Customer\)\n {2}myCustomer: number;\n/, + /@belongsTo\(\(\) => CustomerClass\)\n {2}myCustomer: number;\n/, + /@belongsTo\(\(\) => CustomerClassType\)\n {2}myCustomer: number;\n/, + ]; + const promptArray = [ + { + relationType: 'belongsTo', + sourceModel: 'Order', + destinationModel: 'Customer', + relationName: 'myCustomer', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClass', + destinationModel: 'CustomerClass', + relationName: 'myCustomer', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClassType', + destinationModel: 'CustomerClassType', + relationName: 'myCustomer', + }, + ]; + + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('add import belongsTo, import for target model and belongsTo decorator ', async () => { + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[i], + ); + + assert.file(expectedSourceFile); + assert.fileContent(expectedSourceFile, expectedImport); + assert.fileContent(expectedSourceFile, expectedDecoretor[i]); + }); + } + }); + + context('generate model relation with custom relation name', () => { + const expectedDecoretor = [ + /@belongsTo\(\(\) => Customer\)\n {2}customerPK: number;\n/, + /@belongsTo\(\(\) => CustomerClass\)\n {2}customerPK: number;\n/, + /@belongsTo\(\(\) => CustomerClassType\)\n {2}customerPK: number;\n/, + ]; + + const promptArray = [ + { + relationType: 'belongsTo', + sourceModel: 'Order', + destinationModel: 'Customer', + relationName: 'customerPK', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClass', + destinationModel: 'CustomerClass', + relationName: 'customerPK', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClassType', + destinationModel: 'CustomerClassType', + relationName: 'customerPK', + }, + ]; + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('relation name should be customerPK', async () => { + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[i], + ); + + assert.fileContent(expectedSourceFile, expectedDecoretor[i]); + }); + } + }); + + context('generate model relation with default relation name', () => { + const expectedDecoretor = [ + /@belongsTo\(\(\) => Customer\)\n {2}customerId: number;\n/, + /@belongsTo\(\(\) => CustomerClass\)\n {2}customerClassCustNumber: number;\n/, + /@belongsTo\(\(\) => CustomerClassType\)\n {2}customerClassTypeCustNumber: number;\n/, + ]; + const defaultRelationName = [ + 'customerId', + 'customerClassCustNumber', + 'customerClassTypeCustNumber', + ]; + + const promptArray = [ + { + relationType: 'belongsTo', + sourceModel: 'Order', + destinationModel: 'Customer', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClass', + destinationModel: 'CustomerClass', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClassType', + destinationModel: 'CustomerClassType', + }, + ]; + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('relation name should be ' + defaultRelationName[i], async () => { + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[i], + ); + + assert.fileContent(expectedSourceFile, expectedDecoretor[i]); + }); + } + }); + + context('check if the controller file created ', () => { + const promptArray = [ + { + relationType: 'belongsTo', + sourceModel: 'Order', + destinationModel: 'Customer', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClass', + destinationModel: 'CustomerClass', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClassType', + destinationModel: 'CustomerClassType', + }, + ]; + const controllerClass = [ + /class OrderCustomerController/, + /class OrderClassCustomerClassController/, + /class OrderClassTypeCustomerClassTypeController/, + ]; + const controllerConstructor = [ + /constructor\(\n {4}\@repository\(OrderRepository\)\n {4}public orderRepository: OrderRepository,\n {2}\) \{ \}\n/, + /constructor\(\n {4}\@repository\(OrderClassRepository\)\n {4}public orderClassRepository: OrderClassRepository,\n {2}\) \{ \}\n/, + /constructor\(\n {4}\@repository\(OrderClassTypeRepository\)\n {4}public orderClassTypeRepository: OrderClassTypeRepository,\n {2}\) \{ \}\n/, + ]; + const indexExport = [ + /export \* from '.\/order-customer.controller';/, + /export \* from '.\/order-class-customer-class.controller';/, + /export \* from '.\/order-class-type-customer-class-type.controller';/, + ]; + const sourceClassnames = ['Customer', 'CustomerClass', 'CustomerClassType']; + const targetClassnames = ['Order', 'OrderClass', 'OrderClassType']; + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('new controller file created', async () => { + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + assert.file(expectedControllerFile); + }); + + it('controller with belongsTo class and constructor', async () => { + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + assert.fileContent(expectedControllerFile, controllerClass[i]); + assert.fileContent(expectedControllerFile, controllerConstructor[i]); + }); + + it('the new controller file added to index.ts file', async () => { + const expectedControllerIndexFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + 'index.ts', + ); + + assert.fileContent(expectedControllerIndexFile, indexExport[i]); + }); + + it( + 'controller GET Array of ' + + targetClassnames[i] + + "'s belonging to " + + sourceClassnames[i], + async () => { + const getOrdersByCustomerIdRegEx = [ + /\@get\('\/orders\/{id}\/customer', \{\n {4}responses: \{\n {6}'200': \{\n/, + /content: \{\n {10}'application\/json': \{\n/, + /async getCustomer\(\n {4}\@param\.path\.number\('id'\) id: typeof Order\.prototype\.id,\n/, + /\)\: Promise \{\n/, + /return this\.orderRepository\.customer\(id\);\n {2}\}\n/, + ]; + const getOrdersClassByCustomerClassIdRegEx = [ + /\@get\('\/order-classes\/{id}\/customer-class', \{\n {4}responses: \{\n {6}'200': \{\n/, + /content: \{\n {10}'application\/json': \{\n/, + /async getCustomerClass\(\n {4}\@param\.path\.number\('id'\) id: typeof OrderClass\.prototype\.orderNumber,\n/, + /\)\: Promise \{\n/, + /return this\.orderClassRepository\.customerClass\(id\);\n {2}\}\n/, + ]; + + const getOrdersClassTypeByCustomerClassTypeIdRegEx = [ + /\@get\('\/order-class-types\/{id}\/customer-class-type', \{\n {4}responses: \{\n {6}'200': \{\n/, + /content: \{\n {10}'application\/json': \{\n/, + /async getCustomerClassType\(\n {4}\@param\.path\.string\('id'\) id: typeof OrderClassType\.prototype\.orderString,\n/, + /\)\: Promise \{\n/, + /return this\.orderClassTypeRepository\.customerClassType\(id\);\n {2}\}\n/, + ]; + + const getRegEx = [ + getOrdersByCustomerIdRegEx, + getOrdersClassByCustomerClassIdRegEx, + getOrdersClassTypeByCustomerClassTypeIdRegEx, + ]; + + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + getRegEx[i].forEach(regex => { + assert.fileContent(expectedControllerFile, regex); + }); + }, + ); + } + }); + + context('check source class repository ', () => { + const promptArray = [ + { + relationType: 'belongsTo', + sourceModel: 'Order', + destinationModel: 'Customer', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClass', + destinationModel: 'CustomerClass', + }, + { + relationType: 'belongsTo', + sourceModel: 'OrderClassType', + destinationModel: 'CustomerClassType', + }, + ]; + + const sourceClassnames = ['Order', 'OrderClass', 'OrderClassType']; + + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it(sourceClassnames[i] + ' repostitory has all imports', async () => { + const repositoryBasicImports = [ + /import \{DefaultCrudRepository, repository, BelongsToAccessor\} from \'@loopback\/repository\';\n/, + /import \{inject, Getter\} from '\@loopback\/core';/, + ]; + + const repositoryClassImport = [ + /import \{CustomerRepository\} from '\.\/customer\.repository';/, + /import \{Order, Customer\} from '\.\.\/models';/, + ]; + const repositoryMultiWordClassImport = [ + /import \{CustomerClassRepository\} from '\.\/customer-class\.repository';/, + /import \{OrderClass, CustomerClass\} from '\.\.\/models';/, + ]; + + const repositoryTypeClassImport = [ + /import \{CustomerClassTypeRepository\} from '\.\/customer-class-type\.repository';/, + /import \{OrderClassType, CustomerClassType\} from '\.\.\/models';/, + ]; + + const sourceRepositoryFile = path.join( + SANDBOX_PATH, + REPOSITORY_APP_PATH, + repositoryFileName[i], + ); + + repositoryBasicImports.forEach(regex => { + assert.fileContent(sourceRepositoryFile, regex); + }); + + const importRegEx = [ + repositoryClassImport, + repositoryMultiWordClassImport, + repositoryTypeClassImport, + ]; + + importRegEx[i].forEach(regex => { + assert.fileContent(sourceRepositoryFile, regex); + }); + }); + + it('repository has updated constructor', async () => { + const singleWordClassConstractor = [ + /public readonly customer: BelongsToAccessor;\n/, + /constructor\(@inject\('datasources\.db'\) dataSource: DbDataSource, @repository\.getter\('CustomerRepository'\) protected customerRepositoryGetter: Getter,\) \{\n/, + /super\(Order, dataSource\);\n {4}this\.customer = this\.createBelongsToAccessorFor\('customer', customerRepositoryGetter,\);\n {2}\}\n/, + ]; + + const multiWordClassConstractor = [ + /public readonly customerClass: BelongsToAccessor;\n/, + /constructor\(@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, @repository\.getter\('CustomerClassRepository'\) protected customerClassRepositoryGetter: Getter,\) \{\n/, + /super\(OrderClass, dataSource\);\n {4}this\.customerClass = this\.createBelongsToAccessorFor\('customerClassCustNumber', customerClassRepositoryGetter,\);\n {2}\}\n/, + ]; + + const typeClassConstractor = [ + /public readonly customerClassType: BelongsToAccessor;\n/, + /constructor\(@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, @repository\.getter\('CustomerClassTypeRepository'\) protected customerClassTypeRepositoryGetter: Getter,\) \{\n/, + /super\(OrderClassType, dataSource\);\n {4}this\.customerClassType = this\.createBelongsToAccessorFor\('customerClassTypeCustNumber', customerClassTypeRepositoryGetter,\);\n {2}\}\n/, + ]; + + const sourceRepositoryFile = path.join( + SANDBOX_PATH, + REPOSITORY_APP_PATH, + repositoryFileName[i], + ); + + const updateConstructorRegEx = [ + singleWordClassConstractor, + multiWordClassConstractor, + typeClassConstractor, + ]; + updateConstructorRegEx[i].forEach(regex => { + assert.fileContent(sourceRepositoryFile, regex); + }); + }); + } + + context('generate model relation for existing property name', () => { + const expectedDecoretor = [ + /@belongsTo\(\(\) => Customer\)\n {2}myCustomer: number;\n/, + /@belongsTo\(\(\) => CustomerClass\)\n {2}myCustomer: number;\n/, + /@belongsTo\(\(\) => CustomerClassType\)\n {2}myCustomer: number;\n/, + ]; + const promptList = [ + { + relationType: 'belongsTo', + sourceModel: 'Order', + destinationModel: 'Customer', + relationName: 'myCustomer', + }, + ]; + + it('Verify is property name that already exist will overwriting ', async () => { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(promptList[0]); + + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[0], + ); + + assert.file(expectedSourceFile); + assert.fileContent(expectedSourceFile, expectedDecoretor[0]); + + const data = fs.readFileSync(expectedSourceFile); + const indexOfFirstRelation = data.indexOf('@belongsTo'); + const lastIndexOfRelation = data.lastIndexOf('@belongsTo'); + assert.equal(indexOfFirstRelation, lastIndexOfRelation); + }); + }); + }); +}); diff --git a/packages/cli/test/integration/generators/relation.has-many.integration.js b/packages/cli/test/integration/generators/relation.has-many.integration.js new file mode 100644 index 000000000000..c81d94c9001a --- /dev/null +++ b/packages/cli/test/integration/generators/relation.has-many.integration.js @@ -0,0 +1,814 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/cli +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +const path = require('path'); +const assert = require('yeoman-assert'); +const {expect, TestSandbox} = require('@loopback/testlab'); + +const generator = path.join(__dirname, '../../../generators/relation'); +const {SANDBOX_FILES, SourceEntries} = require('../../fixtures/relation'); +const testUtils = require('../../test-utils'); + +// Test Sandbox +const SANDBOX_PATH = path.resolve(__dirname, '..', '.sandbox'); +const MODEL_APP_PATH = 'src/models'; +const CONTROLLER_PATH = 'src/controllers'; +const REPOSITORY_APP_PATH = 'src/repositories'; + +const sandbox = new TestSandbox(SANDBOX_PATH); + +const hasManyrImportRegEx = /import \{Entity, model, property, hasMany\} from '@loopback\/repository';\n/; +const sourceFileName = [ + 'customer.model.ts', + 'customer-class.model.ts', + 'customer-class-type.model.ts', +]; +const targetFileName = [ + 'order.model.ts', + 'order-class.model.ts', + 'order-class-type.model.ts', +]; +const controllerFileName = [ + 'customer-order.controller.ts', + 'customer-class-order-class.controller.ts', + 'customer-class-type-order-class-type.controller.ts', +]; +const repositoryFileName = [ + 'customer.repository.ts', + 'customer-class.repository.ts', + 'customer-class-type.repository.ts', +]; + +describe('lb4 relation HasMany', function() { + // eslint-disable-next-line no-invalid-this + this.timeout(30000); + + it("rejects relation when source model doesn't have primary Key", async () => { + await sandbox.reset(); + + const prompt = { + relationType: 'hasMany', + sourceModel: 'Nokey', + destinationModel: 'Customer', + }; + + return expect( + testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(prompt), + ).to.be.rejectedWith(/Source model primary key does not exist\./); + }); + + it('rejects relation when relation already exist in the model', async () => { + await sandbox.reset(); + + const prompt = { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + }; + + return expect( + testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: [ + SourceEntries.CustomerModelWithOrdersProperty, + SourceEntries.OrderModel, + SourceEntries.CustomerRepository, + SourceEntries.OrderRepository, + ], + }), + ) + .withPrompts(prompt), + ).to.be.rejectedWith( + /relational property orders already exist in the model Customer/, + ); + }); + + // special cases regardless of the repository type + context('generate model relation', () => { + const promptArray = [ + { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClass', + destinationModel: 'OrderClass', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClassType', + destinationModel: 'OrderClassType', + }, + ]; + + const expectedImport = [ + /import \{Order\} from '\.\/order\.model';\n/, + /import \{OrderClass\} from '\.\/order-class\.model';\n/, + /import \{OrderClassType\} from '\.\/order-class-type\.model';\n/, + ]; + const expectedDecoretor = [ + /\@hasMany\(\(\) => Order\)\n {2}orders: Order\[\];\n/, + /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'customerClassCustNumber'\}\)/, + /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'customerClassTypeCustNumber'\}\)/, + ]; + + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('add import hasMany, import for target model and hasMany decorator ', async () => { + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[i], + ); + + assert.file(expectedSourceFile); + assert.fileContent(expectedSourceFile, hasManyrImportRegEx); + assert.fileContent(expectedSourceFile, expectedImport[i]); + + assert.fileContent(expectedSourceFile, expectedDecoretor[i]); + }); + } + }); + + context('generate model relation with custom relation name', () => { + const promptArray = [ + { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + relationName: 'myOrders', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClass', + destinationModel: 'OrderClass', + relationName: 'myOrders', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClassType', + destinationModel: 'OrderClassType', + relationName: 'myOrders', + }, + ]; + + const expectedImport = [ + /import \{Order\} from '\.\/order\.model';\n/, + /import \{OrderClass\} from '\.\/order-class\.model';\n/, + /import \{OrderClassType\} from '\.\/order-class-type\.model';\n/, + ]; + const expectedDecoretor = [ + /\@hasMany\(\(\) => Order\)\n {2}myOrders: Order\[\];\n/, + /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'customerClassCustNumber'\}\)/, + /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'customerClassTypeCustNumber'\}\)/, + ]; + const expectedProperty = [ + /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}customerId\?\: number;\n/, + /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}customerClassCustNumber\?\: number;\n/, + /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}customerClassTypeCustNumber\?\: number;\n/, + ]; + + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('relation name should be myOrders', async () => { + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[i], + ); + const expectedTargetFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + targetFileName[i], + ); + + assert.file(expectedSourceFile); + assert.fileContent(expectedSourceFile, hasManyrImportRegEx); + + assert.fileContent(expectedSourceFile, expectedImport[i]); + + assert.fileContent(expectedSourceFile, expectedDecoretor[i]); + + assert.fileContent(expectedTargetFile, expectedProperty[i]); + }); + } + }); + + context('generate model relation with custom foreignKey', () => { + const promptArray = [ + { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + foreignKeyName: 'mykey', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClass', + destinationModel: 'OrderClass', + foreignKeyName: 'mykey', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClassType', + destinationModel: 'OrderClassType', + foreignKeyName: 'mykey', + }, + ]; + + const expectedImport = [ + /import \{Order\} from '\.\/order\.model';\n/, + /import \{OrderClass\} from '\.\/order-class\.model';\n/, + /import \{OrderClassType\} from '\.\/order-class-type\.model';\n/, + ]; + const expectedDecoretor = [ + /\@hasMany\(\(\) => Order ,\{keyTo: 'mykey'\}\)\n {2}orders: Order\[\];\n/, + /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'mykey'\}\)\n {2}orderClasses: OrderClass\[\];\n/, + /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'mykey'\}\)\n {2}orderClassTypes: OrderClassType\[\];\n/, + ]; + const expectedProperty = /@property\(\{\n {4}type: 'number',\n {2}\}\)\n {2}mykey\?\: number;\n/; + + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('add the keyTo to the source model', async () => { + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[i], + ); + const expectedTargetFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + targetFileName[i], + ); + + assert.file(expectedSourceFile); + assert.fileContent(expectedSourceFile, hasManyrImportRegEx); + + assert.fileContent(expectedSourceFile, expectedImport[i]); + + assert.fileContent(expectedSourceFile, expectedDecoretor[i]); + + assert.fileContent(expectedTargetFile, expectedProperty); + }); + } + }); + + context('generate model relation with default relation name', () => { + const promptArray = [ + { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClass', + destinationModel: 'OrderClass', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClassType', + destinationModel: 'OrderClassType', + }, + ]; + + const expectedDecoretor = [ + /\@hasMany\(\(\) => Order\)\n {2}orders: Order\[\];\n/, + /\@hasMany\(\(\) => OrderClass ,\{keyTo: 'customerClassCustNumber'\}\)\n/, + /\@hasMany\(\(\) => OrderClassType ,\{keyTo: 'customerClassTypeCustNumber'\}\)\n/, + ]; + const defaultRelationName = ['orders', 'orderClasses', 'orderClassTypes']; + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + it('relation name should be ' + defaultRelationName[i], async () => { + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + + const expectedSourceFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + sourceFileName[i], + ); + + assert.fileContent(expectedSourceFile, expectedDecoretor[i]); + }); + } + }); + + context('check if the controller file created ', () => { + const promptArray = [ + { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClass', + destinationModel: 'OrderClass', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClassType', + destinationModel: 'OrderClassType', + }, + ]; + const controllerClass = [ + /class CustomerOrderController/, + /class CustomerClassOrderClassController/, + /class CustomerClassTypeOrderClassTypeController/, + ]; + const controllerConstructor = [ + /constructor\(\n {4}\@repository\(CustomerRepository\) protected customerRepository: CustomerRepository,\n {2}\) \{ \}\n/, + /constructor\(\n {4}\@repository\(CustomerClassRepository\) protected customerClassRepository: CustomerClassRepository,\n {2}\) \{ \}\n/, + /constructor\(\n {4}\@repository\(CustomerClassTypeRepository\) protected customerClassTypeRepository: CustomerClassTypeRepository,\n {2}\) \{ \}\n/, + ]; + + const indexExport = [ + /export \* from '\.\/customer-order\.controller';/, + /export \* from '\.\/customer-class-order-class\.controller';/, + /export \* from '\.\/customer-class-type-order-class-type\.controller';/, + ]; + const sourceClassnames = ['Customer', 'CustomerClass', 'CustomerClassType']; + const targetClassnames = ['Order', 'OrderClass', 'OrderClassType']; + + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it('new controller file created', async () => { + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + assert.file(expectedControllerFile); + }); + + it('controller with hasMany class and constractor', async () => { + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + assert.fileContent(expectedControllerFile, controllerClass[i]); + assert.fileContent(expectedControllerFile, controllerConstructor[i]); + }); + + it('the new controller file added to index.ts file', async () => { + const expectedControllerIndexFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + 'index.ts', + ); + + assert.fileContent(expectedControllerIndexFile, indexExport[i]); + }); + + it( + 'controller GET Array of ' + + targetClassnames[i] + + "'s belonging to " + + sourceClassnames[i], + async () => { + const getOrdersByCustomerIdRegEx = [ + /\@get\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'Array of Order\\'s belonging to Customer',\n/, + /content: {\n {10}'application\/json': {\n/, + /schema: {type: 'array', items: getModelSchemaRef\(Order\)},/, + /},\n . {6}},\n . {4}},\n . {2}},\n {2}}\)\n/, + /async find\(\n . {2}\@param.path.number\('id'\) id: number,\n/, + /\@param.query.object\('filter'\) filter\?: Filter,\n/, + /\)\: Promise {\n/, + /return this\.customerRepository\.orders\(id\)\.find\(filter\);\n {2}}\n/, + ]; + const getOrdersClassByCustomerClassIdRegEx = [ + /\@get\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'Array of OrderClass\\'s belonging to CustomerClass',\n/, + /content: {\n {10}'application\/json': {\n/, + /schema: {type: 'array', items: getModelSchemaRef\(OrderClass\)},/, + /},\n . {6}},\n . {4}},\n . {2}},\n {2}}\)\n/, + /async find\(\n . {2}\@param.path.number\('id'\) id: number,\n/, + /\@param.query.object\('filter'\) filter\?: Filter,\n/, + /\)\: Promise {\n/, + /return this\.customerClassRepository\.orderClasses\(id\)\.find\(filter\);\n {2}}\n/, + ]; + const getOrdersClassTypeByCustomerClassTypeIdRegEx = [ + /\@get\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'Array of OrderClassType\\'s belonging to CustomerClassType',\n/, + /content: {\n {10}'application\/json': {\n/, + /schema: {type: 'array', items: getModelSchemaRef\(OrderClassType\)},/, + /},\n . {6}},\n . {4}},\n . {2}},\n {2}}\)\n/, + /async find\(\n . {2}\@param.path.number\('id'\) id: number,\n/, + /\@param.query.object\('filter'\) filter\?: Filter,\n/, + /\)\: Promise {\n/, + /return this\.customerClassTypeRepository\.orderClassTypes\(id\).find\(filter\);\n {2}}\n/, + ]; + + const getRegEx = [ + getOrdersByCustomerIdRegEx, + getOrdersClassByCustomerClassIdRegEx, + getOrdersClassTypeByCustomerClassTypeIdRegEx, + ]; + + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + getRegEx[i].forEach(regex => { + assert.fileContent(expectedControllerFile, regex); + }); + }, + ); + + it( + 'controller POST ' + targetClassnames[i] + ' to ' + sourceClassnames[i], + async () => { + const postClassCreateRegEx = [ + /\@post\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'Customer model instance',\n/, + /content: {'application\/json': {schema: getModelSchemaRef\(Order\)}},\n/, + /},\n . {2}},\n .}\)\n {2}async create\(\n/, + /\@param\.path\.number\('id'\) id: typeof Customer\.prototype\.id,\n/, + /\@requestBody\(\{\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(Order, {\n/, + /exclude: \['id'\],\n/, + /optional: \['customerId'\]\n/, + /}\),\s+},\s+},\s+}\) order: Omit,\n/, + /\): Promise {\n/, + /return this\.customerRepository\.orders\(id\)\.create\(order\);\n {2}}\n/, + ]; + const postMultiWordClassCreateRegEx = [ + /\@post\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'CustomerClass model instance',\n/, + /content: {'application\/json': {schema: getModelSchemaRef\(OrderClass\)}},\n/, + /},\n . {2}},\n .}\)\n {2}async create\(\n/, + /\@param\.path\.number\('id'\) id: typeof CustomerClass\.prototype\.custNumber,\n/, + /\@requestBody\(\{\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClass, {\n/, + /exclude: \['orderNumber'\],\n/, + /optional: \['customerClassCustNumber'\]\n/, + /}\),\s+},\s+},\s+}\) orderClass: Omit,\n/, + /\): Promise {\n/, + /return this\.customerClassRepository\.orderClasses\(id\)\.create\(orderClass\);\n {2}}\n/, + ]; + const postTypeClassCreateRegEx = [ + /\@post\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'CustomerClassType model instance',\n/, + /content: {'application\/json': {schema: getModelSchemaRef\(OrderClassType\)}},\n/, + /},\n . {2}},\n .}\)\n {2}async create\(\n/, + /\@param\.path\.number\('id'\) id: typeof CustomerClassType\.prototype\.custNumber,\n/, + /\@requestBody\(\{\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClassType, {\n/, + /exclude: \['orderString'\],\n/, + /optional: \['customerClassTypeCustNumber'\]\n/, + /}\),\s+},\s+},\s+}\) orderClassType: Omit,\n/, + /\): Promise {\n/, + /return this\.customerClassTypeRepository\.orderClassTypes\(id\)\.create\(orderClassType\);\n {2}}\n/, + ]; + + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + + const postRegEx = [ + postClassCreateRegEx, + postMultiWordClassCreateRegEx, + postTypeClassCreateRegEx, + ]; + + postRegEx[i].forEach(regex => { + assert.fileContent(expectedControllerFile, regex); + }); + }, + ); + + it( + 'controller ' + + targetClassnames[i] + + ' PATCH by ' + + sourceClassnames[i] + + ' id', + async () => { + const updateOrderByCustomerIdRegEx = [ + /\@patch\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'Customer.Order PATCH success count',\n/, + /content: {'application\/json': {schema: CountSchema}},\n/, + /},\n {4}},\n {2}}\)\n {2}async patch\(\n/, + /\@param\.path\.number\('id'\) id: number,\n {4}/, + /\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(Order, {partial: true}\),\s+},\s+},\s+}\)\s+order: Partial,\n/, + /\@param\.query\.object\('where', getWhereSchemaFor\(Order\)\) where\?: Where,\n/, + /\): Promise {\n/, + /return this\.customerRepository\.orders\(id\).patch\(order, where\);\n {2}}\n/, + ]; + + const updateOrderClassByCustomerClassIdRegEx = [ + /\@patch\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'CustomerClass.OrderClass PATCH success count',\n/, + /content: {'application\/json': {schema: CountSchema}},\n/, + /},\n {4}},\n {2}}\)\n {2}async patch\(\n/, + /\@param\.path\.number\('id'\) id: number,\n {4}/, + /\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClass, {partial: true}\),\s+},\s+},\s+}\)\s+orderClass: Partial,\n/, + /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClass\)\) where\?: Where,\n/, + /\): Promise {\n/, + /return this\.customerClassRepository\.orderClasses\(id\)\.patch\(orderClass, where\);\n {2}}\n/, + ]; + + const updateOrderClassByCustomerClassTypeIdRegEx = [ + /\@patch\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'CustomerClassType.OrderClassType PATCH success count',\n/, + /content: {'application\/json': {schema: CountSchema}},\n/, + /},\n {4}},\n {2}}\)\n {2}async patch\(\n/, + /\@param\.path\.number\('id'\) id: number,\n {4}/, + /\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClassType, {partial: true}\),\s+},\s+},\s+}\)\s+orderClassType: Partial,\n/, + /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClassType\)\) where\?: Where,\n/, + /\): Promise {\n/, + /return this\.customerClassTypeRepository\.orderClassTypes\(id\).patch\(orderClassType, where\);\n {2}}\n/, + ]; + + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + + const updateRegEx = [ + updateOrderByCustomerIdRegEx, + updateOrderClassByCustomerClassIdRegEx, + updateOrderClassByCustomerClassTypeIdRegEx, + ]; + + updateRegEx[i].forEach(regex => { + assert.fileContent(expectedControllerFile, regex); + }); + }, + ); + + it( + 'controller ' + + targetClassnames[i] + + ' DELETE by ' + + sourceClassnames[i] + + ' id', + async () => { + const deleteOrderByCustomerIdRegEx = [ + /\@del\('\/customers\/{id}\/orders', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'Customer.Order DELETE success count',\n/, + /content: {'application\/json': {schema: CountSchema}},\n/, + /},\n {4}},\n {2}}\)\n {2}async delete\(\n/, + /\@param\.path\.number\('id'\) id: number,\n /, + /\@param\.query\.object\('where', getWhereSchemaFor\(Order\)\) where\?: Where,\n/, + /\): Promise {\n/, + /return this\.customerRepository\.orders\(id\)\.delete\(where\);\n {2}}\n}\n/, + ]; + + const deleteOrderClassByCustomerClassIdRegEx = [ + /\@del\('\/customer-classes\/{id}\/order-classes', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'CustomerClass.OrderClass DELETE success count',\n/, + /content: {'application\/json': {schema: CountSchema}},\n/, + /},\n {4}},\n {2}}\)\n {2}async delete\(\n/, + /\@param\.path\.number\('id'\) id: number,\n /, + /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClass\)\) where\?: Where,\n/, + /\): Promise {\n/, + /return this\.customerClassRepository\.orderClasses\(id\)\.delete\(where\);\n {2}}\n}\n/, + ]; + + const deleteOrderClassTypeByCustomerClassTypeIdRegEx = [ + /\@del\('\/customer-class-types\/{id}\/order-class-types', {\n {4}responses: {\n {6}'200': {\n/, + /description: 'CustomerClassType.OrderClassType DELETE success count',\n/, + /content: {'application\/json': {schema: CountSchema}},\n/, + /},\n {4}},\n {2}}\)\n {2}async delete\(\n/, + /\@param\.path\.number\('id'\) id: number,\n /, + /\@param\.query\.object\('where', getWhereSchemaFor\(OrderClassType\)\) where\?: Where,\n/, + /\): Promise {\n/, + /return this\.customerClassTypeRepository\.orderClassTypes\(id\)\.delete\(where\);\n {2}}\n}\n/, + ]; + + const expectedControllerFile = path.join( + SANDBOX_PATH, + CONTROLLER_PATH, + controllerFileName[i], + ); + + const deleteRegEx = [ + deleteOrderByCustomerIdRegEx, + deleteOrderClassByCustomerClassIdRegEx, + deleteOrderClassTypeByCustomerClassTypeIdRegEx, + ]; + + deleteRegEx[i].forEach(regex => { + assert.fileContent(expectedControllerFile, regex); + }); + }, + ); + } + }); + + context('check source class repository ', () => { + const promptArray = [ + { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClass', + destinationModel: 'OrderClass', + }, + { + relationType: 'hasMany', + sourceModel: 'CustomerClassType', + destinationModel: 'OrderClassType', + }, + ]; + + const sourceClassnames = ['Customer', 'CustomerClass', 'CustomerClassType']; + promptArray.forEach(function(multiItemPrompt, i) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt, i); + }); + }); + + function suite(multiItemPrompt, i) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + }); + + it(sourceClassnames[i] + ' repository has all imports', async () => { + const repositoryBasicImports = [ + /repository, HasManyRepositoryFactory} from '\@loopback\/repository';\n/, + /import \{inject, Getter\} from '\@loopback\/core';/, + ]; + + const repositoryClassImport = [ + /import \{OrderRepository\} from '\.\/order\.repository';/, + /import \{Customer, Order\} from '\.\.\/models';/, + ]; + const repositoryMultiWordClassImport = [ + /import \{OrderClassRepository\} from '\.\/order-class\.repository';/, + /import \{CustomerClass, OrderClass\} from '\.\.\/models';/, + ]; + const repositoryTypeClassImport = [ + /import \{OrderClassTypeRepository\} from '\.\/order-class-type\.repository';/, + /import \{CustomerClassType, OrderClassType\} from '\.\.\/models';/, + ]; + + const sourceRepositoryFile = path.join( + SANDBOX_PATH, + REPOSITORY_APP_PATH, + repositoryFileName[i], + ); + + repositoryBasicImports.forEach(regex => { + assert.fileContent(sourceRepositoryFile, regex); + }); + + const importRegEx = [ + repositoryClassImport, + repositoryMultiWordClassImport, + repositoryTypeClassImport, + ]; + + importRegEx[i].forEach(regex => { + assert.fileContent(sourceRepositoryFile, regex); + }); + }); + + it('repository has updated constructor', async () => { + const singleWordClassConstractor = [ + /public readonly orders: HasManyRepositoryFactory;\n/, + /constructor\(\@inject\('datasources\.db'\) dataSource: DbDataSource, \@repository\.getter\('OrderRepository'\) protected orderRepositoryGetter: Getter,\) \{\n/, + /super\(Customer, dataSource\);\n {4}this.orders = this.createHasManyRepositoryFactoryFor\('orders', orderRepositoryGetter,\);\n {2}\}\n/, + ]; + + const multiWordClassConstractor = [ + /public readonly orderClasses: HasManyRepositoryFactory;\n/, + /constructor\(\@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, \@repository\.getter\('OrderClassRepository'\) protected orderClassRepositoryGetter: Getter,\) \{\n/, + /super\(CustomerClass, dataSource\);\n {4}this\.orderClasses = this\.createHasManyRepositoryFactoryFor\('orderClasses', orderClassRepositoryGetter,\);\n {2}\}\n/, + ]; + const typeClassConstractor = [ + /public readonly orderClassTypes: HasManyRepositoryFactory;\n/, + /constructor\(@inject\('datasources\.myDB'\) dataSource: MyDBDataSource, @repository\.getter\('OrderClassTypeRepository'\) protected orderClassTypeRepositoryGetter: Getter,\) \{\n/, + /super\(CustomerClassType, dataSource\);\n {4}this\.orderClassTypes = this\.createHasManyRepositoryFactoryFor\('orderClassTypes', orderClassTypeRepositoryGetter,\);\n {2}\}/, + ]; + + const sourceRepositoryFile = path.join( + SANDBOX_PATH, + REPOSITORY_APP_PATH, + repositoryFileName[i], + ); + + const updateConstructorRegEx = [ + singleWordClassConstractor, + multiWordClassConstractor, + typeClassConstractor, + ]; + updateConstructorRegEx[i].forEach(regex => { + assert.fileContent(sourceRepositoryFile, regex); + }); + }); + } + }); +}); diff --git a/packages/cli/test/integration/generators/relation.integration.js b/packages/cli/test/integration/generators/relation.integration.js index 8f7b8813104b..d7f2409e6ebf 100644 --- a/packages/cli/test/integration/generators/relation.integration.js +++ b/packages/cli/test/integration/generators/relation.integration.js @@ -12,18 +12,18 @@ const expect = testlab.expect; const TestSandbox = testlab.TestSandbox; const generator = path.join(__dirname, '../../../generators/relation'); const SANDBOX_FILES = require('../../fixtures/relation').SANDBOX_FILES2; -const SANDBOX_FILES3 = require('../../fixtures/relation').SANDBOX_FILES3; const SANDBOX_FILES4 = require('../../fixtures/relation').SANDBOX_FILES4; -const SANDBOX_FILES5 = require('../../fixtures/relation').SANDBOX_FILES5; -const SANDBOX_FILES6 = require('../../fixtures/relation').SANDBOX_FILES6; const testUtils = require('../../test-utils'); // Test Sandbox const SANDBOX_PATH = path.resolve(__dirname, '..', '.sandbox'); const CONTROLLER_PATH = 'src/controllers'; -const MODEL_PATH = 'src/models'; const sandbox = new TestSandbox(SANDBOX_PATH); +// In this test suite, we test scenarios that apply to all relation types +// See `relation-{type}.integration.ts` files for test cases specific +// to different relation types. + describe('lb4 relation', function() { // eslint-disable-next-line no-invalid-this this.timeout(30000); @@ -129,54 +129,8 @@ describe('lb4 relation', function() { ).to.be.rejectedWith(/\"blabla\" model does not exist\./); }); }); - context( - 'Execute hasMany relation with missing primary key in source model', - () => { - it("rejects relation when source model doesn't have primary Key", () => { - const prompt = { - relationType: 'hasMany', - sourceModel: 'Nokey', - destinationModel: 'Customer', - }; - - return expect( - testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(prompt), - ).to.be.rejectedWith(/Source model primary key does not exist\./); - }); - }, - ); - context( - 'Execute belongsTo relation with missing primary key in destination model', - () => { - it("rejects relation when destination model doesn't have primary Key", () => { - const prompt = { - relationType: 'belongsTo', - sourceModel: 'Customer', - destinationModel: 'Nokey', - }; - - return expect( - testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES, - }), - ) - .withPrompts(prompt), - ).to.be.rejectedWith(/Target model primary key does not exist/); - }); - }, - ); - context('add new controller to exisiting index file', () => { + context('add new controller to existing index file', () => { it('check if the controller exported to index file ', async () => { const prompt = { relationType: 'hasMany', @@ -233,73 +187,4 @@ describe('lb4 relation', function() { ); }); }); - - context('Execute relation when models does not exist', () => { - it('rejects relation when models does not exist', () => { - const prompt = { - relationType: 'belongsTo', - sourceModel: 'Customer', - destinationModel: 'Nokey', - }; - - return expect( - testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES3, - }), - ) - .withPrompts(prompt), - ).to.be.rejectedWith(/No models found/); - }); - }); - - context('Execute relation when property already exist in the model', () => { - it('rejects relation when relation already exist in the model', () => { - const prompt = { - relationType: 'hasMany', - sourceModel: 'Customer', - destinationModel: 'Order', - }; - - return expect( - testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES5, - }), - ) - .withPrompts(prompt), - ).to.be.rejectedWith( - /relational property orders already exist in the model Customer/, - ); - }); - it('update property decorator when property already exist in the model', async () => { - const prompt = { - relationType: 'belongsTo', - sourceModel: 'Order', - destinationModel: 'Customer', - }; - - await testUtils - .executeGenerator(generator) - .inDir(SANDBOX_PATH, () => - testUtils.givenLBProject(SANDBOX_PATH, { - additionalFiles: SANDBOX_FILES6, - }), - ) - .withPrompts(prompt); - - const expectedFile = path.join( - SANDBOX_PATH, - MODEL_PATH, - 'order.model.ts', - ); - - const relationalPropertyRegEx = /\@belongsTo\(\(\) \=\> Customer\)/; - assert.fileContent(expectedFile, relationalPropertyRegEx); - }); - }); });