/* eslint-disable node/no-extraneous-import */ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Env } from 'hono' import type { Hono } from 'hono' import { describe, it, expect, expectTypeOf } from 'vitest' import type { Schema } from 'zod' import { OpenAPIHono, createRoute, z } from '../src' describe('Basic - params', () => { const ParamsSchema = z.object({ id: z .string() .min(3) .openapi({ param: { name: 'id', in: 'path', }, example: '1212121', }), }) const UserSchema = z .object({ id: z.string().openapi({ example: '123', }), name: z.string().openapi({ example: 'John Doe', }), age: z.number().openapi({ example: 42, }), }) .openapi('User') const ErrorSchema = z .object({ ok: z.boolean().openapi({ example: false, }), }) .openapi('Error') const route = createRoute({ method: 'get', path: '/users/{id}', request: { params: ParamsSchema, }, responses: { 200: { content: { 'application/json': { schema: UserSchema, }, }, description: 'Get the user', }, 400: { content: { 'application/json': { schema: ErrorSchema, }, }, description: 'Error!', }, }, }) const app = new OpenAPIHono() app.openapi( route, (c) => { const { id } = c.req.valid('param') return c.jsonT({ id, age: 20, name: 'Ultra-man', }) }, (result, c) => { if (!result.success) { const res = c.jsonT( { ok: false, }, 400 ) return res } } ) app.doc('/doc', { openapi: '3.0.0', info: { version: '1.0.0', title: 'My API', }, }) it('Should return 200 response with correct contents', async () => { const res = await app.request('/users/123') expect(res.status).toBe(200) expect(await res.json()).toEqual({ id: '123', age: 20, name: 'Ultra-man', }) }) it('Should return 400 response with correct contents', async () => { const res = await app.request('/users/1') expect(res.status).toBe(400) expect(await res.json()).toEqual({ ok: false }) }) it('Should return OpenAPI documents', async () => { const res = await app.request('/doc') expect(res.status).toBe(200) expect(await res.json()).toEqual({ openapi: '3.0.0', info: { version: '1.0.0', title: 'My API' }, components: { schemas: { User: { type: 'object', properties: { id: { type: 'string', example: '123' }, name: { type: 'string', example: 'John Doe' }, age: { type: 'number', example: 42 }, }, required: ['id', 'name', 'age'], }, Error: { type: 'object', properties: { ok: { type: 'boolean', example: false } }, required: ['ok'], }, }, parameters: {}, }, paths: { '/users/{id}': { get: { parameters: [ { schema: { type: 'string', minLength: 3, example: '1212121' }, required: true, name: 'id', in: 'path', }, ], responses: { '200': { description: 'Get the user', content: { 'application/json': { schema: { $ref: '#/components/schemas/User' } } }, }, '400': { description: 'Error!', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } }, }, }, }, }, }, }) }) }) describe('Query', () => { const QuerySchema = z.object({ page: z.string().openapi({ example: '123', }), }) const BooksSchema = z .object({ titles: z.array(z.string().openapi({})), page: z.number().openapi({}), }) .openapi('Post') const route = createRoute({ method: 'get', path: '/books', request: { query: QuerySchema, }, responses: { 200: { content: { 'application/json': { schema: BooksSchema, }, }, description: 'Get books', }, }, }) const app = new OpenAPIHono() app.openapi(route, (c) => { const { page } = c.req.valid('query') return c.jsonT({ titles: ['Good title'], page: Number(page), }) }) it('Should return 200 response with correct contents', async () => { const res = await app.request('/books?page=123') expect(res.status).toBe(200) expect(await res.json()).toEqual({ titles: ['Good title'], page: 123, }) }) it('Should return 400 response with correct contents', async () => { const res = await app.request('/books') expect(res.status).toBe(400) }) }) describe('JSON', () => { const RequestSchema = z.object({ id: z.number().openapi({}), title: z.string().openapi({}), }) const PostSchema = z .object({ id: z.number().openapi({}), title: z.string().openapi({}), }) .openapi('Post') const route = createRoute({ method: 'post', path: '/posts', request: { body: { content: { 'application/json': { schema: RequestSchema, }, }, }, }, responses: { 200: { content: { 'application/json': { schema: PostSchema, }, }, description: 'Post a post', }, }, }) const app = new OpenAPIHono() app.openapi(route, (c) => { const { id, title } = c.req.valid('json') return c.jsonT({ id, title, }) }) it('Should return 200 response with correct contents', async () => { const req = new Request('http://localhost/posts', { method: 'POST', body: JSON.stringify({ id: 123, title: 'Good title', }), headers: { 'Content-Type': 'application/json', }, }) const res = await app.request(req) expect(res.status).toBe(200) expect(await res.json()).toEqual({ id: 123, title: 'Good title', }) }) it('Should return 400 response with correct contents', async () => { const req = new Request('http://localhost/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({}), }) const res = await app.request(req) expect(res.status).toBe(400) }) }) describe('Form', () => { const RequestSchema = z.object({ id: z.string().openapi({}), title: z.string().openapi({}), }) const PostSchema = z .object({ id: z.number().openapi({}), title: z.string().openapi({}), }) .openapi('Post') const route = createRoute({ method: 'post', path: '/posts', request: { body: { content: { 'application/x-www-form-urlencoded': { schema: RequestSchema, }, }, }, }, responses: { 200: { content: { 'application/json': { schema: PostSchema, }, }, description: 'Post a post', }, }, }) const app = new OpenAPIHono() app.openapi(route, (c) => { const { id, title } = c.req.valid('form') return c.jsonT({ id: Number(id), title, }) }) it('Should return 200 response with correct contents', async () => { const searchParams = new URLSearchParams() searchParams.append('id', '123') searchParams.append('title', 'Good title') const req = new Request('http://localhost/posts', { method: 'POST', body: searchParams, headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }) const res = await app.request(req) expect(res.status).toBe(200) expect(await res.json()).toEqual({ id: 123, title: 'Good title', }) }) it('Should return 400 response with correct contents', async () => { const req = new Request('http://localhost/posts', { method: 'POST', }) const res = await app.request(req) expect(res.status).toBe(400) }) }) describe('Types', () => { const RequestSchema = z.object({ id: z.number().openapi({}), title: z.string().openapi({}), }) const PostSchema = z .object({ id: z.number().openapi({}), message: z.string().openapi({}), }) .openapi('Post') const route = createRoute({ method: 'post', path: '/posts', request: { body: { content: { 'application/json': { schema: RequestSchema, }, }, }, }, responses: { 200: { content: { 'application/json': { schema: PostSchema, }, }, description: 'Post a post', }, }, }) const app = new OpenAPIHono() const appRoutes = app.openapi(route, (c) => { const data = c.req.valid('json') return c.jsonT({ id: data.id, message: 'Success', }) }) it('Should return correct types', () => { type H = Hono< Env, Schema<{ '/posts': { $post: { input: { json: { title: string id: number } } output: { id: number message: string } } } }>, '/' > expectTypeOf(appRoutes).toMatchTypeOf }) })