-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(query-graphql): Fixed empty object accepted by required filters
- Loading branch information
Showing
11 changed files
with
285 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Connection } from 'typeorm'; | ||
import { TodoItemEntity } from '../src/todo-item/todo-item.entity'; | ||
import { executeTruncate } from '../../helpers'; | ||
|
||
const tables = ['todo_item']; | ||
export const truncate = async (connection: Connection): Promise<void> => executeTruncate(connection, tables); | ||
|
||
export const refresh = async (connection: Connection): Promise<void> => { | ||
await truncate(connection); | ||
|
||
const todoRepo = connection.getRepository(TodoItemEntity); | ||
|
||
await todoRepo.save([ | ||
{ title: 'Create Nest App', completed: true }, | ||
{ title: 'Create Entity', completed: false }, | ||
{ title: 'Create Entity Service', completed: false }, | ||
{ title: 'Add Todo Item Resolver', completed: false }, | ||
{ | ||
title: 'How to create item With Sub Tasks', | ||
completed: false | ||
} | ||
]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export const todoItemFields = ` | ||
id | ||
title | ||
completed | ||
description | ||
`; | ||
|
||
export const pageInfoField = ` | ||
pageInfo{ | ||
hasNextPage | ||
hasPreviousPage | ||
startCursor | ||
endCursor | ||
} | ||
`; | ||
|
||
export const edgeNodes = (fields: string): string => ` | ||
edges { | ||
node{ | ||
${fields} | ||
} | ||
cursor | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { CursorConnectionType } from '@ptc-org/nestjs-query-graphql'; | ||
import { Test } from '@nestjs/testing'; | ||
import request from 'supertest'; | ||
import { INestApplication, ValidationPipe } from '@nestjs/common'; | ||
import { Connection } from 'typeorm'; | ||
import { AppModule } from '../src/app.module'; | ||
import { TodoItemDTO } from '../src/todo-item/dto/todo-item.dto'; | ||
import { refresh } from './fixtures'; | ||
import { edgeNodes, pageInfoField, todoItemFields } from './graphql-fragments'; | ||
|
||
describe('TodoItemResolver (filters - e2e)', () => { | ||
let app: INestApplication; | ||
|
||
beforeAll(async () => { | ||
const moduleRef = await Test.createTestingModule({ | ||
imports: [AppModule] | ||
}).compile(); | ||
|
||
app = moduleRef.createNestApplication(); | ||
app.useGlobalPipes( | ||
new ValidationPipe({ | ||
transform: true, | ||
whitelist: true, | ||
forbidNonWhitelisted: true, | ||
skipMissingProperties: false, | ||
forbidUnknownValues: true | ||
}) | ||
); | ||
|
||
await app.init(); | ||
await refresh(app.get(Connection)); | ||
}); | ||
|
||
afterAll(() => refresh(app.get(Connection))); | ||
|
||
describe('query', () => { | ||
it(`should require "completed" filter`, () => | ||
request(app.getHttpServer()) | ||
.post('/graphql') | ||
.send({ | ||
operationName: null, | ||
variables: {}, | ||
query: `{ | ||
todoItems { | ||
${pageInfoField} | ||
${edgeNodes(todoItemFields)} | ||
} | ||
}` | ||
}) | ||
.expect(400) | ||
.then(({ body }) => { | ||
expect(body.errors[0].message).toBe( | ||
'Field "todoItems" argument "filter" of type "TodoItemFilter!" is required, but it was not provided.' | ||
); | ||
})); | ||
|
||
it(`should accepted "completed" filter`, () => | ||
request(app.getHttpServer()) | ||
.post('/graphql') | ||
.send({ | ||
operationName: null, | ||
variables: {}, | ||
query: `{ | ||
todoItems (filter: { completed: { is: true } }) { | ||
${pageInfoField} | ||
${edgeNodes(todoItemFields)} | ||
} | ||
}` | ||
}) | ||
.expect(200) | ||
.then(({ body }) => { | ||
const { edges, pageInfo }: CursorConnectionType<TodoItemDTO> = body.data.todoItems; | ||
expect(pageInfo).toEqual({ | ||
endCursor: 'YXJyYXljb25uZWN0aW9uOjA=', | ||
hasNextPage: false, | ||
hasPreviousPage: false, | ||
startCursor: 'YXJyYXljb25uZWN0aW9uOjA=' | ||
}); | ||
expect(edges).toHaveLength(1); | ||
|
||
expect(edges.map((e) => e.node)).toEqual([ | ||
{ id: '1', title: 'Create Nest App', completed: true, description: null } | ||
]); | ||
})); | ||
|
||
it(`should not accepted empty "completed" filter`, () => | ||
request(app.getHttpServer()) | ||
.post('/graphql') | ||
.send({ | ||
operationName: null, | ||
variables: {}, | ||
query: `{ | ||
todoItems (filter: { completed: { } }) { | ||
${pageInfoField} | ||
${edgeNodes(todoItemFields)} | ||
} | ||
}` | ||
}) | ||
.expect(200) | ||
.then(({ body }) => { | ||
expect(body.errors[0].extensions.response.message[0]).toBe( | ||
'filter.There was no filter provided for "completed"!' | ||
); | ||
})); | ||
}); | ||
|
||
afterAll(async () => { | ||
await app.close(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { GraphQLModule } from '@nestjs/graphql'; | ||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
import { TodoItemModule } from './todo-item/todo-item.module'; | ||
import { typeormOrmConfig, formatGraphqlError } from '../../helpers'; | ||
|
||
@Module({ | ||
imports: [ | ||
TypeOrmModule.forRoot(typeormOrmConfig('basic')), | ||
GraphQLModule.forRoot({ | ||
autoSchemaFile: 'schema.gql', | ||
formatError: formatGraphqlError | ||
}), | ||
TodoItemModule | ||
] | ||
}) | ||
export class AppModule { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { FilterableField } from '@ptc-org/nestjs-query-graphql'; | ||
import { ObjectType, ID, GraphQLISODateTime } from '@nestjs/graphql'; | ||
|
||
@ObjectType('TodoItem') | ||
export class TodoItemDTO { | ||
@FilterableField(() => ID) | ||
id!: number; | ||
|
||
@FilterableField() | ||
title!: string; | ||
|
||
@FilterableField({ nullable: true }) | ||
description?: string; | ||
|
||
@FilterableField({ | ||
filterRequired: true | ||
}) | ||
completed!: boolean; | ||
|
||
@FilterableField(() => GraphQLISODateTime, { filterOnly: true }) | ||
created!: Date; | ||
|
||
@FilterableField(() => GraphQLISODateTime, { filterOnly: true }) | ||
updated!: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { | ||
Column, | ||
CreateDateColumn, | ||
Entity, | ||
PrimaryGeneratedColumn, | ||
UpdateDateColumn, | ||
} from 'typeorm'; | ||
|
||
@Entity({ name: 'todo_item' }) | ||
export class TodoItemEntity { | ||
@PrimaryGeneratedColumn() | ||
id!: number; | ||
|
||
@Column() | ||
title!: string; | ||
|
||
@Column({ nullable: true }) | ||
description?: string; | ||
|
||
@Column() | ||
completed!: boolean; | ||
|
||
@CreateDateColumn() | ||
created!: Date; | ||
|
||
@UpdateDateColumn() | ||
updated!: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql'; | ||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; | ||
import { Module } from '@nestjs/common'; | ||
import { TodoItemDTO } from './dto/todo-item.dto'; | ||
import { TodoItemEntity } from './todo-item.entity'; | ||
|
||
@Module({ | ||
imports: [ | ||
NestjsQueryGraphQLModule.forFeature({ | ||
imports: [NestjsQueryTypeOrmModule.forFeature([TodoItemEntity])], | ||
resolvers: [ | ||
{ | ||
DTOClass: TodoItemDTO, | ||
EntityClass: TodoItemEntity | ||
} | ||
] | ||
}) | ||
] | ||
}) | ||
export class TodoItemModule { | ||
} |
27 changes: 27 additions & 0 deletions
27
packages/query-graphql/src/decorators/has-required.filter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { FilterFieldComparison } from '@ptc-org/nestjs-query-core'; | ||
import { registerDecorator } from 'class-validator'; | ||
|
||
/** | ||
* @internal | ||
* Wraps Args to allow skipping decorating | ||
* @param check - checker to run. | ||
* @param decorators - The decorators to apply | ||
*/ | ||
export function HasRequiredFilter<T>(): PropertyDecorator { | ||
return function (object: object, propertyName: string): void { | ||
registerDecorator({ | ||
name: 'hasRequiredFilter', | ||
target: object.constructor, | ||
propertyName: propertyName, | ||
// constraints: [property], | ||
options: { | ||
message: 'There was no filter provided for "$property"!' | ||
}, | ||
validator: { | ||
validate(value: FilterFieldComparison<T>) { | ||
return Object.keys(value).length > 0; | ||
} | ||
} | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters