Skip to main content
Glama
validation.ts8.9 kB
/** * Validation utilities for TestRail MCP Server */ import { TestRailErrorCodes } from '../types'; export interface ValidationError { field: string; message: string; code: string; } export class ValidationResult { public isValid: boolean; public errors: ValidationError[]; constructor(isValid: boolean = true, errors: ValidationError[] = []) { this.isValid = isValid; this.errors = errors; } addError( field: string, message: string, code: string = TestRailErrorCodes.VALIDATION_ERROR ): void { this.errors.push({ field, message, code }); this.isValid = false; } } export class Validator { /** * Validate required fields */ static validateRequired(obj: any, fields: string[]): ValidationResult { const result = new ValidationResult(); for (const field of fields) { if (obj[field] === undefined || obj[field] === null || obj[field] === '') { result.addError(field, `Field '${field}' is required`); } } return result; } /** * Validate number fields */ static validateNumber(obj: any, field: string, min?: number, max?: number): ValidationResult { const result = new ValidationResult(); if (obj[field] !== undefined && obj[field] !== null) { const value = Number(obj[field]); if (isNaN(value)) { result.addError(field, `Field '${field}' must be a valid number`); } else { if (min !== undefined && value < min) { result.addError(field, `Field '${field}' must be at least ${min}`); } if (max !== undefined && value > max) { result.addError(field, `Field '${field}' must be at most ${max}`); } } } return result; } /** * Validate string fields */ static validateString( obj: any, field: string, minLength?: number, maxLength?: number, pattern?: RegExp ): ValidationResult { const result = new ValidationResult(); if (obj[field] !== undefined && obj[field] !== null) { const value = String(obj[field]); if (minLength !== undefined && value.length < minLength) { result.addError(field, `Field '${field}' must be at least ${minLength} characters long`); } if (maxLength !== undefined && value.length > maxLength) { result.addError(field, `Field '${field}' must be at most ${maxLength} characters long`); } if (pattern && !pattern.test(value)) { result.addError(field, `Field '${field}' does not match required pattern`); } } return result; } /** * Validate email address */ static validateEmail(email: string): ValidationResult { const result = new ValidationResult(); const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailPattern.test(email)) { result.addError('email', 'Invalid email format'); } return result; } /** * Validate URL */ static validateUrl(url: string): ValidationResult { const result = new ValidationResult(); try { new URL(url); } catch { result.addError('url', 'Invalid URL format'); } return result; } /** * Validate TestRail status ID */ static validateStatusId(statusId: number): ValidationResult { const result = new ValidationResult(); const validStatuses = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; if (!validStatuses.includes(statusId)) { result.addError( 'statusId', `Invalid status ID. Valid values are: ${validStatuses.join(', ')}` ); } return result; } /** * Validate array field */ static validateArray( obj: any, field: string, minLength?: number, maxLength?: number ): ValidationResult { const result = new ValidationResult(); if (obj[field] !== undefined && obj[field] !== null) { if (!Array.isArray(obj[field])) { result.addError(field, `Field '${field}' must be an array`); } else { const array = obj[field]; if (minLength !== undefined && array.length < minLength) { result.addError(field, `Field '${field}' must contain at least ${minLength} items`); } if (maxLength !== undefined && array.length > maxLength) { result.addError(field, `Field '${field}' must contain at most ${maxLength} items`); } } } return result; } /** * Validate object field */ static validateObject(obj: any, field: string): ValidationResult { const result = new ValidationResult(); if (obj[field] !== undefined && obj[field] !== null) { if (typeof obj[field] !== 'object' || Array.isArray(obj[field])) { result.addError(field, `Field '${field}' must be an object`); } } return result; } /** * Validate enum value */ static validateEnum(obj: any, field: string, validValues: any[]): ValidationResult { const result = new ValidationResult(); if (obj[field] !== undefined && obj[field] !== null) { if (!validValues.includes(obj[field])) { result.addError(field, `Field '${field}' must be one of: ${validValues.join(', ')}`); } } return result; } /** * Combine multiple validation results */ static combine(...results: ValidationResult[]): ValidationResult { const combined = new ValidationResult(); for (const result of results) { if (!result.isValid) { combined.isValid = false; combined.errors.push(...result.errors); } } return combined; } } /** * Common validation functions for TestRail entities */ export class TestRailValidator { /** * Validate TestRail connection input */ static validateConnectionInput(input: any): ValidationResult { return Validator.combine( Validator.validateRequired(input, ['baseUrl', 'username', 'apiKey']), Validator.validateUrl(input.baseUrl), Validator.validateEmail(input.username), Validator.validateString(input, 'apiKey', 1), Validator.validateNumber(input, 'timeout', 1000, 300000) ); } /** * Validate project creation input */ static validateCreateProjectInput(input: any): ValidationResult { return Validator.combine( Validator.validateRequired(input, ['name']), Validator.validateString(input, 'name', 1, 255), Validator.validateString(input, 'announcement', 0, 1000), Validator.validateNumber(input, 'suiteMode', 1, 3) ); } /** * Validate case creation input */ static validateCreateCaseInput(input: any): ValidationResult { return Validator.combine( Validator.validateRequired(input, ['sectionId', 'title']), Validator.validateNumber(input, 'sectionId', 1), Validator.validateString(input, 'title', 1, 255), Validator.validateNumber(input, 'templateId', 1), Validator.validateNumber(input, 'typeId', 1), Validator.validateNumber(input, 'priorityId', 1), Validator.validateNumber(input, 'milestoneId', 1), Validator.validateString(input, 'refs', 0, 255), Validator.validateString(input, 'estimate', 0, 50), Validator.validateArray(input, 'stepsDetailed', 0, 100) ); } /** * Validate run creation input */ static validateCreateRunInput(input: any): ValidationResult { return Validator.combine( Validator.validateRequired(input, ['projectId', 'name']), Validator.validateNumber(input, 'projectId', 1), Validator.validateString(input, 'name', 1, 255), Validator.validateString(input, 'description', 0, 1000), Validator.validateNumber(input, 'suiteId', 1), Validator.validateNumber(input, 'milestoneId', 1), Validator.validateNumber(input, 'assignedToId', 1), Validator.validateArray(input, 'caseIds', 0, 10000), Validator.validateArray(input, 'configIds', 0, 100) ); } /** * Validate result creation input */ static validateAddResultInput(input: any): ValidationResult { const result = Validator.combine( Validator.validateRequired(input, ['statusId']), Validator.validateStatusId(input.statusId), Validator.validateNumber(input, 'testId', 1), Validator.validateNumber(input, 'runId', 1), Validator.validateNumber(input, 'caseId', 1), Validator.validateNumber(input, 'assignedToId', 1), Validator.validateString(input, 'comment', 0, 2000), Validator.validateString(input, 'version', 0, 100), Validator.validateString(input, 'elapsed', 0, 50), Validator.validateString(input, 'defects', 0, 255), Validator.validateArray(input, 'stepResults', 0, 100) ); // Custom validation: either testId or (runId + caseId) must be provided if (!input.testId && !(input.runId && input.caseId)) { result.addError('identification', 'Either testId or both runId and caseId must be provided'); } return result; } }

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/samuelvinay91/testrail-mcp'

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