/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable unused-imports/no-unused-vars-ts */ import { INestApplication, UsePipes } from '@nestjs/common'; import { Args, Field, GraphQLModule, ID, InputType, Mutation, ObjectType, OmitType, Query, Resolver, } from '@nestjs/graphql'; import { Test, TestingModule } from '@nestjs/testing'; import * as Joi from 'joi'; import { JoiSchema, JoiSchemaExtends } from 'joi-class-decorators'; import { CREATE, JoiPipe, JoiValidationGroups } from 'nestjs-joi'; import * as request from 'supertest'; describe('NestJS GraphQL integration', () => { @ObjectType() class Entity { @Field(() => ID) id!: string; @Field({}) @JoiSchema(Joi.string().valid('default').required()) @JoiSchema([JoiValidationGroups.CREATE], Joi.string().valid('create').required()) @JoiSchema([JoiValidationGroups.UPDATE], Joi.string().valid('update').required()) prop!: string; } @InputType() @JoiSchemaExtends(Entity) class CreateEntityInput extends OmitType(Entity, ['id'], InputType) {} @Resolver() class EntityResolver { @Query(() => Entity) entity(): Entity { return this.getEntity(); } @Mutation(() => Entity) create_ConstructorInArgs(@Args('input', JoiPipe) input: CreateEntityInput): Entity { return this.getEntity(); } @Mutation(() => Entity) create_InstanceInArgs(@Args('input', new JoiPipe()) input: CreateEntityInput): Entity { return this.getEntity(); } @Mutation(() => Entity) create_InstanceInArgsWithGroup( @Args('input', new JoiPipe({ group: CREATE })) input: CreateEntityInput, ): Entity { return this.getEntity(); } @Mutation(() => Entity) @UsePipes(JoiPipe) create_ConstructorInUsePipes(@Args('input') input: CreateEntityInput): Entity { return this.getEntity(); } @Mutation(() => Entity) @UsePipes(new JoiPipe()) create_InstanceInUsePipes(@Args('input') input: CreateEntityInput): Entity { return this.getEntity(); } @Mutation(() => Entity) @UsePipes(new JoiPipe({ group: CREATE })) create_InstanceInUsePipesWithGroup(@Args('input') input: CreateEntityInput): Entity { return this.getEntity(); } @Mutation(() => Entity) create_NoPipe(@Args('input') input: CreateEntityInput): Entity { return this.getEntity(); } private getEntity(): Entity { const entity = new Entity(); entity.id = '1'; entity.prop = 'newentity'; return entity; } } let module: TestingModule; let app: INestApplication; describe('with pipes defined in resolver', () => { beforeEach(async () => { module = await Test.createTestingModule({ imports: [ GraphQLModule.forRoot({ autoSchemaFile: true, }), ], providers: [EntityResolver], }).compile(); app = module.createNestApplication(); await app.init(); }); afterEach(async () => { await app.close(); }); const CASES = [ { title: '@Args(JoiPipe)', mutation: 'create_ConstructorInArgs', propValue: 'default', }, { title: '@Args(new JoiPipe)', mutation: 'create_InstanceInArgs', propValue: 'default', }, { title: '@Args(new JoiPipe(CREATE))', mutation: 'create_InstanceInArgsWithGroup', propValue: 'create', }, { title: '@UsePipes(JoiPipe)', mutation: 'create_ConstructorInUsePipes', propValue: 'default', }, { title: '@UsePipes(new JoiPipe)', mutation: 'create_InstanceInUsePipes', propValue: 'default', }, { title: '@UsePipes(new JoiPipe(CREATE))', mutation: 'create_InstanceInUsePipesWithGroup', propValue: 'create', }, ]; for (const { title, mutation, propValue } of CASES) { describe(title, () => { it('should use the pipe correctly (positive test)', async () => { return request(app.getHttpServer()) .post('/graphql') .send({ operationName: mutation, variables: { CreateEntityInput: { prop: propValue, }, }, query: ` mutation ${mutation}($CreateEntityInput: CreateEntityInput!) { ${mutation}(input: $CreateEntityInput) { id prop } } `, }) .expect(res => expect(res.body).toEqual({ data: { [mutation]: { id: '1', prop: 'newentity', }, }, }), ); }); it('should use the pipe correctly (negative test)', async () => { return request(app.getHttpServer()) .post('/graphql') .send({ operationName: mutation, variables: { CreateEntityInput: { prop: 'NOT' + propValue, }, }, query: ` mutation ${mutation}($CreateEntityInput: CreateEntityInput!) { ${mutation}(input: $CreateEntityInput) { id prop } } `, }) .expect(res => expect(res.body.errors[0].message).toContain(`"prop" must be [${propValue}]`), ); }); }); } }); describe('with app.useGlobalPipes(new JoiPipe)', () => { beforeEach(async () => { module = await Test.createTestingModule({ imports: [ GraphQLModule.forRoot({ autoSchemaFile: true, }), ], providers: [EntityResolver], }).compile(); app = module.createNestApplication(); app.useGlobalPipes(new JoiPipe()); await app.init(); }); afterEach(async () => { await app.close(); }); it('should use the pipe correctly (positive test)', async () => { return request(app.getHttpServer()) .post('/graphql') .send({ operationName: 'create_NoPipe', variables: { CreateEntityInput: { prop: 'default', }, }, query: ` mutation create_NoPipe($CreateEntityInput: CreateEntityInput!) { create_NoPipe(input: $CreateEntityInput) { id prop } } `, }) .expect(res => expect(res.body).toEqual({ data: { create_NoPipe: { id: '1', prop: 'newentity', }, }, }), ); }); it('should use the pipe correctly (negative test)', async () => { return request(app.getHttpServer()) .post('/graphql') .send({ operationName: 'create_NoPipe', variables: { CreateEntityInput: { prop: 'NOT' + 'default', }, }, query: ` mutation create_NoPipe($CreateEntityInput: CreateEntityInput!) { create_NoPipe(input: $CreateEntityInput) { id prop } } `, }) .expect(res => expect(res.body.errors[0].message).toContain(`"prop" must be [default]`)); }); }); describe('with app.useGlobalPipes(new JoiPipe(group))', () => { beforeEach(async () => { module = await Test.createTestingModule({ imports: [ GraphQLModule.forRoot({ autoSchemaFile: true, }), ], providers: [EntityResolver], }).compile(); app = module.createNestApplication(); app.useGlobalPipes(new JoiPipe({ group: CREATE })); await app.init(); }); afterEach(async () => { await app.close(); }); it('should use the pipe correctly (positive test)', async () => { return request(app.getHttpServer()) .post('/graphql') .send({ operationName: 'create_NoPipe', variables: { CreateEntityInput: { prop: 'create', }, }, query: ` mutation create_NoPipe($CreateEntityInput: CreateEntityInput!) { create_NoPipe(input: $CreateEntityInput) { id prop } } `, }) .expect(res => expect(res.body).toEqual({ data: { create_NoPipe: { id: '1', prop: 'newentity', }, }, }), ); }); it('should use the pipe correctly (negative test)', async () => { return request(app.getHttpServer()) .post('/graphql') .send({ operationName: 'create_NoPipe', variables: { CreateEntityInput: { prop: 'NOT' + 'create', }, }, query: ` mutation create_NoPipe($CreateEntityInput: CreateEntityInput!) { create_NoPipe(input: $CreateEntityInput) { id prop } } `, }) .expect(res => expect(res.body.errors[0].message).toContain(`"prop" must be [create]`)); }); }); });