Files
supabase/apps/docs/app/api/graphql/tests/errors.collection.test.ts
Charis fba3f7bd45 feat(docs): add error code filtering to GraphQL errors endpoint (#36284)
- Add support for filtering errors by specific error codes
- Implement `errors` query that accepts array of error code IDs
- Add tests for error code filtering functionality
- Update GraphQL schema with new error filtering capabilities
2025-06-10 10:55:19 -04:00

459 lines
13 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { supabase } from '~/lib/supabase'
import { POST } from '../route'
describe('/api/graphql errors collection', () => {
it('returns a list of errors with pagination info', async () => {
// Get the expected order of errors from the database
const { data: dbErrors } = await supabase()
.schema('content')
.from('error')
.select('id, code, ...service(service:name), httpStatusCode:http_status_code, message')
.is('deleted_at', null)
.order('id', { ascending: true })
const errorsQuery = `
query {
errors(first: 2) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
code
service
httpStatusCode
message
}
}
nodes {
code
service
httpStatusCode
message
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: errorsQuery }),
})
const result = await POST(request)
const {
data: { errors },
errors: queryErrors,
} = await result.json()
expect(queryErrors).toBeUndefined()
expect(errors.totalCount).toBe(3)
expect(errors.edges).toHaveLength(2)
expect(errors.nodes).toHaveLength(2)
expect(errors.pageInfo.hasNextPage).toBe(true)
expect(errors.pageInfo.hasPreviousPage).toBe(false)
expect(errors.pageInfo.startCursor).toBeDefined()
expect(errors.pageInfo.endCursor).toBeDefined()
// Compare against the first error from the database
expect(dbErrors).not.toBe(null)
const firstDbError = dbErrors![0]
const firstError = errors.nodes[0]
expect(firstError.code).toBe(firstDbError.code)
expect(firstError.service).toBe(firstDbError.service)
expect(firstError.httpStatusCode).toBe(firstDbError.httpStatusCode)
expect(firstError.message).toBe(firstDbError.message)
const firstEdge = errors.edges[0]
expect(firstEdge.cursor).toBeDefined()
expect(firstEdge.node).toEqual(firstError)
})
it('supports cursor-based pagination', async () => {
const firstPageQuery = `
query {
errors(first: 1) {
edges {
cursor
node {
code
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
const firstRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: firstPageQuery }),
})
const firstResult = await POST(firstRequest)
const firstJson = await firstResult.json()
expect(firstJson.errors).toBeUndefined()
expect(firstJson.data.errors.edges).toHaveLength(1)
expect(firstJson.data.errors.pageInfo.hasNextPage).toBe(true)
expect(firstJson.data.errors.pageInfo.hasPreviousPage).toBe(false)
const firstCursor = firstJson.data.errors.edges[0].cursor
const secondPageQuery = `
query {
errors(first: 1, after: "${firstCursor}") {
edges {
cursor
node {
code
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
const secondRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: secondPageQuery }),
})
const secondResult = await POST(secondRequest)
const secondJson = await secondResult.json()
expect(secondJson.errors).toBeUndefined()
expect(secondJson.data.errors.edges).toHaveLength(1)
expect(secondJson.data.errors.pageInfo.hasPreviousPage).toBe(true)
expect(firstJson.data.errors.edges[0].node.code).not.toBe(
secondJson.data.errors.edges[0].node.code
)
})
it('returns empty list when paginating past available results', async () => {
// Base64 encode the UUID that's guaranteed to be after any real data
const afterCursor = Buffer.from('ffffffff-ffff-ffff-ffff-ffffffffffff', 'utf8').toString(
'base64'
)
const query = `
query {
errors(first: 1, after: "${afterCursor}") {
edges {
cursor
node {
code
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query }),
})
const result = await POST(request)
const json = await result.json()
expect(json.errors).toBeUndefined()
expect(json.data.errors.edges).toHaveLength(0)
expect(json.data.errors.pageInfo.hasNextPage).toBe(false)
expect(json.data.errors.pageInfo.hasPreviousPage).toBe(true)
})
it('supports backward pagination with last', async () => {
const lastPageQuery = `
query {
errors(last: 1) {
edges {
cursor
node {
code
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
const lastRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: lastPageQuery }),
})
const lastResult = await POST(lastRequest)
const lastJson = await lastResult.json()
expect(lastJson.errors).toBeUndefined()
expect(lastJson.data.errors.edges).toHaveLength(1)
expect(lastJson.data.errors.pageInfo.hasNextPage).toBe(false)
expect(lastJson.data.errors.pageInfo.hasPreviousPage).toBe(true)
const lastCursor = lastJson.data.errors.edges[0].cursor
const beforeLastQuery = `
query {
errors(last: 1, before: "${lastCursor}") {
edges {
cursor
node {
code
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
const beforeLastRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: beforeLastQuery }),
})
const beforeLastResult = await POST(beforeLastRequest)
const beforeLastJson = await beforeLastResult.json()
expect(beforeLastJson.errors).toBeUndefined()
expect(beforeLastJson.data.errors.edges).toHaveLength(1)
expect(beforeLastJson.data.errors.pageInfo.hasNextPage).toBe(true)
expect(beforeLastJson.data.errors.edges[0].node.code).not.toBe(
lastJson.data.errors.edges[0].node.code
)
})
it('filters by service when service argument is provided', async () => {
// First, get all errors to check we have errors from different services
const allErrorsQuery = `
query {
errors {
nodes {
service
}
}
}
`
const allRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: allErrorsQuery }),
})
const allResult = await POST(allRequest)
const allJson = await allResult.json()
expect(allJson.errors).toBeUndefined()
// Verify we have errors from multiple services
const services = new Set(allJson.data.errors.nodes.map((e: any) => e.service))
expect(services.size).toBeGreaterThan(1)
// Test filtering by AUTH service
const authErrorsQuery = `
query {
errors(service: AUTH) {
totalCount
nodes {
code
service
}
}
}
`
const authRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: authErrorsQuery }),
})
const authResult = await POST(authRequest)
const authJson = await authResult.json()
expect(authJson.errors).toBeUndefined()
// Verify all returned errors are from AUTH service
expect(authJson.data.errors.nodes.length).toBeGreaterThan(0)
expect(authJson.data.errors.nodes.every((e: any) => e.service === 'AUTH')).toBe(true)
})
it('supports service filtering with pagination', async () => {
const firstPageQuery = `
query {
errors(service: AUTH, first: 1) {
edges {
cursor
node {
code
service
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`
const firstRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: firstPageQuery }),
})
const firstResult = await POST(firstRequest)
const firstJson = await firstResult.json()
expect(firstJson.errors).toBeUndefined()
// Verify the returned error is from AUTH service
expect(firstJson.data.errors.edges[0].node.service).toBe('AUTH')
// If there are more AUTH errors, test pagination
if (firstJson.data.errors.pageInfo.hasNextPage) {
const cursor = firstJson.data.errors.pageInfo.endCursor
const secondPageQuery = `
query {
errors(service: AUTH, first: 1, after: "${cursor}") {
edges {
node {
code
service
}
}
}
}
`
const secondRequest = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: secondPageQuery }),
})
const secondResult = await POST(secondRequest)
const secondJson = await secondResult.json()
expect(secondJson.errors).toBeUndefined()
// Verify the second page also returns AUTH errors
expect(secondJson.data.errors.edges[0].node.service).toBe('AUTH')
// And it's a different error
expect(secondJson.data.errors.edges[0].node.code).not.toBe(
firstJson.data.errors.edges[0].node.code
)
}
})
it('filters by code when code argument is provided', async () => {
// First, get the first error code from the database to test with
const { data: dbErrors } = await supabase()
.schema('content')
.from('error')
.select('code')
.is('deleted_at', null)
.limit(1)
expect(dbErrors).not.toBe(null)
expect(dbErrors).toHaveLength(1)
const testCode = dbErrors![0].code
const codeFilterQuery = `
query {
errors(code: "${testCode}") {
totalCount
nodes {
code
service
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: codeFilterQuery }),
})
const result = await POST(request)
const json = await result.json()
expect(json.errors).toBeUndefined()
// Verify all returned errors have the specified code
expect(json.data.errors.nodes.length).toBeGreaterThan(0)
expect(json.data.errors.nodes.every((e: any) => e.code === testCode)).toBe(true)
})
it('filters by both service and code when both arguments are provided', async () => {
// Get an error that exists for AUTH service
const { data: authError } = await supabase()
.schema('content')
.from('error')
.select('code, ...service(service:name)')
.is('deleted_at', null)
.eq('service.name', 'AUTH')
.limit(1)
expect(authError).not.toBe(null)
expect(authError).toHaveLength(1)
const testCode = authError![0].code
const bothFiltersQuery = `
query {
errors(service: AUTH, code: "${testCode}") {
totalCount
nodes {
code
service
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: bothFiltersQuery }),
})
const result = await POST(request)
const json = await result.json()
expect(json.errors).toBeUndefined()
// Verify all returned errors match both filters
expect(json.data.errors.nodes.length).toBeGreaterThan(0)
expect(
json.data.errors.nodes.every((e: any) => e.code === testCode && e.service === 'AUTH')
).toBe(true)
})
it('returns empty list when code filter matches no errors', async () => {
const nonExistentCodeQuery = `
query {
errors(code: "NONEXISTENT_CODE_12345") {
totalCount
nodes {
code
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: nonExistentCodeQuery }),
})
const result = await POST(request)
const json = await result.json()
expect(json.errors).toBeUndefined()
expect(json.data.errors.totalCount).toBe(0)
expect(json.data.errors.nodes).toHaveLength(0)
})
})