diff --git a/integration/crud-typeorm/companies/company.entity.ts b/integration/crud-typeorm/companies/company.entity.ts index 1562df93..27ff43b9 100644 --- a/integration/crud-typeorm/companies/company.entity.ts +++ b/integration/crud-typeorm/companies/company.entity.ts @@ -1,11 +1,12 @@ -import { Entity, Column, OneToMany } from 'typeorm'; -import { IsOptional, IsString, MaxLength, IsNotEmpty } from 'class-validator'; +import { Column, Entity, OneToMany } from 'typeorm'; +import { IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator'; import { Type } from 'class-transformer'; import { CrudValidationGroups } from '@nestjsx/crud'; import { BaseEntity } from '../base-entity'; import { User } from '../users/user.entity'; import { Project } from '../projects/project.entity'; +import { Task } from '../tasks/task.entity'; const { CREATE, UPDATE } = CrudValidationGroups; @@ -40,4 +41,7 @@ export class Company extends BaseEntity { @OneToMany((type) => Project, (p) => p.company) projects: Project[]; + + @OneToMany(() => Task, (t) => t.company) + tasks: Task[]; } diff --git a/integration/crud-typeorm/projects/project.entity.ts b/integration/crud-typeorm/projects/project.entity.ts index 9a9e2af0..e150cf79 100644 --- a/integration/crud-typeorm/projects/project.entity.ts +++ b/integration/crud-typeorm/projects/project.entity.ts @@ -1,17 +1,11 @@ -import { Entity, Column, ManyToOne, ManyToMany, JoinTable } from 'typeorm'; -import { - IsOptional, - IsString, - IsNumber, - MaxLength, - IsDefined, - IsBoolean, -} from 'class-validator'; +import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm'; +import { IsBoolean, IsDefined, IsNumber, IsOptional, IsString, MaxLength } from 'class-validator'; import { CrudValidationGroups } from '@nestjsx/crud'; import { BaseEntity } from '../base-entity'; import { Company } from '../companies/company.entity'; import { User } from '../users/user.entity'; +import { Task } from '../tasks/task.entity'; const { CREATE, UPDATE } = CrudValidationGroups; @@ -48,4 +42,7 @@ export class Project extends BaseEntity { @ManyToMany((type) => User, (u) => u.projects, { cascade: true }) @JoinTable() users?: User[]; + + @OneToMany((type) => Task, (t) => t.project) + tasks?: Task[]; } diff --git a/integration/crud-typeorm/seeds.ts b/integration/crud-typeorm/seeds.ts index b679b6d8..85c8e761 100644 --- a/integration/crud-typeorm/seeds.ts +++ b/integration/crud-typeorm/seeds.ts @@ -91,6 +91,39 @@ export class Seeds1544303473346 implements MigrationInterface { ('19@email.com', false, 2, 19), ('20@email.com', false, 2, 20); `); + + // tasks + await queryRunner.query(` + INSERT INTO public.tasks ("name", "status", "companyId", "projectId", "userId") + VALUES ('task11', 'a', 1, 1, 1), + ('task12', 'a', 1, 1, 1), + ('task13', 'a', 1, 1, 1), + ('task14', 'a', 1, 1, 1), + ('task21', 'a', 1, 2, 2), + ('task22', 'a', 1, 2, 2), + ('task23', 'a', 1, 2, 2), + ('task24', 'a', 1, 2, 2), + ('task31', 'a', 1, 3, 3), + ('task32', 'a', 1, 3, 3), + ('task33', 'a', 1, 3, 3), + ('task34', 'a', 1, 3, 3), + ('task41', 'a', 1, 4, 4), + ('task42', 'a', 1, 4, 4), + ('task43', 'a', 1, 4, 4), + ('task44', 'a', 1, 4, 4), + ('task1', 'a', 1, 1, 5), + ('task2', 'a', 1, 1, 5), + ('task3', 'a', 1, 1, 5), + ('task4', 'a', 1, 1, 5), + ('task1', 'a', 1, 1, 6), + ('task2', 'a', 1, 1, 6), + ('task3', 'a', 1, 1, 6), + ('task4', 'a', 1, 1, 6), + ('task1', 'a', 1, 1, 7), + ('task2', 'a', 1, 1, 7), + ('task3', 'a', 1, 1, 7), + ('task4', 'a', 1, 1, 7); + `); } public async down(queryRunner: QueryRunner): Promise {} diff --git a/integration/crud-typeorm/tasks/task.entity.ts b/integration/crud-typeorm/tasks/task.entity.ts new file mode 100644 index 00000000..dbc7c45e --- /dev/null +++ b/integration/crud-typeorm/tasks/task.entity.ts @@ -0,0 +1,37 @@ +import { Column, Entity, ManyToOne } from 'typeorm'; + +import { BaseEntity } from '../base-entity'; +import { User } from '../users/user.entity'; +import { Company } from '../companies/company.entity'; +import { Project } from '../projects/project.entity'; + +@Entity('tasks') +export class Task extends BaseEntity { + @Column({ type: 'varchar', length: 100, nullable: false }) + name: string; + + @Column({ type: 'varchar', nullable: false }) + status: string; + + @Column({ nullable: false }) + companyId: number; + + @Column({ nullable: false }) + projectId: number; + + @Column({ nullable: false }) + userId: number; + + /** + * Relations + */ + + @ManyToOne(() => Company, o => o.tasks) + company: Company; + + @ManyToOne(() => Project, o => o.tasks) + project: Project; + + @ManyToOne(() => User, o => o.tasks) + user: User; +} diff --git a/integration/crud-typeorm/users/user.entity.ts b/integration/crud-typeorm/users/user.entity.ts index fed7b5bd..3cfe9e02 100644 --- a/integration/crud-typeorm/users/user.entity.ts +++ b/integration/crud-typeorm/users/user.entity.ts @@ -1,13 +1,5 @@ -import { Entity, Column, JoinColumn, OneToOne, ManyToOne, ManyToMany } from 'typeorm'; -import { - IsOptional, - IsString, - MaxLength, - IsNotEmpty, - IsEmail, - IsBoolean, - ValidateNested, -} from 'class-validator'; +import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne } from 'typeorm'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { CrudValidationGroups } from '@nestjsx/crud'; @@ -15,6 +7,7 @@ import { BaseEntity } from '../base-entity'; import { UserProfile } from '../users-profiles/user-profile.entity'; import { Company } from '../companies/company.entity'; import { Project } from '../projects/project.entity'; +import { Task } from '../tasks/task.entity'; const { CREATE, UPDATE } = CrudValidationGroups; @@ -57,4 +50,7 @@ export class User extends BaseEntity { @ManyToMany((type) => Project, (c) => c.users) projects?: Project[]; + + @OneToMany((type) => Task, (t) => t.user) + tasks?: Task[]; } diff --git a/packages/crud-typeorm/test/0.basic-crud.spec.ts b/packages/crud-typeorm/test/0.basic-crud.spec.ts index f95c5c7f..1fb6f9ed 100644 --- a/packages/crud-typeorm/test/0.basic-crud.spec.ts +++ b/packages/crud-typeorm/test/0.basic-crud.spec.ts @@ -9,11 +9,10 @@ import { Crud } from '../../crud/src/decorators/crud.decorator'; import { HttpExceptionFilter } from '../../../integration/shared/https-exception.filter'; import { withCache } from '../../../integration/crud-typeorm/orm.config'; import { Company } from '../../../integration/crud-typeorm/companies'; -import { Project } from '../../../integration/crud-typeorm/projects'; import { User } from '../../../integration/crud-typeorm/users'; -import { UserProfile } from '../../../integration/crud-typeorm/users-profiles'; import { CompaniesService } from './__fixture__/companies.service'; import { UsersService } from './__fixture__/users.service'; +import { Entities } from './__fixture__/constants'; describe('#crud-typeorm', () => { describe('#basic crud', () => { @@ -27,7 +26,8 @@ describe('#crud-typeorm', () => { }) @Controller('companies') class CompaniesController { - constructor(public service: CompaniesService) {} + constructor(public service: CompaniesService) { + } } @Crud({ @@ -58,14 +58,15 @@ describe('#crud-typeorm', () => { }) @Controller('companies/:companyId/users') class UsersController { - constructor(public service: UsersService) {} + constructor(public service: UsersService) { + } } beforeAll(async () => { const fixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(withCache), - TypeOrmModule.forFeature([Company, Project, User, UserProfile]), + TypeOrmModule.forFeature(Entities), ], controllers: [CompaniesController, UsersController], providers: [ diff --git a/packages/crud-typeorm/test/1.query-params.spec.ts b/packages/crud-typeorm/test/1.query-params.spec.ts index f8e32071..6f409b64 100644 --- a/packages/crud-typeorm/test/1.query-params.spec.ts +++ b/packages/crud-typeorm/test/1.query-params.spec.ts @@ -11,10 +11,10 @@ import { withCache } from '../../../integration/crud-typeorm/orm.config'; import { Company } from '../../../integration/crud-typeorm/companies'; import { Project } from '../../../integration/crud-typeorm/projects'; import { User } from '../../../integration/crud-typeorm/users'; -import { UserProfile } from '../../../integration/crud-typeorm/users-profiles'; import { CompaniesService } from './__fixture__/companies.service'; import { UsersService } from './__fixture__/users.service'; import { ProjectsService } from './__fixture__/projects.service'; +import { Entities } from './__fixture__/constants'; describe('#crud-typeorm', () => { describe('#query params', () => { @@ -38,7 +38,8 @@ describe('#crud-typeorm', () => { }) @Controller('companies') class CompaniesController { - constructor(public service: CompaniesService) {} + constructor(public service: CompaniesService) { + } } @Crud({ @@ -57,7 +58,8 @@ describe('#crud-typeorm', () => { }) @Controller('projects') class ProjectsController { - constructor(public service: ProjectsService) {} + constructor(public service: ProjectsService) { + } } @Crud({ @@ -71,14 +73,15 @@ describe('#crud-typeorm', () => { }) @Controller('users') class UsersController { - constructor(public service: UsersService) {} + constructor(public service: UsersService) { + } } beforeAll(async () => { const fixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(withCache), - TypeOrmModule.forFeature([Company, Project, User, UserProfile]), + TypeOrmModule.forFeature(Entities), ], controllers: [CompaniesController, ProjectsController, UsersController], providers: [ diff --git a/packages/crud-typeorm/test/__fixture__/constants.ts b/packages/crud-typeorm/test/__fixture__/constants.ts new file mode 100644 index 00000000..07c4d964 --- /dev/null +++ b/packages/crud-typeorm/test/__fixture__/constants.ts @@ -0,0 +1,35 @@ +/* + * Created by Diluka on 2019-06-13. + * + * + * ----------- 神 兽 佑 我 ----------- + * ┏┓ ┏┓+ + + * ┏┛┻━━━━━━┛┻┓ + + + * ┃ ┃ + * ┣ ━ ┃ ++ + + + + * ████━████ ┃+ + * ┃ ┃ + + * ┃ ┴ ┃ + * ┃ ┃ + + + * ┗━┓ ┏━┛ Code is far away from bug + * ┃ ┃ with the animal protecting + * ┃ ┃ + + + + + * ┃ ┃ + * ┃ ┃ + + * ┃ ┃ + + + * ┃ ┃ + + * ┃ ┗━━━┓ + + + * ┃ ┣┓ + * ┃ ┏┛ + * ┗┓┓┏━━━━┳┓┏┛ + + + + + * ┃┫┫ ┃┫┫ + * ┗┻┛ ┗┻┛+ + + + + * ----------- 永 无 BUG ------------ + */ +import { Company } from '../../../../integration/crud-typeorm/companies'; +import { Project } from '../../../../integration/crud-typeorm/projects'; +import { User } from '../../../../integration/crud-typeorm/users'; +import { UserProfile } from '../../../../integration/crud-typeorm/users-profiles'; +import { Task } from '../../../../integration/crud-typeorm/tasks/task.entity'; + +export const Entities = [Company, Project, User, UserProfile, Task]; diff --git a/packages/crud-typeorm/test/__fixture__/tasks.service.ts b/packages/crud-typeorm/test/__fixture__/tasks.service.ts new file mode 100644 index 00000000..7bd02335 --- /dev/null +++ b/packages/crud-typeorm/test/__fixture__/tasks.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { TypeOrmCrudService } from '../../../crud-typeorm/src/typeorm-crud.service'; +import { Task } from '../../../../integration/crud-typeorm/tasks/task.entity'; + +@Injectable() +export class TasksService extends TypeOrmCrudService { + constructor(@InjectRepository(Task) repo) { + super(repo); + } +} diff --git a/packages/crud-typeorm/test/issues/issue-87.spec.ts b/packages/crud-typeorm/test/issues/issue-87.spec.ts new file mode 100644 index 00000000..b7a7d470 --- /dev/null +++ b/packages/crud-typeorm/test/issues/issue-87.spec.ts @@ -0,0 +1,118 @@ +import { Controller, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import * as request from 'supertest'; +import { TasksService } from '../__fixture__/tasks.service'; +import { Task } from '../../../../integration/crud-typeorm/tasks/task.entity'; +import { withCache } from '../../../../integration/crud-typeorm/orm.config'; +import { APP_FILTER } from '@nestjs/core'; +import { HttpExceptionFilter } from '../../../../integration/shared/https-exception.filter'; +import { Entities } from '../__fixture__/constants'; + +import { Crud } from '../../../crud/src/decorators/crud.decorator'; + +describe('Test to reproduce issue #87', () => { + + @Crud({ + model: { + type: Task, + }, + query: { + join: { + 'user': {}, + 'user.company': {}, + }, + }, + }) + @Controller('tasks') + class TasksController { + constructor(public service: TasksService) { + } + } + + let app: INestApplication; + let server: any; + let service: any; + + beforeAll(async () => { + const fixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(withCache), + TypeOrmModule.forFeature(Entities), + ], + controllers: [TasksController], + providers: [ + { provide: APP_FILTER, useClass: HttpExceptionFilter }, + TasksService, + ], + }).compile(); + + app = fixture.createNestApplication(); + service = app.get(TasksService); + + await app.init(); + server = app.getHttpServer(); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('GET /tasks', () => { + it('should return a list of tasks', async () => { + // When + const res = await request(server) + .get('/tasks') + .expect(200); + + expect(res.body).toEqual(expect.any(Array)); + }); + }); + + describe('GET /tasks?join=user&join=user.company', () => { + it('should return a list of tasks with nested user.company.* entities', async () => { + // When + const res = await request(server) + .get('/tasks?join=user&join=user.company') + .expect(200); + + // Then + expect(res.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + user: expect.objectContaining({ + company: expect.objectContaining({ + name: expect.any(String), + }), + }), + }), + ]), + ); + }); + }); + + describe('GET /tasks?join=user&join=user.company&filter=company.name||eq||Name1', () => { + it('should filter by nested user.company.name', async () => { + // If + const companyName = 'Name1'; + + // When + const res = await request(server) + .get(`/tasks?join=user&join=user.company&filter=company.name||eq||${companyName}`) + .expect(200); + + // Then + expect(res.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + user: expect.objectContaining({ + company: expect.objectContaining({ + name: companyName, + }), + }), + }), + ]), + ); + }); + }); +});