@the_cfdude/productboard-mcp
Version:
Model Context Protocol server for Productboard REST API with dynamic tool loading
165 lines (141 loc) • 5.53 kB
text/typescript
/**
* Unit tests for validation utilities
*/
import { describe, it, expect } from '@jest/globals';
import {
sanitizeString,
validateEmail,
validateUrl,
validateArray,
validateRequired,
validatePagination,
validateDate,
validateEnum,
validateRequestSize,
} from '../utils/validation.js';
import { ValidationError } from '../errors/index.js';
describe('Validation Utilities', () => {
describe('sanitizeString', () => {
it('should sanitize valid strings', () => {
expect(sanitizeString('Hello World', 'test')).toBe('Hello World');
expect(sanitizeString(' trimmed ', 'test')).toBe('trimmed');
});
it('should remove dangerous characters', () => {
expect(sanitizeString("Hello<script>alert('xss')</script>", 'test')).toBe(
"Helloscriptalert('xss')/script"
);
});
it('should throw on invalid input', () => {
expect(() => sanitizeString(123, 'test')).toThrow(ValidationError);
expect(() => sanitizeString('', 'test')).toThrow(ValidationError);
});
it('should enforce max length', () => {
const longString = 'a'.repeat(300);
expect(() => sanitizeString(longString, 'test', 255)).toThrow(
ValidationError
);
});
});
describe('validateEmail', () => {
it('should validate correct emails', () => {
expect(validateEmail('test@example.com')).toBe('test@example.com');
expect(validateEmail('User@EXAMPLE.COM')).toBe('user@example.com');
});
it('should reject invalid emails', () => {
expect(() => validateEmail('notanemail')).toThrow(ValidationError);
expect(() => validateEmail('@example.com')).toThrow(ValidationError);
expect(() => validateEmail('test@')).toThrow(ValidationError);
});
});
describe('validateUrl', () => {
it('should validate correct URLs', () => {
expect(validateUrl('https://example.com')).toBe('https://example.com');
expect(validateUrl('http://localhost:3000/path')).toBe(
'http://localhost:3000/path'
);
});
it('should reject invalid URLs', () => {
expect(() => validateUrl('not a url')).toThrow(ValidationError);
expect(() => validateUrl('ftp://example.com')).toThrow(ValidationError);
expect(() => validateUrl('javascript:alert(1)')).toThrow(ValidationError);
});
});
describe('validateArray', () => {
it('should validate arrays', () => {
expect(validateArray([1, 2, 3], 'test')).toEqual([1, 2, 3]);
expect(validateArray([], 'test')).toEqual([]);
});
it('should reject non-arrays', () => {
expect(() => validateArray('not array', 'test')).toThrow(ValidationError);
expect(() => validateArray({}, 'test')).toThrow(ValidationError);
});
it('should enforce max length', () => {
const longArray = new Array(101).fill(1);
expect(() => validateArray(longArray, 'test')).toThrow(ValidationError);
});
it('should apply validator function', () => {
const validator = (item: unknown): string => String(item).toUpperCase();
expect(validateArray(['a', 'b'], 'test', validator)).toEqual(['A', 'B']);
});
});
describe('validateRequired', () => {
it('should pass when all required fields present', () => {
expect(() =>
validateRequired({ a: 1, b: 2, c: 3 }, ['a', 'b'])
).not.toThrow();
});
it('should throw when required fields missing', () => {
expect(() =>
validateRequired({ a: 1, b: undefined }, ['a', 'b'])
).toThrow(ValidationError);
expect(() => validateRequired({ a: null }, ['a'])).toThrow(
ValidationError
);
});
});
describe('validatePagination', () => {
it('should validate correct pagination', () => {
expect(validatePagination({ limit: 50, offset: 0 })).toEqual({
limit: 50,
offset: 0,
});
expect(validatePagination({})).toEqual({ limit: 50, offset: 0 });
});
it('should reject invalid pagination', () => {
expect(() => validatePagination({ limit: 0 })).toThrow(ValidationError);
expect(() => validatePagination({ limit: 101 })).toThrow(ValidationError);
expect(() => validatePagination({ offset: -1 })).toThrow(ValidationError);
});
});
describe('validateDate', () => {
it('should validate correct dates', () => {
const date = validateDate('2024-01-01', 'test');
expect(date).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
});
it('should reject invalid dates', () => {
expect(() => validateDate('not a date', 'test')).toThrow(ValidationError);
expect(() => validateDate('2024-13-01', 'test')).toThrow(ValidationError);
});
});
describe('validateEnum', () => {
const validValues = ['red', 'green', 'blue'] as const;
it('should validate correct enum values', () => {
expect(validateEnum('red', validValues, 'color')).toBe('red');
expect(validateEnum('blue', validValues, 'color')).toBe('blue');
});
it('should reject invalid enum values', () => {
expect(() => validateEnum('yellow', validValues, 'color')).toThrow(
ValidationError
);
});
});
describe('validateRequestSize', () => {
it('should allow small payloads', () => {
expect(() => validateRequestSize({ small: 'data' })).not.toThrow();
});
it('should reject large payloads', () => {
const largeData = { data: 'x'.repeat(1024 * 1024) };
expect(() => validateRequestSize(largeData)).toThrow(ValidationError);
});
});
});