From 97c3f3234277b28deb4bc5ef6f5cb8c4cfc29a80 Mon Sep 17 00:00:00 2001 From: Muhammad Aaqil Date: Thu, 20 Apr 2023 22:14:28 +0500 Subject: [PATCH] fix: enable control update in case of multiple belongsTo relation Signed-off-by: Muhammad Aaqil --- .../relation/belongs-to-relation.generator.js | 58 +++++++++++-- ...lation.belongs-to.integration.snapshots.js | 86 +++++++++++++++++++ .../controllers/task-employee.controller.ts | 1 + packages/cli/test/fixtures/relation/index.js | 1 + .../fixtures/relation/models/task.model.ts | 20 +++++ .../relation/repositories/task.repository.ts | 17 ++++ .../relation.belongs-to.integration.js | 49 +++++++++++ 7 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 packages/cli/test/fixtures/relation/controllers/task-employee.controller.ts create mode 100644 packages/cli/test/fixtures/relation/models/task.model.ts create mode 100644 packages/cli/test/fixtures/relation/repositories/task.repository.ts diff --git a/packages/cli/generators/relation/belongs-to-relation.generator.js b/packages/cli/generators/relation/belongs-to-relation.generator.js index f5a1d46e180d..030502ebe44b 100644 --- a/packages/cli/generators/relation/belongs-to-relation.generator.js +++ b/packages/cli/generators/relation/belongs-to-relation.generator.js @@ -65,13 +65,57 @@ module.exports = class BelongsToRelationGenerator extends ( path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), ); - this.copyTemplatedFiles(source, dest, this.artifactInfo); - await relationUtils.addExportController( - this, - path.resolve(this.artifactInfo.outDir, 'index.ts'), - this.artifactInfo.controllerClassName, - utils.toFileName(this.artifactInfo.name) + '.controller', - ); + if (this.fs.exists(dest)) { + const project = new relationUtils.AstLoopBackProject(); + const sourceFile = project.addSourceFileAtPath(dest); + const sourceClass = relationUtils.getClassObj( + sourceFile, + this.artifactInfo.controllerClassName, + ); + const structure = sourceClass.getStructure(); + structure.methods.forEach((method, index) => { + if (method.name.startsWith('get')) { + const {statements, parameters} = method; + const lastStatementIndex = statements.length - 1; + let returnStatement = statements[lastStatementIndex]; + returnStatement = returnStatement.substring( + returnStatement.indexOf('return') + 6, + returnStatement.lastIndexOf(';'), + ); + returnStatement = returnStatement.trim(); + + if (returnStatement.startsWith('[')) { + returnStatement = returnStatement.substring( + returnStatement.indexOf('[') + 1, + returnStatement.lastIndexOf(']'), + ); + } + returnStatement = `${returnStatement},\n\t this.${this.artifactInfo.paramSourceRepository}.${this.artifactInfo.relationPropertyName}(${this.artifactInfo.sourceModelPrimaryKey})`; + structure.methods[index].statements[lastStatementIndex] = + `return [${returnStatement}];`; + structure.methods[index].returnType = + `Promise[]>`; + parameters.forEach(({decorators}, paramIndex) => { + decorators.forEach((decorator, decorIndex) => { + structure.methods[index].parameters[paramIndex].decorators[ + decorIndex + ].name = `param.path.${decorator.name}`; + }); + }); + } + }); + sourceClass.set(structure); + sourceClass.formatText(); + await sourceFile.save(); + } else { + this.copyTemplatedFiles(source, dest, this.artifactInfo); + await relationUtils.addExportController( + this, + path.resolve(this.artifactInfo.outDir, 'index.ts'), + this.artifactInfo.controllerClassName, + utils.toFileName(this.artifactInfo.name) + '.controller', + ); + } } async generateModels(options) { diff --git a/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js b/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js index 3c7de80928d5..2196beebc4ad 100644 --- a/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js +++ b/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js @@ -238,6 +238,92 @@ export * from './order-customer.controller'; `; +exports[`lb4 relation checks if the controller file created for multiple relations answers {"relationType":"belongsTo","sourceModel":"Task","destinationModel":"Employee","relationName":"assignedTo"} checks controller content with belongsTo relation for multiple relations 1`] = ` +import { + repository, +} from '@loopback/repository'; +import { + param, + get, + getModelSchemaRef, +} from '@loopback/rest'; +import { + Task, + Employee, +} from '../models'; +import {TaskRepository} from '../repositories'; + +export class TaskEmployeeController { + constructor( + @repository(TaskRepository) + public taskRepository: TaskRepository, + ) { } + + @get('/tasks/{id}/employee', { + responses: { + '200': { + description: 'Employee belonging to Task', + content: { + 'application/json': { + schema: getModelSchemaRef(Employee), + }, + }, + }, + }, + }) + async getEmployee( + @param.path.number('id') id: typeof Task.prototype.id, + ): Promise { + return this.taskRepository.assignedTo(id); + } +} + +`; + + +exports[`lb4 relation checks if the controller file created for multiple relations answers {"relationType":"belongsTo","sourceModel":"Task","destinationModel":"Employee","relationName":"createdBy"} checks controller content with belongsTo relation for multiple relations 1`] = ` +import { + repository, +} from '@loopback/repository'; +import { + param, + get, + getModelSchemaRef, +} from '@loopback/rest'; +import { + Task, + Employee, +} from '../models'; +import {TaskRepository} from '../repositories'; + +export class TaskEmployeeController { + constructor( + @repository(TaskRepository) + public taskRepository: TaskRepository, + ) { } + + @get('/tasks/{id}/employee', { + responses: { + '200': { + description: 'Employee belonging to Task', + content: { + 'application/json': { + schema: getModelSchemaRef(Employee), + }, + }, + }, + }, + }) + async getEmployee( + @param.path.number('id') id: typeof Task.prototype.id, + ): Promise { + return this.taskRepository.createdBy(id); + } +} + +`; + + exports[`lb4 relation checks if the controller file created for same table relation answers {"relationType":"belongsTo","sourceModel":"Employee","destinationModel":"Employee"} checks controller content with belongsTo relation with same table 1`] = ` export class EmployeeController {} diff --git a/packages/cli/test/fixtures/relation/controllers/task-employee.controller.ts b/packages/cli/test/fixtures/relation/controllers/task-employee.controller.ts new file mode 100644 index 000000000000..8502740c1610 --- /dev/null +++ b/packages/cli/test/fixtures/relation/controllers/task-employee.controller.ts @@ -0,0 +1 @@ +export class TaskEmployeeController {} diff --git a/packages/cli/test/fixtures/relation/index.js b/packages/cli/test/fixtures/relation/index.js index 720850b57377..5ddff6ec4436 100644 --- a/packages/cli/test/fixtures/relation/index.js +++ b/packages/cli/test/fixtures/relation/index.js @@ -234,6 +234,7 @@ exports.SANDBOX_FILES = [ SourceEntries.PatientRepository, SourceEntries.AppointmentRepository, SourceEntries.EmployeeRepository, + SourceEntries.TaskRepository, SourceEntries.AccountModel, SourceEntries.CustomerModel, diff --git a/packages/cli/test/fixtures/relation/models/task.model.ts b/packages/cli/test/fixtures/relation/models/task.model.ts new file mode 100644 index 000000000000..f98862206c49 --- /dev/null +++ b/packages/cli/test/fixtures/relation/models/task.model.ts @@ -0,0 +1,20 @@ +import {Entity, model, property} from '@loopback/repository'; + +@model() +export class Task extends Entity { + @property({ + type: 'number', + id: true, + default: 0, + }) + id?: number; + + @property({ + type: 'string', + }) + title?: string; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/packages/cli/test/fixtures/relation/repositories/task.repository.ts b/packages/cli/test/fixtures/relation/repositories/task.repository.ts new file mode 100644 index 000000000000..4cae45507070 --- /dev/null +++ b/packages/cli/test/fixtures/relation/repositories/task.repository.ts @@ -0,0 +1,17 @@ +import {inject} from '@loopback/core'; +import {BelongsToAccessor, DefaultCrudRepository} from '@loopback/repository'; +import {DbDataSource} from '../datasources'; +import {Customer, Task} from '../models'; + +export class TaskRepository extends DefaultCrudRepository< + Task, + typeof Task.prototype.id +> { + public readonly myCustomer: BelongsToAccessor< + Customer, + typeof Task.prototype.id + >; + constructor(@inject('datasources.db') dataSource: DbDataSource) { + super(Task, dataSource); + } +} diff --git a/packages/cli/test/integration/generators/relation.belongs-to.integration.js b/packages/cli/test/integration/generators/relation.belongs-to.integration.js index b3a89f045d71..c92f226780ce 100644 --- a/packages/cli/test/integration/generators/relation.belongs-to.integration.js +++ b/packages/cli/test/integration/generators/relation.belongs-to.integration.js @@ -23,6 +23,7 @@ const sandbox = new TestSandbox(path.resolve(__dirname, '../.sandbox')); const sourceFileName = 'order.model.ts'; const controllerFileName = 'order-customer.controller.ts'; const controllerFileNameForSameTableRelation = 'employee.controller.ts'; +const controllerFileNameForMultipleRelations = 'task-employee.controller.ts'; const repositoryFileName = 'order.repository.ts'; const repositoryFileNameForSameTableRelation = 'employee.repository.ts'; // speed up tests by avoiding reading docs @@ -497,4 +498,52 @@ describe('lb4 relation', /** @this {Mocha.Suite} */ function () { }); }, ); + + context( + 'checks if the controller file created for multiple relations', + () => { + const promptArray = [ + { + relationType: 'belongsTo', + sourceModel: 'Task', + destinationModel: 'Employee', + relationName: 'createdBy', + }, + { + relationType: 'belongsTo', + sourceModel: 'Task', + destinationModel: 'Employee', + relationName: 'assignedTo', + }, + ]; + promptArray.forEach(function (multiItemPrompt) { + describe('answers ' + JSON.stringify(multiItemPrompt), () => { + suite(multiItemPrompt); + }); + }); + function suite(multiItemPrompt) { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(sandbox.path, () => + testUtils.givenLBProject(sandbox.path, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withOptions(options) + .withPrompts(multiItemPrompt); + }); + it('checks controller content with belongsTo relation for multiple relations', async () => { + const filePath = path.join( + sandbox.path, + CONTROLLER_PATH, + controllerFileNameForMultipleRelations, + ); + assert.file(filePath); + expectFileToMatchSnapshot(filePath); + }); + } + }, + ); });