Skip to content

Commit

Permalink
feat(nestjs-query): Add enableFetchAllWithNegative option to allow…
Browse files Browse the repository at this point in the history
… to return all the items with `-1` (#87)

Fixes #63

Querying the todo items from the
[example](https://tripss.github.io/nestjs-query/docs/introduction/example)

```graphql
  todoItems(paging: {first:-1}) {
    # ...
  }
```

returns the entire list.
  • Loading branch information
TriPSs committed Apr 8, 2024
2 parents bbe86c3 + ff0e245 commit 3a00c53
Show file tree
Hide file tree
Showing 30 changed files with 692 additions and 27 deletions.
22 changes: 22 additions & 0 deletions examples/fetch-all-with-negative/e2e/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Connection } from 'typeorm'

import { executeTruncate } from '../../helpers'
import { TodoItemEntity } from '../src/todo-item/todo-item.entity'

const tables = ['todo_item']
export const truncate = async (connection: Connection): Promise<void> => executeTruncate(connection, tables)

export let todoItems: TodoItemEntity[]

export const refresh = async (connection: Connection): Promise<void> => {
await truncate(connection)

const todoRepo = connection.getRepository(TodoItemEntity)

todoItems = await todoRepo.save(
Array.from({ length: 100 }, (_, index) => ({
title: `todoTitle${index + 1}`,
completed: index % 2 === 0
}))
)
}
36 changes: 36 additions & 0 deletions examples/fetch-all-with-negative/e2e/graphql-fragments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const todoItemFields = `
id
title
completed
`

export const cursorPageInfoField = `
pageInfo{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
`

export const offsetPageInfoField = `
pageInfo{
hasNextPage
hasPreviousPage
}
`

export const nodes = (fields: string): string => `
nodes{
${fields}
}
`

export const edgeNodes = (fields: string): string => `
edges {
node{
${fields}
}
cursor
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { INestApplication, ValidationPipe } from '@nestjs/common'
import { Test } from '@nestjs/testing'
import { CursorConnectionType } from '@ptc-org/nestjs-query-graphql'
import request from 'supertest'
import { Connection } from 'typeorm'

import { AppModule } from '../src/app.module'
import { TodoItemCursorFetchWithNegativeDisableDTO } from '../src/todo-item/dto/todo-item-cursor-fetch-all-disable.dto'
import { refresh } from './fixtures'
import { cursorPageInfoField, edgeNodes, todoItemFields } from './graphql-fragments'

describe('TodoItemResolver (cursor pagination - fetch all with negative disabled)', () => {
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', () => {
describe('paging', () => {
it('should not allow to fetch all the nodes', () =>
request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
variables: {},
query: `{
todoItemCursorFetchWithNegativeDisables(paging: {first: -1, after: "YXJyYXljb25uZWN0aW9uOjE="}) {
${cursorPageInfoField}
${edgeNodes(todoItemFields)}
}
}`
})
.expect(200)
.then(({ body }) => {
const { edges, pageInfo }: CursorConnectionType<TodoItemCursorFetchWithNegativeDisableDTO> =
body.data.todoItemCursorFetchWithNegativeDisables
expect(pageInfo).toEqual({
endCursor: null,
hasNextPage: false,
hasPreviousPage: false,
startCursor: null
})
expect(edges).toHaveLength(0)
}))
})
})

afterAll(async () => {
await app.close()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { INestApplication, ValidationPipe } from '@nestjs/common'
import { Test } from '@nestjs/testing'
import { CursorConnectionType } from '@ptc-org/nestjs-query-graphql'
import request from 'supertest'
import { Connection } from 'typeorm'

import { AppModule } from '../src/app.module'
import { TodoItemCursorFetchWithNegativeEnableDTO } from '../src/todo-item/dto/todo-item-cursor-fetch-all-enable.dto'
import { refresh, todoItems } from './fixtures'
import { cursorPageInfoField, edgeNodes, todoItemFields } from './graphql-fragments'

describe('TodoItemResolver (cursor pagination - fetch all with negative enabled)', () => {
let app: INestApplication

const describeIf = (condition: boolean) => (condition ? describe : describe.skip)

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', () => {
describe('paging', () => {
it('should return all the nodes', () =>
request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
variables: {},
query: `{
todoItemCursorFetchWithNegativeEnables(paging: {first: -1}) {
${cursorPageInfoField}
${edgeNodes(todoItemFields)}
}
}`
})
.expect(200)
.then(({ body }) => {
const { edges, pageInfo }: CursorConnectionType<TodoItemCursorFetchWithNegativeEnableDTO> =
body.data.todoItemCursorFetchWithNegativeEnables
expect(pageInfo).toEqual({
endCursor: 'YXJyYXljb25uZWN0aW9uOjk5',
hasNextPage: false,
hasPreviousPage: false,
startCursor: 'YXJyYXljb25uZWN0aW9uOjA='
})
expect(edges).toHaveLength(100)

expect(edges.map((e) => e.node)).toEqual(todoItems)
}))

describeIf(process.env.NESTJS_QUERY_DB_TYPE == 'postgres')('postgres', () => {
it('should return all the nodes after the given cursor', () =>
request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
variables: {},
query: `{
todoItemCursorFetchWithNegativeEnables(paging: {first: -1, after: "YXJyYXljb25uZWN0aW9uOjE="}) {
${cursorPageInfoField}
${edgeNodes(todoItemFields)}
}
}`
})
.expect(200)
.then(({ body }) => {
const { edges, pageInfo }: CursorConnectionType<TodoItemCursorFetchWithNegativeEnableDTO> =
body.data.todoItemCursorFetchWithNegativeEnables
expect(pageInfo).toEqual({
endCursor: 'YXJyYXljb25uZWN0aW9uOjk5',
hasNextPage: false,
hasPreviousPage: true,
startCursor: 'YXJyYXljb25uZWN0aW9uOjI='
})
expect(edges).toHaveLength(98)

expect(edges.map((e) => e.node)).toEqual(todoItems.slice(2))
}))
})

describeIf(process.env.NESTJS_QUERY_DB_TYPE == 'mysql')('mysql', () => {
it('should return an error when requesting all the nodes after the given cursor', () =>
request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
variables: {},
query: `{
todoItemCursorFetchWithNegativeEnables(paging: {first: -1, after: "YXJyYXljb25uZWN0aW9uOjE="}) {
${cursorPageInfoField}
${edgeNodes(todoItemFields)}
}
}`
})
.expect(200)
.then(({ body }) => {
expect(body.errors).toBeDefined()
expect(body.errors).toHaveLength(1)
expect(body.errors[0].message).toContain('RDBMS does not support OFFSET without LIMIT in SELECT statements')
expect(body.data).toBeNull()
}))
})
})
it('should return an error when request all the nodes before the given cursor', () =>
request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
variables: {},
query: `{
todoItemCursorFetchWithNegativeEnables(paging: {last: -1, before: "YXJyYXljb25uZWN0aW9uOjk4"}) {
${cursorPageInfoField}
${edgeNodes(todoItemFields)}
}
}`
})
.expect(200)
.then(({ body }) => {
const { edges, pageInfo }: CursorConnectionType<TodoItemCursorFetchWithNegativeEnableDTO> =
body.data.todoItemCursorFetchWithNegativeEnables
expect(pageInfo).toEqual({
endCursor: 'YXJyYXljb25uZWN0aW9uOjk3',
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'YXJyYXljb25uZWN0aW9uOjA='
})
expect(edges).toHaveLength(98)

expect(edges.map((e) => e.node)).toEqual(todoItems.slice(0, 98))
}))
})

afterAll(async () => {
await app.close()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { INestApplication, ValidationPipe } from '@nestjs/common'
import { Test } from '@nestjs/testing'
import { OffsetConnectionType } from '@ptc-org/nestjs-query-graphql'
import request from 'supertest'
import { Connection } from 'typeorm'

import { AppModule } from '../src/app.module'
import { TodoItemOffsetFetchWithNegativeDisableDTO } from '../src/todo-item/dto/todo-item-offset-fetch-all-disable.dto'
import { refresh } from './fixtures'
import { nodes as graphqlNodes, offsetPageInfoField, todoItemFields } from './graphql-fragments'

describe('TodoItemResolver (offset pagination - fetch all with negative disabled)', () => {
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', () => {
describe('paging', () => {
it('should not allow to fetch all the nodes', () =>
request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
variables: {},
query: `{
todoItemOffsetFetchWithNegativeDisables(paging: {limit: -1, offset: 2}) {
${offsetPageInfoField}
${graphqlNodes(todoItemFields)}
}
}`
})
.expect(200)
.then(({ body }) => {
const { nodes, pageInfo }: OffsetConnectionType<TodoItemOffsetFetchWithNegativeDisableDTO> =
body.data.todoItemOffsetFetchWithNegativeDisables
expect(pageInfo).toEqual({
hasNextPage: false,
hasPreviousPage: false
})
expect(nodes).toHaveLength(0)
}))
})
})

afterAll(async () => {
await app.close()
})
})

0 comments on commit 3a00c53

Please sign in to comment.