kimi-code / packages /kap-server /test /openapi.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw History Blame Contribute Delete
5.74 kB
import { describe, expect, it } from 'vitest';
import { sharedAuthHeaders, sharedServer } from './helpers/sharedServer';
describe('server-v2 OpenAPI', () => {
async function fetchOpenApi(): Promise<Record<string, unknown>> {
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<string, unknown>;
}
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<Record<string, unknown>>;
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<Record<string, unknown>>;
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<string, unknown> {
if (typeof value !== 'object' || value === null) {
throw new Error('expected object');
}
return value as Record<string, unknown>;
}
function operation(
doc: Record<string, unknown>,
path: string,
method: string,
): Record<string, unknown> {
const paths = asRecord(doc['paths']);
const pathItem = asRecord(paths[path]);
return asRecord(pathItem[method]);
}