Skip to main content
Glama

Tinder API MCP Server

validation.test.ts18.2 kB
import { z } from 'zod'; import { Request, Response, NextFunction } from 'express'; import { validationService, ValidationService, ValidationOptions, ValidationResult } from '../../utils/validation'; import { ApiError } from '../../utils/error-handler'; import { ErrorCodes } from '../../types'; import { schemaRegistry, SchemaId } from '../../schemas/registry'; import logger from '../../utils/logger'; // Mock dependencies jest.mock('../../utils/logger'); jest.mock('../../schemas/registry'); describe('Validation Service', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('getInstance', () => { it('should return the singleton instance', () => { // Act const instance1 = ValidationService.getInstance(); const instance2 = ValidationService.getInstance(); // Assert expect(instance1).toBe(instance2); expect(instance1).toBe(validationService); }); }); describe('validate', () => { it('should validate data against a schema from registry', () => { // Arrange const schemaId = 'test.schema' as SchemaId; const data = { name: 'John Doe' }; const validatedData = { name: 'JOHN DOE' }; // Transformed data // Mock schema registry (schemaRegistry.safeValidate as jest.Mock).mockReturnValue({ success: true, data: validatedData }); // Act const result = validationService.validate(schemaId, data); // Assert expect(schemaRegistry.safeValidate).toHaveBeenCalledWith(schemaId, data); expect(result).toEqual({ success: true, data: validatedData }); }); it('should return validation errors when validation fails', () => { // Arrange const schemaId = 'test.schema' as SchemaId; const data = { age: 'not-a-number' }; const mockError = new z.ZodError([ { code: z.ZodIssueCode.invalid_type, expected: 'number', received: 'string', path: ['age'], message: 'Expected number, received string' } ]); // Mock schema registry (schemaRegistry.safeValidate as jest.Mock).mockReturnValue({ success: false, error: mockError }); // Mock formatZodError jest.spyOn(validationService, 'formatZodError').mockReturnValue('age: Expected number, received string'); // Act const result = validationService.validate(schemaId, data); // Assert expect(schemaRegistry.safeValidate).toHaveBeenCalledWith(schemaId, data); expect(result).toEqual({ success: false, errors: mockError, errorMessage: 'age: Expected number, received string' }); expect(validationService.formatZodError).toHaveBeenCalledWith(mockError); }); it('should handle validation timeout', async () => { // Arrange const schemaId = 'test.schema' as SchemaId; const data = { name: 'John Doe' }; // Mock setTimeout to trigger timeout immediately jest.useFakeTimers(); // Act const validatePromise = validationService.validate(schemaId, data, { timeout: 100 }); // Advance timers to trigger timeout jest.advanceTimersByTime(101); // Assert await expect(validatePromise).rejects.toThrow('Validation timeout exceeded'); // Restore timers jest.useRealTimers(); }); it('should validate data size before schema validation', () => { // Arrange const schemaId = 'test.schema' as SchemaId; const largeString = 'a'.repeat(1000001); // Exceeds maxStringLength // Spy on validateDataSize const validateDataSizeSpy = jest.spyOn(validationService as any, 'validateDataSize'); validateDataSizeSpy.mockImplementation(() => { throw new Error('Input string exceeds maximum allowed length'); }); // Act const result = validationService.validate(schemaId, largeString); // Assert expect(validateDataSizeSpy).toHaveBeenCalledWith(largeString); expect(result).toEqual({ success: false, errorMessage: 'Input string exceeds maximum allowed length' }); // Restore spy validateDataSizeSpy.mockRestore(); }); it('should validate nesting depth before schema validation', () => { // Arrange const schemaId = 'test.schema' as SchemaId; const deeplyNestedData = { a: { b: { c: { d: { e: { f: { g: { h: { i: { j: { k: 'too deep' } } } } } } } } } } }; // Spy on validateNestingDepth const validateNestingDepthSpy = jest.spyOn(validationService as any, 'validateNestingDepth'); validateNestingDepthSpy.mockImplementation(() => { throw new Error('Input exceeds maximum nesting depth of 10'); }); // Act const result = validationService.validate(schemaId, deeplyNestedData); // Assert expect(validateNestingDepthSpy).toHaveBeenCalledWith(deeplyNestedData, 10); expect(result).toEqual({ success: false, errorMessage: 'Input exceeds maximum nesting depth of 10' }); // Restore spy validateNestingDepthSpy.mockRestore(); }); it('should handle errors during validation', () => { // Arrange const schemaId = 'test.schema' as SchemaId; const data = { name: 'John Doe' }; // Mock schema registry to throw error (schemaRegistry.safeValidate as jest.Mock).mockImplementation(() => { throw new Error('Unexpected error'); }); // Act const result = validationService.validate(schemaId, data); // Assert expect(schemaRegistry.safeValidate).toHaveBeenCalledWith(schemaId, data); expect(result).toEqual({ success: false, errorMessage: 'Unexpected error' }); expect(logger.error).toHaveBeenCalled(); }); }); describe('validateWithSchema', () => { it('should validate data against a schema directly', () => { // Arrange const schema = z.object({ name: z.string().transform(val => val.toUpperCase()) }); const data = { name: 'John Doe' }; // Act const result = validationService.validateWithSchema(schema, data); // Assert expect(result).toEqual({ success: true, data: { name: 'JOHN DOE' } }); }); it('should return validation errors when validation fails', () => { // Arrange const schema = z.object({ age: z.number() }); const data = { age: 'not-a-number' }; // Mock formatZodError jest.spyOn(validationService, 'formatZodError').mockReturnValue('age: Expected number, received string'); // Act const result = validationService.validateWithSchema(schema, data); // Assert expect(result.success).toBe(false); expect(result.errors).toBeDefined(); expect(result.errorMessage).toBe('age: Expected number, received string'); expect(validationService.formatZodError).toHaveBeenCalled(); }); it('should handle validation timeout', async () => { // Arrange const schema = z.object({ name: z.string() }); const data = { name: 'John Doe' }; // Mock setTimeout to trigger timeout immediately jest.useFakeTimers(); // Act const validatePromise = validationService.validateWithSchema(schema, data, { timeout: 100 }); // Advance timers to trigger timeout jest.advanceTimersByTime(101); // Assert await expect(validatePromise).rejects.toThrow('Validation timeout exceeded'); // Restore timers jest.useRealTimers(); }); it('should validate data size before schema validation', () => { // Arrange const schema = z.string(); const largeString = 'a'.repeat(1000001); // Exceeds maxStringLength // Spy on validateDataSize const validateDataSizeSpy = jest.spyOn(validationService as any, 'validateDataSize'); validateDataSizeSpy.mockImplementation(() => { throw new Error('Input string exceeds maximum allowed length'); }); // Act const result = validationService.validateWithSchema(schema, largeString); // Assert expect(validateDataSizeSpy).toHaveBeenCalledWith(largeString); expect(result).toEqual({ success: false, errorMessage: 'Input string exceeds maximum allowed length' }); // Restore spy validateDataSizeSpy.mockRestore(); }); it('should handle errors during validation', () => { // Arrange const schema = z.object({ name: z.string() }); const data = { name: 'John Doe' }; // Mock schema.safeParse to throw error jest.spyOn(schema, 'safeParse').mockImplementation(() => { throw new Error('Unexpected error'); }); // Act const result = validationService.validateWithSchema(schema, data); // Assert expect(result).toEqual({ success: false, errorMessage: 'Unexpected error' }); expect(logger.error).toHaveBeenCalled(); }); }); describe('formatZodError', () => { it('should format Zod error into readable message', () => { // Arrange const error = new z.ZodError([ { code: z.ZodIssueCode.invalid_type, expected: 'number', received: 'string', path: ['age'], message: 'Expected number, received string' }, { code: z.ZodIssueCode.too_small, minimum: 3, type: 'string', inclusive: true, path: ['name'], message: 'String must contain at least 3 character(s)' } ]); // Act const result = validationService.formatZodError(error); // Assert expect(result).toBe('age: Expected number, received string; name: String must contain at least 3 character(s)'); }); it('should handle empty path in Zod error', () => { // Arrange const error = new z.ZodError([ { code: z.ZodIssueCode.custom, path: [], message: 'Invalid input' } ]); // Act const result = validationService.formatZodError(error); // Assert expect(result).toBe('Invalid input'); }); }); describe('createValidationMiddleware', () => { it('should create middleware that validates request data', () => { // Arrange const schemaId = 'test.schema' as SchemaId; const req = { body: { name: 'John Doe' } } as Request; const res = {} as Response; const next = jest.fn(); // Mock validate method jest.spyOn(validationService, 'validate').mockReturnValue({ success: true, data: { name: 'JOHN DOE' } }); // Act const middleware = validationService.createValidationMiddleware(schemaId); middleware(req, res, next); // Assert expect(validationService.validate).toHaveBeenCalledWith(schemaId, req.body, {}); expect(req.body).toEqual({ name: 'JOHN DOE' }); expect(next).toHaveBeenCalledWith(); }); it('should pass error to next when validation fails', () => { // Arrange const schemaId = 'test.schema' as SchemaId; const req = { body: { age: 'not-a-number' } } as Request; const res = {} as Response; const next = jest.fn(); // Mock validate method jest.spyOn(validationService, 'validate').mockReturnValue({ success: false, errorMessage: 'age: Expected number, received string' }); // Act const middleware = validationService.createValidationMiddleware(schemaId); middleware(req, res, next); // Assert expect(validationService.validate).toHaveBeenCalledWith(schemaId, req.body, {}); expect(next).toHaveBeenCalledWith(expect.any(ApiError)); expect(next.mock.calls[0][0]).toBeInstanceOf(ApiError); expect(next.mock.calls[0][0].code).toBe(ErrorCodes.VALIDATION_ERROR); expect(next.mock.calls[0][0].message).toBe('Validation failed for body'); expect(next.mock.calls[0][0].details).toEqual({ details: 'age: Expected number, received string' }); }); it('should validate different request parts', () => { // Arrange const schemaId = 'query.schema' as SchemaId; const req = { query: { sort: 'asc' } } as Request; const res = {} as Response; const next = jest.fn(); // Mock validate method jest.spyOn(validationService, 'validate').mockReturnValue({ success: true, data: { sort: 'asc' } }); // Act const middleware = validationService.createValidationMiddleware(schemaId, 'query'); middleware(req, res, next); // Assert expect(validationService.validate).toHaveBeenCalledWith(schemaId, req.query, {}); expect(req.query).toEqual({ sort: 'asc' }); expect(next).toHaveBeenCalledWith(); }); }); describe('createSchemaValidationMiddleware', () => { it('should create middleware that validates request data with schema', () => { // Arrange const schema = z.object({ name: z.string().transform(val => val.toUpperCase()) }); const req = { body: { name: 'John Doe' } } as Request; const res = {} as Response; const next = jest.fn(); // Mock validateWithSchema method jest.spyOn(validationService, 'validateWithSchema').mockReturnValue({ success: true, data: { name: 'JOHN DOE' } }); // Act const middleware = validationService.createSchemaValidationMiddleware(schema); middleware(req, res, next); // Assert expect(validationService.validateWithSchema).toHaveBeenCalledWith(schema, req.body, {}); expect(req.body).toEqual({ name: 'JOHN DOE' }); expect(next).toHaveBeenCalledWith(); }); it('should pass error to next when validation fails', () => { // Arrange const schema = z.object({ age: z.number() }); const req = { body: { age: 'not-a-number' } } as Request; const res = {} as Response; const next = jest.fn(); // Mock validateWithSchema method jest.spyOn(validationService, 'validateWithSchema').mockReturnValue({ success: false, errorMessage: 'age: Expected number, received string' }); // Act const middleware = validationService.createSchemaValidationMiddleware(schema); middleware(req, res, next); // Assert expect(validationService.validateWithSchema).toHaveBeenCalledWith(schema, req.body, {}); expect(next).toHaveBeenCalledWith(expect.any(ApiError)); expect(next.mock.calls[0][0]).toBeInstanceOf(ApiError); expect(next.mock.calls[0][0].code).toBe(ErrorCodes.VALIDATION_ERROR); }); }); describe('validateDataSize', () => { it('should throw error when string exceeds maximum length', () => { // Arrange const largeString = 'a'.repeat(1000001); // Exceeds default maxStringLength (1000000) // Act & Assert expect(() => { (validationService as any).validateDataSize(largeString); }).toThrow('Input string exceeds maximum allowed length'); }); it('should throw error when array exceeds maximum length', () => { // Arrange const largeArray = Array(10001).fill('item'); // Exceeds default maxArrayLength (10000) // Act & Assert expect(() => { (validationService as any).validateDataSize(largeArray); }).toThrow('Input array exceeds maximum allowed length'); }); it('should throw error when object has too many properties', () => { // Arrange const largeObject: Record<string, string> = {}; for (let i = 0; i < 1001; i++) { // Exceeds default max properties (1000) largeObject[`key${i}`] = `value${i}`; } // Act & Assert expect(() => { (validationService as any).validateDataSize(largeObject); }).toThrow('Input object exceeds maximum allowed properties'); }); it('should not throw error for valid data', () => { // Arrange const validData = { name: 'John Doe', age: 30, email: 'john@example.com' }; // Act & Assert expect(() => { (validationService as any).validateDataSize(validData); }).not.toThrow(); }); }); describe('validateNestingDepth', () => { it('should throw error when nesting depth exceeds maximum', () => { // Arrange const deeplyNestedData = { a: { b: { c: { d: { e: { f: { g: { h: { i: { j: { k: 'too deep' } } } } } } } } } } }; const maxDepth = 5; // Act & Assert expect(() => { (validationService as any).validateNestingDepth(deeplyNestedData, maxDepth); }).toThrow(`Input exceeds maximum nesting depth of ${maxDepth}`); }); it('should not throw error for valid nesting depth', () => { // Arrange const validData = { a: { b: { c: 'valid' } } }; const maxDepth = 5; // Act & Assert expect(() => { (validationService as any).validateNestingDepth(validData, maxDepth); }).not.toThrow(); }); it('should handle arrays in nested objects', () => { // Arrange const nestedArrayData = { a: { b: [{ c: 'item1' }, { c: 'item2' }] } }; const maxDepth = 5; // Act & Assert expect(() => { (validationService as any).validateNestingDepth(nestedArrayData, maxDepth); }).not.toThrow(); }); it('should handle deeply nested arrays', () => { // Arrange const deeplyNestedArray = [[[[[[[[['too deep']]]]]]]]]; const maxDepth = 5; // Act & Assert expect(() => { (validationService as any).validateNestingDepth(deeplyNestedArray, maxDepth); }).toThrow(`Input exceeds maximum nesting depth of ${maxDepth}`); }); }); });

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/glassBead-tc/tinder-mcp-server'

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