Skip to main content
Glama
error-handling.test.js14.8 kB
/** * Error Handling Tests - Edge cases and error conditions * * Tests for graceful handling of: * - Missing/unavailable resources * - Invalid/malformed input * - Boundary conditions * - Special characters and unicode */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { validateSearchQuery, validateLimit, validateDaysBack, validateDate, validateContact, escapeSQL, escapeAppleScript, stripHtmlTags, validateLanceDBId } from '../../lib/validators.js' // ============ EMPTY/NULL INPUT HANDLING ============ describe('Empty and Null Input Handling', () => { describe('validateSearchQuery', () => { it('should throw for null query', () => { expect(() => validateSearchQuery(null)).toThrow() }) it('should throw for undefined query', () => { expect(() => validateSearchQuery(undefined)).toThrow() }) it('should throw for empty string query', () => { expect(() => validateSearchQuery('')).toThrow() }) it('should throw for whitespace-only query', () => { expect(() => validateSearchQuery(' \t\n ')).toThrow() }) it('should handle query with only special characters', () => { // Should not throw - just return the special chars (may find nothing) const result = validateSearchQuery('!@#$%^&*()') expect(result).toBe('!@#$%^&*()') }) }) describe('validateLimit', () => { it('should return default for null', () => { const result = validateLimit(null, 10, 100) expect(result).toBe(10) }) it('should return default for undefined', () => { const result = validateLimit(undefined, 10, 100) expect(result).toBe(10) }) it('should return default for NaN', () => { const result = validateLimit(NaN, 10, 100) expect(result).toBe(10) }) it('should return default for empty string', () => { const result = validateLimit('', 10, 100) expect(result).toBe(10) }) }) describe('validateContact', () => { it('should return null for null input', () => { expect(validateContact(null)).toBeNull() }) it('should return null for undefined input', () => { expect(validateContact(undefined)).toBeNull() }) it('should return null for empty string', () => { expect(validateContact('')).toBeNull() }) it('should return null for whitespace-only', () => { expect(validateContact(' ')).toBeNull() }) }) describe('validateDate', () => { it('should return null for null input', () => { expect(validateDate(null)).toBeNull() }) it('should return null for undefined input', () => { expect(validateDate(undefined)).toBeNull() }) it('should return null for invalid date string', () => { expect(validateDate('not-a-date')).toBeNull() }) it('should return null for empty string', () => { expect(validateDate('')).toBeNull() }) }) }) // ============ VERY LONG INPUT HANDLING ============ describe('Very Long Input Handling', () => { describe('validateSearchQuery with long input', () => { it('should truncate queries over 1000 characters', () => { const longQuery = 'x'.repeat(2000) const result = validateSearchQuery(longQuery) expect(result.length).toBeLessThanOrEqual(1000) }) it('should handle 10,000 character query without crashing', () => { const veryLongQuery = 'search term '.repeat(1000) expect(() => validateSearchQuery(veryLongQuery)).not.toThrow() }) it('should preserve meaningful content when truncating', () => { const query = 'important keyword ' + 'x'.repeat(2000) const result = validateSearchQuery(query, 100) expect(result).toContain('important') }) }) describe('escapeSQL with long input', () => { it('should handle very long SQL strings', () => { const longString = "test's value ".repeat(500) const result = escapeSQL(longString) expect(result).toContain("''") // Escaped quotes expect(result.length).toBeGreaterThan(longString.length) // Escaping adds chars }) }) describe('stripHtmlTags with long input', () => { it('should handle very long HTML without hanging', () => { const longHtml = '<div>content</div>'.repeat(1000) const start = Date.now() const result = stripHtmlTags(longHtml) const elapsed = Date.now() - start expect(elapsed).toBeLessThan(1000) // Should complete in under 1 second expect(result).not.toContain('<div>') }) }) }) // ============ SPECIAL CHARACTERS AND UNICODE ============ describe('Special Characters and Unicode', () => { describe('Unicode in search queries', () => { it('should handle Japanese characters', () => { const query = '日本語検索' const result = validateSearchQuery(query) expect(result).toBe(query) }) it('should handle Chinese characters', () => { const query = '中文搜索测试' const result = validateSearchQuery(query) expect(result).toBe(query) }) it('should handle Arabic characters', () => { const query = 'بحث عربي' const result = validateSearchQuery(query) expect(result).toBe(query) }) it('should handle emoji in queries', () => { const query = 'meeting 📅 with team 👥' const result = validateSearchQuery(query) expect(result).toContain('📅') expect(result).toContain('👥') }) it('should handle mixed unicode and ASCII', () => { const query = 'John の meeting about 项目' const result = validateSearchQuery(query) expect(result).toBe(query) }) }) describe('Regex special characters in search', () => { it('should handle regex metacharacters safely', () => { const query = 'test.*pattern+more?' const result = validateSearchQuery(query) expect(result).toBe(query) }) it('should handle brackets and parentheses', () => { const query = 'function(param) [array]' const result = validateSearchQuery(query) expect(result).toBe(query) }) it('should handle backslashes', () => { const query = 'path\\to\\file' const result = validateSearchQuery(query) expect(result).toBe(query) }) it('should handle caret and dollar sign', () => { const query = '^start $100 end$' const result = validateSearchQuery(query) expect(result).toBe(query) }) }) describe('SQL special characters', () => { it('should escape single quotes', () => { const input = "O'Brien's email" const result = escapeSQL(input) expect(result).toBe("O''Brien''s email") }) it('should handle multiple quote types', () => { const input = `It's a "test" with 'quotes'` const result = escapeSQL(input) expect(result).toContain("''") }) it('should handle semicolons (SQL statement separator)', () => { const input = 'test; DROP TABLE users;--' const result = escapeSQL(input) // Should escape but not remove - content is escaped for safety expect(result).toBeDefined() }) }) describe('AppleScript special characters', () => { it('should escape double quotes', () => { const input = 'Say "Hello World"' const result = escapeAppleScript(input) expect(result).toContain('\\"') }) it('should escape backslashes', () => { const input = 'Path\\to\\file' const result = escapeAppleScript(input) expect(result).toContain('\\\\') }) }) }) // ============ BOUNDARY CONDITIONS ============ describe('Boundary Conditions', () => { describe('validateLimit boundaries', () => { it('should handle limit = 0', () => { const result = validateLimit(0, 10, 100) expect(result).toBe(10) // Returns default for invalid }) it('should handle limit = -1', () => { const result = validateLimit(-1, 10, 100) expect(result).toBe(10) // Returns default for invalid }) it('should cap at maximum', () => { const result = validateLimit(999999, 10, 100) expect(result).toBe(100) }) it('should handle limit = 1 (minimum valid)', () => { const result = validateLimit(1, 10, 100) expect(result).toBe(1) }) it('should handle limit at exact maximum', () => { const result = validateLimit(100, 10, 100) expect(result).toBe(100) }) it('should handle floating point numbers', () => { const result = validateLimit(5.7, 10, 100) expect(result).toBe(5) // Should floor to integer }) it('should handle string numbers', () => { const result = validateLimit('25', 10, 100) expect(result).toBe(25) }) }) describe('validateDaysBack boundaries', () => { it('should handle daysBack = 0', () => { const result = validateDaysBack(0) expect(result).toBe(0) }) it('should handle negative daysBack', () => { const result = validateDaysBack(-5) expect(result).toBe(0) // Invalid, returns default }) it('should cap at maximum (3650 days = ~10 years)', () => { const result = validateDaysBack(5000) expect(result).toBeLessThanOrEqual(3650) }) }) describe('validateDate boundaries', () => { it('should accept dates within valid range', () => { // Use a date well within range to avoid timezone edge cases const result = validateDate('2020-06-15') expect(result).not.toBeNull() }) it('should reject clearly old dates', () => { const result = validateDate('1980-01-01') expect(result).toBeNull() }) it('should accept recent dates', () => { const result = validateDate('2024-01-15') expect(result).not.toBeNull() }) it('should handle far future dates based on validator config', () => { // Note: The validator allows year <= 2100 // Due to timezone parsing, boundary dates may behave unexpectedly const result = validateDate('2150-01-01') // 2150 > 2100 so should be rejected expect(result).toBeNull() }) it('should handle leap year date (Feb 29)', () => { const result = validateDate('2024-02-29') expect(result).not.toBeNull() }) it('should auto-correct invalid leap year date', () => { // JavaScript Date auto-corrects Feb 29 in non-leap years to March 1 // The validator doesn't catch this - it just validates the parsed date const result = validateDate('2023-02-29') // This gets parsed as 2023-03-01, which is valid expect(result).not.toBeNull() }) }) }) // ============ MALFORMED INPUT ============ describe('Malformed Input Handling', () => { describe('validateDate with malformed dates', () => { it('should reject date with wrong format (DD/MM/YYYY)', () => { // May be interpreted incorrectly, test behavior const result = validateDate('31/12/2024') // Either parses wrong or returns null - both acceptable expect(true).toBe(true) // Document the behavior }) it('should reject partial date', () => { const result = validateDate('2024-12') // Behavior may vary - document it expect(typeof result === 'object' || result === null).toBe(true) }) it('should reject date with extra characters', () => { const result = validateDate('2024-12-15abc') // Should either parse the valid part or reject expect(true).toBe(true) }) }) describe('validateLanceDBId with malformed IDs', () => { it('should reject ID with null bytes', () => { const result = validateLanceDBId('test\x00id') expect(result).toBeNull() }) it('should handle ID with whitespace (including newlines)', () => { // Note: \s in regex matches newlines, so they're technically allowed const result = validateLanceDBId('test\nid') // The validator's regex includes \s which matches newlines expect(result).toBe('test\nid') }) it('should reject empty ID', () => { const result = validateLanceDBId('') expect(result).toBeNull() }) it('should accept ID with hyphens and colons', () => { // Note: underscores are NOT allowed by the validator regex const result = validateLanceDBId('test-id:123') expect(result).toBe('test-id:123') }) it('should reject ID over 500 characters', () => { const longId = 'x'.repeat(501) const result = validateLanceDBId(longId) expect(result).toBeNull() }) }) }) // ============ TYPE COERCION ============ describe('Type Coercion', () => { describe('validateLimit type handling', () => { it('should convert string "30" to number 30', () => { const result = validateLimit('30', 10, 100) expect(result).toBe(30) }) it('should handle boolean true', () => { const result = validateLimit(true, 10, 100) // Boolean true is not a valid number, returns default expect(result).toBe(10) }) it('should handle boolean false', () => { const result = validateLimit(false, 10, 100) expect(result).toBe(10) // false/0 is invalid, returns default }) it('should handle object input', () => { const result = validateLimit({}, 10, 100) expect(result).toBe(10) // Invalid, returns default }) it('should handle array input', () => { const result = validateLimit([5], 10, 100) // May convert or return default expect(typeof result).toBe('number') }) }) describe('validateContact type handling', () => { it('should handle number input', () => { const result = validateContact(12345) expect(result).toBeNull() // Not a string }) it('should handle object input', () => { const result = validateContact({ name: 'John' }) expect(result).toBeNull() // Not a string }) }) }) // ============ CONCURRENT/RAPID CALLS ============ describe('Rapid Sequential Calls', () => { it('should handle many rapid validateSearchQuery calls', () => { const queries = Array(100).fill('test query') const results = queries.map(q => validateSearchQuery(q)) expect(results).toHaveLength(100) results.forEach(r => expect(r).toBe('test query')) }) it('should handle many rapid escapeSQL calls', () => { const inputs = Array(100).fill("O'Brien") const results = inputs.map(i => escapeSQL(i)) expect(results).toHaveLength(100) results.forEach(r => expect(r).toBe("O''Brien")) }) it('should handle many rapid stripHtmlTags calls', () => { const inputs = Array(100).fill('<p>Test</p>') const results = inputs.map(i => stripHtmlTags(i)) expect(results).toHaveLength(100) results.forEach(r => expect(r.trim()).toBe('Test')) }) })

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/sfls1397/Apple-Tools-MCP'

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