Skip to main content
Glama
validation.test.ts6.13 kB
import { z } from 'zod'; import { CommonSchemas, createSanitizedStringSchema, createValidationMiddleware, sanitizeString, validateAndTransform, validateBranchName, validateBuildConfigId, validateBuildParameters, validateTeamCityUrl, validateWithSchema, } from '@/utils/validation'; jest.mock('@/utils/error-logger', () => ({ errorLogger: { logError: jest.fn() }, })); describe('validation utilities', () => { describe('CommonSchemas', () => { it('validates teamCityUrl', () => { expect(CommonSchemas.teamCityUrl.safeParse('https://example.com').success).toBe(true); expect(CommonSchemas.teamCityUrl.safeParse('ftp://example.com').success).toBe(false); expect(CommonSchemas.teamCityUrl.safeParse('not-a-url').success).toBe(false); }); it('validates buildConfigId', () => { expect(CommonSchemas.buildConfigId.safeParse('Proj_Build-1.2').success).toBe(true); expect(CommonSchemas.buildConfigId.safeParse('').success).toBe(false); expect(CommonSchemas.buildConfigId.safeParse('bad id').success).toBe(false); }); it('validates branchName', () => { expect(CommonSchemas.branchName.safeParse('feature/abc').success).toBe(true); expect(CommonSchemas.branchName.safeParse('').success).toBe(false); expect(CommonSchemas.branchName.safeParse('bad name with spaces').success).toBe(false); expect(CommonSchemas.branchName.safeParse('bad~name').success).toBe(false); }); it('validates buildParameters', () => { expect( CommonSchemas.buildParameters.safeParse({ A: 'a', B: 2, C: true, D: null }).success ).toBe(true); const res = CommonSchemas.buildParameters.safeParse({ A: {} }); expect(res.success).toBe(false); }); it('validates pagination with defaults', () => { const parsed = CommonSchemas.pagination.parse({}); expect(parsed.limit).toBe(100); expect(parsed.offset).toBe(0); expect(CommonSchemas.pagination.safeParse({ limit: 0 }).success).toBe(false); }); it('validates dateRange ordering', () => { const ok = CommonSchemas.dateRange.safeParse({ from: '2024-01-01T00:00:00.000Z', to: '2024-02-01T00:00:00.000Z', }); expect(ok.success).toBe(true); const bad = CommonSchemas.dateRange.safeParse({ from: '2024-02-01T00:00:00.000Z', to: '2024-01-01T00:00:00.000Z', }); expect(bad.success).toBe(false); }); }); describe('validateWithSchema', () => { it('returns success with parsed data', () => { const schema = z.object({ x: z.number() }); const result = validateWithSchema({ x: 1 }, schema); expect(result.success).toBe(true); expect(result.data).toEqual({ x: 1 }); }); it('returns MCPValidationError on ZodError and logs', () => { const schema = z.object({ x: z.string() }); const result = validateWithSchema({ x: 1 }, schema, { operation: 'op', field: 'x' }); expect(result.success).toBe(false); expect(result.error).toBeDefined(); // eslint-disable-next-line @typescript-eslint/no-var-requires const { errorLogger } = require('@/utils/error-logger'); expect(errorLogger.logError).toHaveBeenCalled(); }); it('handles unexpected errors gracefully', () => { const badSchema = { parse: () => { throw new Error('boom'); }, } as unknown as z.ZodSchema<{ x: number }>; const result = validateWithSchema({ x: 1 }, badSchema, { operation: 't', field: 'y' }); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); }); describe('validateAndTransform', () => { it('returns parsed data on success', () => { const schema = z.object({ a: z.string() }); const out = validateAndTransform({ a: 'ok' }, schema); expect(out).toEqual({ a: 'ok' }); }); it('throws on failure', () => { const schema = z.object({ a: z.string() }); expect(() => validateAndTransform({ a: 1 }, schema)).toThrow(); }); }); describe('createValidationMiddleware', () => { it('validates input and returns value', () => { const middleware = createValidationMiddleware(z.object({ n: z.number() })); const out = middleware({ n: 2 }, 'tool'); expect(out).toEqual({ n: 2 }); }); it('throws MCPValidationError on failure', () => { const middleware = createValidationMiddleware(z.object({ n: z.number() })); expect(() => middleware({ n: 'bad' }, 'tool')).toThrow(); }); }); describe('sanitizeString and schemas', () => { it('sanitizes control chars, trims, and truncates', () => { const input = `\u0007 hello\nworld ${'x'.repeat(300)}`; const out = sanitizeString(input, 10); expect(out.length).toBe(10); expect(out.includes('\u0007')).toBe(false); expect(out.includes('\n')).toBe(false); }); it('returns empty for non-string', () => { // @ts-expect-error testing runtime behavior expect(sanitizeString(undefined)).toBe(''); }); it('createSanitizedStringSchema transforms value', () => { const schema = createSanitizedStringSchema(5); const res = schema.parse(' abcdef '); expect(res).toBe('abcde'); }); }); describe('deprecated helpers', () => { it('validateTeamCityUrl / validateBuildConfigId / validateBranchName', () => { expect(validateTeamCityUrl('https://example.com')).toBe(true); expect(validateTeamCityUrl('not-a-url')).toBe(false); expect(validateBuildConfigId('ABC_1-2.3')).toBe(true); expect(validateBuildConfigId('bad id')).toBe(false); expect(validateBranchName('main')).toBe(true); expect(validateBranchName('bad name')).toBe(false); }); it('validateBuildParameters returns errors for invalid map', () => { const ok = validateBuildParameters({ P: 'x', Q: 1 }); expect(ok.valid).toBe(true); const bad = validateBuildParameters({ P: {} as unknown as string }); expect(bad.valid).toBe(false); expect(bad.errors.length).toBeGreaterThan(0); }); }); });

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/Daghis/teamcity-mcp'

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