Skip to main content
Glama
test-helpers.ts13.9 kB
import { randomBytes, randomUUID } from 'crypto'; import jwt from 'jsonwebtoken'; import { Socket } from 'socket.io'; import { EventEmitter } from 'events'; // Test data generators export class TestDataGenerator { static generateUser(overrides: Partial<any> = {}) { return { id: randomUUID(), email: `test${Date.now()}@example.com`, username: `testuser${Date.now()}`, firstName: 'Test', lastName: 'User', roles: ['user'], permissions: ['basic'], mfaEnabled: false, mfaVerified: false, createdAt: new Date(), updatedAt: new Date(), ...overrides, }; } static generateSession(overrides: Partial<any> = {}) { return { id: randomUUID(), userId: randomUUID(), createdAt: new Date(), lastActivity: new Date(), ipAddress: '127.0.0.1', userAgent: 'Test Agent', ...overrides, }; } static generateJWTPayload(overrides: Partial<any> = {}) { return { sub: randomUUID(), email: 'test@example.com', roles: ['user'], permissions: ['basic'], sessionId: randomUUID(), mfaVerified: false, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, iss: 'secure-mcp-server', aud: 'secure-mcp-client', jti: randomUUID(), ...overrides, }; } static generateMCPMessage(overrides: Partial<any> = {}) { return { jsonrpc: '2.0', id: randomUUID(), method: 'test/method', params: {}, ...overrides, }; } static generateSecurePassword(): string { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'; let password = ''; // Ensure at least one of each required character type password += 'A'; // uppercase password += 'a'; // lowercase password += '1'; // number password += '!'; // special char // Fill the rest randomly for (let i = 4; i < 12; i++) { password += chars.charAt(Math.floor(Math.random() * chars.length)); } return password.split('').sort(() => Math.random() - 0.5).join(''); } static generateMaliciousPayload(type: 'sql' | 'xss' | 'command' | 'nosql' | 'path'): string { const payloads = { sql: "'; DROP TABLE users; --", xss: '<script>alert("XSS")</script>', command: '; rm -rf /', nosql: '{"$where": "this.password.match(/.*/)"}', path: '../../../etc/passwd', }; return payloads[type]; } static generateRateLimitRequests(count: number): Array<() => Promise<any>> { return Array.from({ length: count }, (_, i) => async () => ({ id: i, timestamp: new Date(), })); } } // Mock factories export class MockFactory { static createMockRedis() { const data = new Map<string, string>(); const sets = new Map<string, Set<string>>(); const expiries = new Map<string, number>(); return { get: jest.fn((key: string) => Promise.resolve(data.get(key) || null)), set: jest.fn((key: string, value: string) => { data.set(key, value); return Promise.resolve('OK'); }), setex: jest.fn((key: string, seconds: number, value: string) => { data.set(key, value); expiries.set(key, Date.now() + seconds * 1000); return Promise.resolve('OK'); }), del: jest.fn((...keys: string[]) => { let count = 0; keys.forEach(key => { if (data.delete(key)) count++; sets.delete(key); expiries.delete(key); }); return Promise.resolve(count); }), incr: jest.fn((key: string) => { const current = parseInt(data.get(key) || '0', 10); const newValue = current + 1; data.set(key, newValue.toString()); return Promise.resolve(newValue); }), expire: jest.fn((key: string, seconds: number) => { if (data.has(key)) { expiries.set(key, Date.now() + seconds * 1000); return Promise.resolve(1); } return Promise.resolve(0); }), ttl: jest.fn((key: string) => { const expiry = expiries.get(key); if (!expiry) return Promise.resolve(-1); const remaining = Math.ceil((expiry - Date.now()) / 1000); return Promise.resolve(Math.max(0, remaining)); }), sadd: jest.fn((key: string, ...members: string[]) => { if (!sets.has(key)) sets.set(key, new Set()); const set = sets.get(key)!; let added = 0; members.forEach(member => { if (!set.has(member)) { set.add(member); added++; } }); return Promise.resolve(added); }), srem: jest.fn((key: string, ...members: string[]) => { const set = sets.get(key); if (!set) return Promise.resolve(0); let removed = 0; members.forEach(member => { if (set.delete(member)) removed++; }); return Promise.resolve(removed); }), smembers: jest.fn((key: string) => { const set = sets.get(key); return Promise.resolve(set ? Array.from(set) : []); }), hgetall: jest.fn((key: string) => { const value = data.get(key); try { return Promise.resolve(value ? JSON.parse(value) : {}); } catch { return Promise.resolve({}); } }), keys: jest.fn((pattern: string) => { const regex = new RegExp(pattern.replace(/\*/g, '.*')); const matchingKeys = Array.from(data.keys()).filter(key => regex.test(key)); return Promise.resolve(matchingKeys); }), ping: jest.fn(() => Promise.resolve('PONG')), disconnect: jest.fn(() => Promise.resolve()), }; } static createMockPrisma() { return { $connect: jest.fn(() => Promise.resolve()), $disconnect: jest.fn(() => Promise.resolve()), $queryRaw: jest.fn(() => Promise.resolve([{ result: 1 }])), user: { create: jest.fn(), findUnique: jest.fn(), findMany: jest.fn(() => Promise.resolve([])), update: jest.fn(), delete: jest.fn(), upsert: jest.fn(), count: jest.fn(() => Promise.resolve(0)), }, session: { create: jest.fn(), findUnique: jest.fn(), findMany: jest.fn(() => Promise.resolve([])), update: jest.fn(), delete: jest.fn(), deleteMany: jest.fn(() => Promise.resolve({ count: 0 })), }, auditLog: { create: jest.fn(), findMany: jest.fn(() => Promise.resolve([])), }, }; } static createMockVault() { const secrets = new Map<string, any>(); return { read: jest.fn((path: string) => { const secret = secrets.get(path); return Promise.resolve(secret ? { data: secret } : null); }), write: jest.fn((path: string, data: any) => { secrets.set(path, data); return Promise.resolve(); }), health: jest.fn(() => Promise.resolve({ status: 'ok' })), delete: jest.fn((path: string) => { secrets.delete(path); return Promise.resolve(); }), }; } static createMockSocket(): Partial<Socket> { const socket = new EventEmitter() as any; socket.id = randomUUID(); socket.data = {}; socket.handshake = { address: '127.0.0.1', headers: { 'user-agent': 'test-agent', }, auth: {}, }; socket.send = jest.fn(); socket.emit = jest.fn(); socket.disconnect = jest.fn(); socket.join = jest.fn(); socket.leave = jest.fn(); socket.rooms = new Set(); socket.on = jest.fn(); socket.off = jest.fn(); socket.once = jest.fn(); return socket; } static createMockLogger() { return { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), trace: jest.fn(), child: jest.fn(() => MockFactory.createMockLogger()), }; } static createMockExpress() { const req: any = { headers: {}, body: {}, query: {}, params: {}, ip: '127.0.0.1', path: '/test', method: 'GET', get: jest.fn((header: string) => req.headers[header.toLowerCase()]), user: null, session: null, securityContext: null, securityFlags: null, }; const res: any = { status: jest.fn(() => res), json: jest.fn(() => res), send: jest.fn(() => res), set: jest.fn(() => res), cookie: jest.fn(() => res), clearCookie: jest.fn(() => res), redirect: jest.fn(() => res), end: jest.fn(() => res), headers: {}, }; const next = jest.fn(); return { req, res, next }; } } // Test utilities export class TestUtilities { static async waitFor(condition: () => boolean | Promise<boolean>, timeout = 5000): Promise<void> { const start = Date.now(); while (Date.now() - start < timeout) { const result = await Promise.resolve(condition()); if (result) return; await new Promise(resolve => setTimeout(resolve, 100)); } throw new Error(`Condition not met within ${timeout}ms`); } static createJWT(payload: any, secret = 'test-secret'): string { return jwt.sign(payload, secret); } static async simulateNetworkDelay(ms = 100): Promise<void> { await new Promise(resolve => setTimeout(resolve, ms)); } static generateRandomBytes(length: number): Buffer { return randomBytes(length); } static createBatchRequests<T>(count: number, generator: (index: number) => T): T[] { return Array.from({ length: count }, (_, i) => generator(i)); } static async executeInParallel<T>( tasks: (() => Promise<T>)[], maxConcurrency = 10 ): Promise<T[]> { const results: T[] = []; const executing: Promise<void>[] = []; for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; const promise = task().then(result => { results[i] = result; }); executing.push(promise); if (executing.length >= maxConcurrency) { await Promise.race(executing); const completed = executing.findIndex(p => p === promise); if (completed !== -1) { executing.splice(completed, 1); } } } await Promise.all(executing); return results; } static createSecurityTestCases() { return { sqlInjection: [ "'; DROP TABLE users; --", "' OR '1'='1", "1'; EXEC xp_cmdshell('dir'); --", "' UNION SELECT * FROM users --", ], xss: [ '<script>alert("XSS")</script>', '<img src=x onerror=alert("XSS")>', 'javascript:alert("XSS")', '<svg onload=alert("XSS")>', ], commandInjection: [ '; rm -rf /', '| cat /etc/passwd', '&& shutdown -h now', '`whoami`', ], pathTraversal: [ '../../../etc/passwd', '..\\..\\..\\windows\\system32\\config\\sam', '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd', ], noSqlInjection: [ '{"$where": "this.password.match(/.*/)"}', '{"$ne": null}', '{"$regex": ".*"}', '{"$gt": ""}', ], }; } } // Performance test helpers export class PerformanceTestHelpers { static async measureExecutionTime<T>(fn: () => Promise<T>): Promise<{ result: T; duration: number }> { const start = process.hrtime.bigint(); const result = await fn(); const end = process.hrtime.bigint(); const duration = Number(end - start) / 1000000; // Convert to milliseconds return { result, duration }; } static async measureMemoryUsage<T>(fn: () => Promise<T>): Promise<{ result: T; memoryDelta: number }> { if (global.gc) global.gc(); // Force garbage collection const memBefore = process.memoryUsage(); const result = await fn(); if (global.gc) global.gc(); // Force garbage collection const memAfter = process.memoryUsage(); const memoryDelta = memAfter.heapUsed - memBefore.heapUsed; return { result, memoryDelta }; } static async stressTest<T>( operation: () => Promise<T>, options: { concurrent: number; duration: number; errorThreshold?: number; } ): Promise<{ totalRequests: number; successful: number; failed: number; averageResponseTime: number; maxResponseTime: number; minResponseTime: number; errors: Error[]; }> { const { concurrent, duration, errorThreshold = 0.1 } = options; const startTime = Date.now(); const endTime = startTime + duration; let totalRequests = 0; let successful = 0; let failed = 0; const responseTimes: number[] = []; const errors: Error[] = []; const workers = Array.from({ length: concurrent }, async () => { while (Date.now() < endTime) { totalRequests++; const requestStart = Date.now(); try { await operation(); successful++; responseTimes.push(Date.now() - requestStart); } catch (error) { failed++; errors.push(error as Error); } } }); await Promise.all(workers); const averageResponseTime = responseTimes.length > 0 ? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length : 0; const maxResponseTime = responseTimes.length > 0 ? Math.max(...responseTimes) : 0; const minResponseTime = responseTimes.length > 0 ? Math.min(...responseTimes) : 0; const errorRate = failed / totalRequests; if (errorRate > errorThreshold) { throw new Error(`Error rate ${errorRate} exceeded threshold ${errorThreshold}`); } return { totalRequests, successful, failed, averageResponseTime, maxResponseTime, minResponseTime, errors, }; } }

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/perfecxion-ai/secure-mcp'

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