Skip to main content
Glama
schema-refs.test.ts9.18 kB
import type Oas from 'oas'; import type { HttpMethods } from 'oas/types'; import type { OpenAPIV3 } from 'openapi-types'; import { describe, it, expect } from 'vitest'; import { log } from '../log.ts'; import { createTestOas } from '../test/create-oas.ts'; import type { ResponseSchemaExtractor } from './response-schema.ts'; import { ResponseSchemaExtractor as ResponseSchemaExtractorClass } from './response-schema.ts'; /** * Extended schema type for testing purposes to handle $ref properties * that might appear in schemas before they are fully resolved */ interface SchemaWithRef { $ref?: string; type?: string; properties?: Record<string, SchemaWithRef>; items?: SchemaWithRef; format?: string; required?: string[]; additionalProperties?: boolean | SchemaWithRef; } /** * This test suite documents and verifies our assumptions about how $ref * is handled in OpenAPI and how it is resolved in the MCPify codebase. */ describe('Schema References Resolution', () => { /** * Helper function to create a response schema extractor for testing */ function createExtractorFromSpec( spec: Oas, path: string, method: HttpMethods, ): ResponseSchemaExtractor { // Create a new schema extractor instance return ResponseSchemaExtractorClass.fromOp(spec.operation(path, method), log); } it('should resolve $ref references in response schemas', async () => { // Arrange // Create an OpenAPI spec with references const petSchema: OpenAPIV3.SchemaObject = { type: 'object', properties: { id: { type: 'integer', format: 'int64', }, name: { type: 'string', }, status: { type: 'string', enum: ['available', 'pending', 'sold'], }, }, required: ['name'], }; const apiErrorSchema: OpenAPIV3.SchemaObject = { type: 'object', properties: { code: { type: 'integer', format: 'int32', }, message: { type: 'string', }, }, required: ['code', 'message'], }; const petsPaths: OpenAPIV3.PathsObject = { '/pets/{petId}': { get: { summary: 'Find pet by ID', description: 'Returns a single pet', operationId: 'getPetById', parameters: [ { name: 'petId', in: 'path', description: 'ID of pet to return', required: true, schema: { type: 'integer', format: 'int64', }, }, ], responses: { '200': { description: 'Successful operation', content: { 'application/json': { schema: { $ref: '#/components/schemas/Pet', }, }, }, }, '404': { description: 'Pet not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error', }, }, }, }, }, }, }, }; const components: OpenAPIV3.ComponentsObject = { schemas: { Pet: petSchema, Error: apiErrorSchema, }, }; // Create a fully normalized spec with resolved references const spec = await createTestOas(petsPaths, components); // Use the ResponseSchemaExtractor to get schemas from the parsed spec const extractor = createExtractorFromSpec(spec, '/pets/{petId}', 'get' as HttpMethods); // Get the 200 response schema const successSchema = extractor.getSchema('200'); // Get the 404 response schema const errorSchema = extractor.getSchema('404'); // Assert // Check that schemas exist expect(successSchema).not.toBeNull(); expect(errorSchema).not.toBeNull(); // In a normalized spec, we should have resolved schemas, not references const successWithRef = successSchema as SchemaWithRef; const errorWithRef = errorSchema as SchemaWithRef; // When using createNormalizedOas, we expect schema references to be resolved // The schema should be an object, not a reference expect(successSchema?.type).toBe('object'); expect(successWithRef.$ref).toBeUndefined(); expect(errorSchema?.type).toBe('object'); expect(errorWithRef.$ref).toBeUndefined(); // If the schemas are resolved (no $ref), then check their properties if (successSchema?.type === 'object') { expect(successSchema.properties).toHaveProperty('id'); expect(successSchema.properties).toHaveProperty('name'); expect(successSchema.properties).toHaveProperty('status'); expect(successSchema.properties?.['id']).toHaveProperty('type', 'integer'); } if (errorSchema?.type === 'object') { expect(errorSchema.properties).toHaveProperty('code'); expect(errorSchema.properties).toHaveProperty('message'); expect(errorSchema.properties?.['code']).toHaveProperty('type', 'integer'); } }); it('should resolve nested $ref references in response schemas', async () => { // Arrange // Create an OpenAPI spec with nested references const categorySchema: OpenAPIV3.SchemaObject = { type: 'object', properties: { id: { type: 'integer', format: 'int64', }, name: { type: 'string', }, }, }; const tagSchemaObj: OpenAPIV3.SchemaObject = { type: 'object', properties: { id: { type: 'integer', format: 'int64', }, name: { type: 'string', }, }, }; const petWithRefsSchema: OpenAPIV3.SchemaObject = { type: 'object', properties: { id: { type: 'integer', format: 'int64', }, name: { type: 'string', }, category: { $ref: '#/components/schemas/Category', }, tags: { type: 'array', items: { $ref: '#/components/schemas/Tag', }, }, }, }; const listPetsPath: OpenAPIV3.PathsObject = { '/pets': { get: { summary: 'List all pets', description: 'Returns all pets', operationId: 'listPets', responses: { '200': { description: 'Successful operation', content: { 'application/json': { schema: { type: 'array', items: { $ref: '#/components/schemas/Pet', }, }, }, }, }, }, }, }, }; const nestedComponents: OpenAPIV3.ComponentsObject = { schemas: { Category: categorySchema, Tag: tagSchemaObj, Pet: petWithRefsSchema, }, }; // Create a fully normalized spec with resolved references const spec = await createTestOas(listPetsPath, nestedComponents); // Use the ResponseSchemaExtractor to get schemas from the parsed spec const extractor = createExtractorFromSpec(spec, '/pets', 'get' as HttpMethods); // Get the 200 response schema const petsSchema = extractor.getSchema('200'); // Assert schema exists expect(petsSchema).not.toBeNull(); // When using createNormalizedOas, we expect schema references to be resolved // The schema should be an array with items, not a reference expect(petsSchema?.type).toBe('array'); expect(petsSchema?.items).toBeDefined(); // If normalized correctly, petsSchema should be an array with item objects if (petsSchema?.type === 'array' && petsSchema.items) { const petItemSchema = petsSchema.items as SchemaWithRef; // petItemSchema should be a resolved object without $ref expect(petItemSchema.type).toBe('object'); expect(petItemSchema.$ref).toBeUndefined(); expect(petItemSchema.properties).toBeDefined(); // Check category property is properly resolved if (petItemSchema.properties?.['category']) { const categorySchema = petItemSchema.properties['category']; expect(categorySchema.type).toBe('object'); expect(categorySchema.$ref).toBeUndefined(); expect(categorySchema.properties).toHaveProperty('id'); expect(categorySchema.properties).toHaveProperty('name'); } // Check tags property is properly resolved if (petItemSchema.properties?.['tags']) { const tagsSchema = petItemSchema.properties['tags']; expect(tagsSchema.type).toBe('array'); if (tagsSchema.items) { const tagItems = tagsSchema.items; expect(tagItems.type).toBe('object'); expect(tagItems.$ref).toBeUndefined(); expect(tagItems.properties).toHaveProperty('id'); expect(tagItems.properties).toHaveProperty('name'); } } } }); });

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