78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
import { mkdtempSync, writeFileSync } from 'node:fs'
|
||
import { tmpdir } from 'node:os'
|
||
import { join } from 'node:path'
|
||
import { afterEach, describe, expect, it } from 'vitest'
|
||
import { z } from 'zod'
|
||
import { buildTestApp } from './helpers.js'
|
||
|
||
let app: Awaited<ReturnType<typeof buildTestApp>> | undefined
|
||
|
||
afterEach(async () => {
|
||
await app?.close()
|
||
app = undefined
|
||
})
|
||
|
||
describe('error handler', () => {
|
||
it('необработанная ошибка → 500 без деталей', async () => {
|
||
app = await buildTestApp()
|
||
app.get('/__boom', async () => {
|
||
throw new Error('secret internals')
|
||
})
|
||
const res = await app.inject({ method: 'GET', url: '/__boom' })
|
||
expect(res.statusCode).toBe(500)
|
||
expect(res.json()).toEqual({
|
||
error: { code: 'INTERNAL', message: 'Internal server error' },
|
||
})
|
||
expect(res.body).not.toContain('secret internals')
|
||
})
|
||
|
||
it('ZodError → 400 BAD_REQUEST', async () => {
|
||
app = await buildTestApp()
|
||
app.get('/__zod', async () => z.object({ a: z.string() }).parse({}))
|
||
const res = await app.inject({ method: 'GET', url: '/__zod' })
|
||
expect(res.statusCode).toBe(400)
|
||
expect(res.json().error.code).toBe('BAD_REQUEST')
|
||
})
|
||
|
||
it('ошибка с statusCode 4xx сохраняет статус', async () => {
|
||
app = await buildTestApp()
|
||
app.get('/__teapot', async () => {
|
||
throw Object.assign(new Error('nope'), { statusCode: 418 })
|
||
})
|
||
const res = await app.inject({ method: 'GET', url: '/__teapot' })
|
||
expect(res.statusCode).toBe(418)
|
||
})
|
||
})
|
||
|
||
describe('not found', () => {
|
||
it.each(['/oauth/nope', '/.well-known/nope', '/api/v1/nope'])(
|
||
'%s → 404 JSON',
|
||
async (url) => {
|
||
app = await buildTestApp()
|
||
const res = await app.inject({ method: 'GET', url })
|
||
expect(res.statusCode).toBe(404)
|
||
expect(res.json().error.code).toBe('NOT_FOUND')
|
||
},
|
||
)
|
||
|
||
it('без STATIC_DIR неизвестный путь → 404 JSON', async () => {
|
||
app = await buildTestApp()
|
||
const res = await app.inject({ method: 'GET', url: '/some/spa/route' })
|
||
expect(res.statusCode).toBe(404)
|
||
})
|
||
|
||
it('со STATIC_DIR: SPA-путь → index.html, /oauth и /.well-known → 404', async () => {
|
||
const dir = mkdtempSync(join(tmpdir(), 'ap-static-'))
|
||
writeFileSync(join(dir, 'index.html'), '<html>spa</html>')
|
||
app = await buildTestApp({ STATIC_DIR: dir })
|
||
const spa = await app.inject({ method: 'GET', url: '/some/spa/route' })
|
||
expect(spa.statusCode).toBe(200)
|
||
expect(spa.body).toContain('spa')
|
||
for (const url of ['/oauth/nope', '/.well-known/nope']) {
|
||
const res = await app.inject({ method: 'GET', url })
|
||
expect(res.statusCode).toBe(404)
|
||
expect(res.json().error.code).toBe('NOT_FOUND')
|
||
}
|
||
})
|
||
})
|