Skip to main content
Glama
response-schema.test.ts9.12 kB
import Oas from 'oas'; import type { HttpMethods } from 'oas/types'; import type { OpenAPIV3 } from 'openapi-types'; import { describe, it, expect } from 'vitest'; import { testApp } from '../test/create-oas.ts'; import { ResponseSchemaExtractor } from './response-schema.ts'; describe('ResponseSchemaExtractor', () => { // Helper function to create properly typed schema objects function createSchema( type: 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null', properties?: Record<string, OpenAPIV3.SchemaObject>, ): OpenAPIV3.SchemaObject { const schema: OpenAPIV3.SchemaObject = { type: type as OpenAPIV3.NonArraySchemaObjectType }; if (properties) { schema.properties = properties; } return schema; } // Helper function to create properly typed response objects function createResponseObject( description: string, contentType: string, schema: OpenAPIV3.SchemaObject, ): OpenAPIV3.ResponseObject { return { description, content: { [contentType]: { schema, }, }, }; } // Creates a typed mock OAS object function createMockOas( path: string, method: string, responses: Record<string, OpenAPIV3.ResponseObject>, ): { oas: Oas; path: string; method: string } { const oas = new Oas({ openapi: '3.1.0', info: { title: 'Test API', version: '1.0.0', }, paths: { [path]: { [method]: { responses, }, }, }, }); return { oas, path, method }; } // Helper function to create a response schema extractor with test data function createExtractor( responses: Record<string, OpenAPIV3.ResponseObject> = {}, path = '/test', method: HttpMethods = 'get', ): ResponseSchemaExtractor { const { oas } = createMockOas(path, method, responses); const { app } = testApp(); // Use operation instead of getOperation which expects a full URL return ResponseSchemaExtractor.fromOp(oas.operation(path, method), app.log); } describe('getSchema', () => { it('should return null for empty responses', () => { // Arrange const extractor = createExtractor({}); // Act const schema = extractor.getSchema('200'); // Assert expect(schema).toBeNull(); }); it('should extract schema from successful response', () => { // Arrange const successSchema = createSchema('object', { success: createSchema('boolean'), }); const responses: Record<string, OpenAPIV3.ResponseObject> = { '200': createResponseObject('Success', 'application/json', successSchema), }; const extractor = createExtractor(responses); // Act const schema = extractor.getSchema('200'); // Assert // Check schema exists and has the right structure expect(schema).not.toBeNull(); expect(schema?.type).toBe('object'); // Check that properties exist and have the expected structure const properties = schema?.properties; expect(properties).toBeDefined(); // Check the success property exists and has the right type // Using bracket notation for property access from index signature expect(properties?.['success']).toBeDefined(); expect(properties?.['success']?.type).toBe('boolean'); }); it('should fallback to default response when specific status code not found', () => { // Arrange const defaultSchema = createSchema('object', { message: createSchema('string'), }); const responses: Record<string, OpenAPIV3.ResponseObject> = { default: createResponseObject('Default response', 'application/json', defaultSchema), }; const extractor = createExtractor(responses); // Act const schema = extractor.getSchema('404'); // Assert using same pattern as previous test expect(schema).not.toBeNull(); expect(schema?.type).toBe('object'); // Check properties exist const properties = schema?.properties; expect(properties).toBeDefined(); // Check message property with bracket notation expect(properties?.['message']).toBeDefined(); expect(properties?.['message']?.type).toBe('string'); }); it('should prefer application/json content type over others', () => { // Arrange const jsonSchema = createSchema('object', { data: createSchema('object'), }); const textSchema = createSchema('string'); const response: OpenAPIV3.ResponseObject = { description: 'Mixed response types', content: { 'application/json': { schema: jsonSchema }, 'text/plain': { schema: textSchema }, }, }; const responses = { '200': response }; const extractor = createExtractor(responses); // Act const schema = extractor.getSchema('200'); // Assert expect(schema).not.toBeNull(); expect(schema?.type).toBe('object'); // Should use JSON schema, not text schema expect(schema?.properties?.['data']).toBeDefined(); }); it('should handle JSON-compatible content types', () => { // Arrange const jsonApiSchema = createSchema('object', { data: createSchema('object'), }); const response: OpenAPIV3.ResponseObject = { description: 'JSON API response', content: { 'application/vnd.api+json': { schema: jsonApiSchema }, }, }; const responses = { '200': response }; const extractor = createExtractor(responses); // Act const schema = extractor.getSchema('200'); // Assert expect(schema).not.toBeNull(); expect(schema?.type).toBe('object'); expect(schema?.properties?.['data']).toBeDefined(); }); it('should fallback to first content type if no JSON types are available', () => { // Arrange const textSchema = createSchema('string'); const response: OpenAPIV3.ResponseObject = { description: 'Text response', content: { 'text/plain': { schema: textSchema }, }, }; const responses = { '200': response }; const extractor = createExtractor(responses); // Act const schema = extractor.getSchema('200'); // Assert expect(schema).not.toBeNull(); expect(schema?.type).toBe('string'); }); it('should cache schema results', () => { // Arrange const successSchema = createSchema('object'); const responses = { '200': createResponseObject('Success', 'application/json', successSchema), }; const extractor = createExtractor(responses); // Act const schema1 = extractor.getSchema('200'); const schema2 = extractor.getSchema('200'); // This should use cached result // Assert expect(schema1).toBe(schema2); // Same object reference from cache }); }); describe('statusCodes', () => { it('should return all available status codes', () => { // Arrange const responses = { '200': createResponseObject('Success', 'application/json', createSchema('object')), '400': createResponseObject('Bad Request', 'application/json', createSchema('object')), default: createResponseObject('Default', 'application/json', createSchema('object')), }; const extractor = createExtractor(responses); // Act const statusCodes = extractor.statusCodes; // Assert expect(statusCodes).toHaveLength(3); expect(statusCodes).toContain('200'); expect(statusCodes).toContain('400'); expect(statusCodes).toContain('default'); }); it('should return empty array when no responses exist', () => { // Arrange const extractor = createExtractor({}); // Act const statusCodes = extractor.statusCodes; // Assert expect(statusCodes).toEqual([]); }); }); describe('error handling', () => { it('should properly throw an error when the operation is missing required methods', () => { // Arrange - Create a real OAS instance but attempt to access a non-existent path const { app } = testApp(); // Create a real OAS instance with a valid spec const validOas = new Oas({ openapi: '3.1.0', info: { title: 'Test API', version: '1.0.0', }, paths: { '/existing-path': { get: { responses: { '200': { description: 'OK', }, }, }, }, }, }); // Use a path that doesn't exist to trigger a real error const nonExistentPointer = { path: '/non-existent-path', method: 'get' } as const; // We expect this to throw an error - the actual error is from OAS's URL validation expect(() => { ResponseSchemaExtractor.from(validOas, nonExistentPointer, app.log); }).toThrow('Invalid URL'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wycats/mcpify'

If you have feedback or need assistance with the MCP directory API, please join our Discord server