Skip to content

Commit

Permalink
Merge pull request #34 from doug-martin/issue19
Browse files Browse the repository at this point in the history
Issue19
  • Loading branch information
doug-martin authored Feb 24, 2020
2 parents 4639d64 + b822fc2 commit 718daeb
Show file tree
Hide file tree
Showing 17 changed files with 402 additions and 17 deletions.
4 changes: 4 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.3.5

* [FIXED] Validate Input for Create & Update [#19](https://github.com/doug-martin/nestjs-query/issues/19)

# v0.3.4

* [FIXED] Can't remove on Many-To-Many relations [#31](https://github.com/doug-martin/nestjs-query/issues/31)
Expand Down
12 changes: 11 additions & 1 deletion examples/nest-graphql-typeorm/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ import { AppModule } from './app.module';

async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));

app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
skipMissingProperties: false,
forbidUnknownValues: true,
}),
);

await app.listen(3000);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { IsString, MaxLength, IsBoolean } from 'class-validator';
import { Field, InputType } from 'type-graphql';

@InputType('TodoItemInput')
export class TodoItemInputDTO {
@IsString()
@MaxLength(20)
@Field()
title!: string;

@IsBoolean()
@Field()
completed!: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import 'reflect-metadata';
import { ID, ObjectType } from 'type-graphql';
import * as nestGraphql from '@nestjs/graphql';
import { instance, mock, when, deepEqual } from 'ts-mockito';
import { instance, mock, when, objectContaining } from 'ts-mockito';
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { QueryService, DeepPartial } from '@nestjs-query/core';
import { IsString } from 'class-validator';
Expand Down Expand Up @@ -133,7 +133,7 @@ describe('CreateResolver', () => {
stringField: 'foo',
};
const resolver = new TestResolver(instance(mockService));
when(mockService.createOne(deepEqual(args.input))).thenResolve(output);
when(mockService.createOne(objectContaining(args.input))).thenResolve(output);
const result = await resolver.createOne(args);
return expect(result).toEqual(output);
});
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('CreateResolver', () => {
},
];
const resolver = new TestResolver(instance(mockService));
when(mockService.createMany(deepEqual(args.input))).thenResolve(output);
when(mockService.createMany(objectContaining(args.input))).thenResolve(output);
const result = await resolver.createMany(args);
return expect(result).toEqual(output);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,55 @@
import 'reflect-metadata';
import * as typeGraphql from 'type-graphql';
import { plainToClass } from 'class-transformer';
import { validateSync } from 'class-validator';
import { DeleteManyArgsType } from '../../src';

describe('DeleteManyArgsType', (): void => {
const argsTypeSpy = jest.spyOn(typeGraphql, 'ArgsType');
const fieldSpy = jest.spyOn(typeGraphql, 'Field');

class FakeFilter {}
it('should create an args type with an array field', () => {
class FakeFilter {}
DeleteManyArgsType(FakeFilter);
expect(argsTypeSpy).toBeCalledWith();
expect(argsTypeSpy).toBeCalledTimes(1);
expect(fieldSpy.mock.calls[0]![0]!()).toEqual(FakeFilter);
});

describe('validation', () => {
it('should validate the filter is defined', () => {
const Type = DeleteManyArgsType(FakeFilter);
const input = {};
const it = plainToClass(Type, input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmptyObject: 'input must be a non-empty object',
},
property: 'input',
target: input,
},
]);
});

it('should validate the filter is not empty', () => {
const Type = DeleteManyArgsType(FakeFilter);
const input = { input: {} };
const it = plainToClass(Type, input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmptyObject: 'input must be a non-empty object',
},
property: 'input',
target: input,
value: input.input,
},
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'reflect-metadata';
import * as typeGraphql from 'type-graphql';
import { plainToClass } from 'class-transformer';
import { validateSync } from 'class-validator';
import { DeleteOneArgsType } from '../../src';

describe('DeleteOneArgsType', (): void => {
Expand All @@ -13,4 +15,39 @@ describe('DeleteOneArgsType', (): void => {
expect(fieldSpy).toBeCalledTimes(1);
expect(fieldSpy.mock.calls[0]![0]!()).toEqual(typeGraphql.ID);
});

describe('validation', () => {
it('should validate the id is defined', () => {
const input = {};
const it = plainToClass(DeleteOneArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'input should not be empty',
},
property: 'input',
target: input,
},
]);
});

it('should validate the id is not empty', () => {
const input = { input: '' };
const it = plainToClass(DeleteOneArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'input should not be empty',
},
property: 'input',
target: input,
value: '',
},
]);
});
});
});
69 changes: 69 additions & 0 deletions packages/query-graphql/__tests__/types/relation-args.type.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'reflect-metadata';
import * as typeGraphql from 'type-graphql';
import { plainToClass } from 'class-transformer';
import { validateSync } from 'class-validator';
import { RelationArgsType } from '../../src';

describe('RelationArgsType', (): void => {
Expand All @@ -22,4 +23,72 @@ describe('RelationArgsType', (): void => {
expect(it.id).toEqual(input.id);
expect(it.relationId).toEqual(input.relationId);
});

describe('validation', () => {
it('should validate the id is defined', () => {
const input = { relationId: 1 };
const it = plainToClass(RelationArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'id should not be empty',
},
property: 'id',
target: input,
},
]);
});

it('should validate the id is not empty', () => {
const input = { id: '', relationId: 1 };
const it = plainToClass(RelationArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'id should not be empty',
},
property: 'id',
target: input,
value: '',
},
]);
});

it('should validate that relationId is defined', () => {
const input = { id: 1 };
const it = plainToClass(RelationArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'relationId should not be empty',
},
property: 'relationId',
target: input,
},
]);
});

it('should validate that relationId is not empty', () => {
const input: RelationArgsType = { id: 1, relationId: '' };
const it = plainToClass(RelationArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'relationId should not be empty',
},
property: 'relationId',
target: input,
value: input.relationId,
},
]);
});
});
});
87 changes: 87 additions & 0 deletions packages/query-graphql/__tests__/types/relations-args.type.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'reflect-metadata';
import * as typeGraphql from 'type-graphql';
import { plainToClass } from 'class-transformer';
import { validateSync } from 'class-validator';
import { RelationsArgsType } from '../../src';

describe('RelationsArgsType', (): void => {
Expand All @@ -22,4 +23,90 @@ describe('RelationsArgsType', (): void => {
expect(it.id).toEqual(input.id);
expect(it.relationIds).toEqual(input.relationIds);
});

describe('validation', () => {
it('should validate the id is defined', () => {
const input = { relationIds: [2, 3, 4] };
const it = plainToClass(RelationsArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'id should not be empty',
},
property: 'id',
target: input,
},
]);
});

it('should validate the id is not empty', () => {
const input = { id: '', relationIds: [2, 3, 4] };
const it = plainToClass(RelationsArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'id should not be empty',
},
property: 'id',
target: input,
value: '',
},
]);
});

it('should validate that relationsIds is not empty', () => {
const input: RelationsArgsType = { id: 1, relationIds: [] };
const it = plainToClass(RelationsArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
arrayNotEmpty: 'relationIds should not be empty',
},
property: 'relationIds',
target: input,
value: input.relationIds,
},
]);
});

it('should validate that relationsIds is unique', () => {
const input: RelationsArgsType = { id: 1, relationIds: [1, 2, 1, 2] };
const it = plainToClass(RelationsArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
arrayUnique: "All relationIds's elements must be unique",
},
property: 'relationIds',
target: input,
value: input.relationIds,
},
]);
});

it('should validate that relationsIds does not contain an empty id', () => {
const input: RelationsArgsType = { id: 1, relationIds: [''] };
const it = plainToClass(RelationsArgsType(), input);
const errors = validateSync(it);
expect(errors).toEqual([
{
children: [],
constraints: {
isNotEmpty: 'each value in relationIds should not be empty',
},
property: 'relationIds',
target: input,
value: input.relationIds,
},
]);
});
});
});
Loading

0 comments on commit 718daeb

Please sign in to comment.