import { describe, expect, it } from 'vitest'; import { sharedAuthHeaders, sharedServer } from './helpers/sharedServer'; describe('server-v2 OpenAPI', () => { async function fetchOpenApi(): Promise> { const res = await fetch(`${sharedServer().base}/openapi.json`, { headers: sharedAuthHeaders(), } as never); expect(res.status).toBe(200); expect(res.headers.get('content-type')).toContain('application/json'); return (await res.json()) as Record; } it('returns a valid OpenAPI 3 document', async () => { const doc = await fetchOpenApi(); expect(doc['openapi']).toMatch(/^3\.\d+\.\d+$/); const info = asRecord(doc['info']); expect(info['title']).toBe('Kimi Code Server API'); expect(typeof info['version']).toBe('string'); }); it('covers the core /api/v1 routes v2 registers', async () => { const doc = await fetchOpenApi(); const paths = asRecord(doc['paths']); expect(paths['/api/v1/healthz']).toBeDefined(); expect(paths['/api/v1/meta']).toBeDefined(); expect(paths['/api/v1/sessions']).toBeDefined(); expect(paths['/api/v1/files']).toBeDefined(); expect(paths['/api/v1/sessions/{session_id}/fs/{*}']).toBeDefined(); }); it('projects the session-action dispatcher into archive and delete only', async () => { const doc = await fetchOpenApi(); const paths = asRecord(doc['paths']); expect(paths['/api/v1/sessions/{tail}']).toBeUndefined(); expect(paths['/api/v1/sessions/{session_id}:archive']).toBeDefined(); expect(paths['/api/v1/sessions/{session_id}:delete']).toBeDefined(); expect(paths['/api/v1/sessions/{session_id}:fork']).toBeUndefined(); expect(paths['/api/v1/sessions/{session_id}:undo']).toBeUndefined(); const archiveOp = operation(doc, '/api/v1/sessions/{session_id}:archive', 'post'); expect(archiveOp['operationId']).toBe('runSessionArchiveAction'); const params = archiveOp['parameters'] as Array>; expect(params.some((p) => p['in'] === 'path' && p['name'] === 'session_id')).toBe(true); expect(params.some((p) => p['name'] === 'tail')).toBe(false); const deleteOp = operation(doc, '/api/v1/sessions/{session_id}:delete', 'post'); expect(deleteOp['operationId']).toBe('runSessionDeleteAction'); const deleteParams = deleteOp['parameters'] as Array>; expect(deleteParams.some((p) => p['in'] === 'path' && p['name'] === 'session_id')).toBe(true); expect(deleteParams.some((p) => p['name'] === 'tail')).toBe(false); }); it('describes the file upload as multipart/form-data', async () => { const doc = await fetchOpenApi(); const uploadOp = operation(doc, '/api/v1/files', 'post'); const requestBody = asRecord(uploadOp['requestBody']); const content = asRecord(requestBody['content']); expect(content['multipart/form-data']).toBeDefined(); }); it('describes session export as a ZIP or JSON error envelope', async () => { const doc = await fetchOpenApi(); const exportOp = operation(doc, '/api/v1/sessions/{session_id}/export', 'post'); const responses = asRecord(exportOp['responses']); const response = asRecord(responses['200']); const content = asRecord(response['content']); const headers = asRecord(response['headers']); const zipSchema = asRecord(asRecord(content['application/zip'])['schema']); const errorSchema = asRecord(asRecord(content['application/json'])['schema']); const errorProperties = asRecord(errorSchema['properties']); expect(zipSchema).toMatchObject({ type: 'string', format: 'binary' }); expect(errorProperties).toMatchObject({ code: expect.any(Object), msg: expect.any(Object), data: expect.any(Object), request_id: expect.any(Object), }); expect(headers['content-disposition']).toBeDefined(); expect(headers['content-length']).toBeDefined(); expect(headers['cache-control']).toBeDefined(); }); it('represents the fs-action dispatcher as a oneOf union', async () => { const doc = await fetchOpenApi(); const fsActionOp = operation(doc, '/api/v1/sessions/{session_id}/{tail}', 'post'); const requestBody = asRecord(fsActionOp['requestBody']); const content = asRecord(requestBody['content']); const json = asRecord(content['application/json']); const schema = asRecord(json['schema']); expect(Array.isArray(schema['oneOf'])).toBe(true); }); it('documents MCP OAuth failures for auth completion', async () => { const doc = await fetchOpenApi(); const authCompleteOp = operation(doc, '/api/v2/mcp/auth:complete', 'post'); const responses = asRecord(authCompleteOp['responses']); const response = asRecord(responses['200']); const content = asRecord(response['content']); const schema = asRecord(asRecord(content['application/json'])['schema']); const variants = schema['oneOf']; expect(Array.isArray(variants)).toBe(true); expect( (variants as unknown[]).some((variant) => { const properties = asRecord(asRecord(variant)['properties']); const values = asRecord(properties['code'])['enum']; return Array.isArray(values) && values.includes(40929); }), ).toBe(true); }); }); function asRecord(value: unknown): Record { if (typeof value !== 'object' || value === null) { throw new Error('expected object'); } return value as Record; } function operation( doc: Record, path: string, method: string, ): Record { const paths = asRecord(doc['paths']); const pathItem = asRecord(paths[path]); return asRecord(pathItem[method]); }