UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

472 lines 15.9 kB
/** * TestDataFactory - Generate test fixtures and synthetic data * * Provides methods to generate test fixtures from use case examples, * edge case data (boundary values, null, invalid), and synthetic data * for comprehensive test coverage. * * @module src/testing/fixtures/test-data-factory */ import * as crypto from 'crypto'; // =========================== // TestDataFactory Class // =========================== export class TestDataFactory { seed; rng; constructor(seed) { this.seed = seed ?? Date.now(); this.rng = this.createSeededRng(this.seed); } /** * Generate fixtures from a data schema * * @param schema - Data schema definition * @param options - Generation options * @returns Generated fixtures */ generateFromSchema(schema, options = {}) { const opts = { includeInvalid: true, includeBoundary: true, includeNullCases: true, count: 3, ...options }; // Generate valid fixture const valid = this.generateValidRecord(schema); // Generate invalid fixtures const invalid = opts.includeInvalid ? this.generateInvalidRecords(schema, opts.count) : []; // Generate boundary fixtures const boundary = opts.includeBoundary ? this.generateBoundaryRecords(schema) : []; // Generate null case fixtures const nullCases = opts.includeNullCases ? this.generateNullCaseRecords(schema) : []; return { valid, invalid, boundary, nullCases }; } /** * Generate a valid record based on schema * * @param schema - Data schema * @returns Valid record */ generateValidRecord(schema) { const record = {}; for (const field of schema.fields) { record[field.name] = this.generateValidValue(field); } return record; } /** * Generate multiple valid records * * @param schema - Data schema * @param count - Number of records to generate * @returns Array of valid records */ generateValidRecords(schema, count) { const records = []; for (let i = 0; i < count; i++) { records.push(this.generateValidRecord(schema)); } return records; } /** * Generate invalid records (one invalid field per record) * * @param schema - Data schema * @param count - Maximum number of invalid records * @returns Array of invalid records */ generateInvalidRecords(schema, count) { const records = []; for (const field of schema.fields) { if (records.length >= count) break; const record = this.generateValidRecord(schema); record[field.name] = this.generateInvalidValue(field); records.push(record); } return records; } /** * Generate boundary value records * * @param schema - Data schema * @returns Array of boundary records */ generateBoundaryRecords(schema) { const records = []; for (const field of schema.fields) { const boundaryValues = this.generateBoundaryValues(field); for (const value of boundaryValues) { const record = this.generateValidRecord(schema); record[field.name] = value; records.push(record); } } return records; } /** * Generate null case records (null for each nullable field) * * @param schema - Data schema * @returns Array of null case records */ generateNullCaseRecords(schema) { const records = []; for (const field of schema.fields) { if (field.nullable) { const record = this.generateValidRecord(schema); record[field.name] = null; records.push(record); } } return records; } // =========================== // Type-Specific Generators // =========================== /** * Generate a string value */ generateString(minLength = 1, maxLength = 50, pattern) { if (pattern) { return this.generateFromPattern(pattern); } const length = this.randomInt(minLength, maxLength); const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(this.rng() * chars.length)); } return result; } /** * Generate a number value */ generateNumber(min = 0, max = 1000) { return min + this.rng() * (max - min); } /** * Generate an integer value */ generateInteger(min = 0, max = 1000) { return Math.floor(this.generateNumber(min, max)); } /** * Generate a boolean value */ generateBoolean() { return this.rng() > 0.5; } /** * Generate a date value */ generateDate(minYear = 2000, maxYear = 2030) { const year = this.randomInt(minYear, maxYear); const month = this.randomInt(0, 11); const day = this.randomInt(1, 28); return new Date(year, month, day); } /** * Generate a datetime value */ generateDatetime(minYear = 2000, maxYear = 2030) { const date = this.generateDate(minYear, maxYear); date.setHours(this.randomInt(0, 23)); date.setMinutes(this.randomInt(0, 59)); date.setSeconds(this.randomInt(0, 59)); return date; } /** * Generate an email address */ generateEmail() { const username = this.generateString(5, 10); const domains = ['example.com', 'test.org', 'demo.net', 'sample.io']; const domain = domains[Math.floor(this.rng() * domains.length)]; return `${username.toLowerCase()}@${domain}`; } /** * Generate a URL */ generateUrl() { const protocols = ['http', 'https']; const protocol = protocols[Math.floor(this.rng() * protocols.length)]; const domain = this.generateString(5, 15).toLowerCase(); const tlds = ['com', 'org', 'net', 'io']; const tld = tlds[Math.floor(this.rng() * tlds.length)]; return `${protocol}://${domain}.${tld}`; } /** * Generate a UUID */ generateUuid() { return crypto.randomUUID(); } /** * Generate an enum value */ generateEnum(values) { if (values.length === 0) return ''; return values[Math.floor(this.rng() * values.length)]; } /** * Generate an array value */ generateArray(generator, minLength = 1, maxLength = 5) { const length = this.randomInt(minLength, maxLength); const array = []; for (let i = 0; i < length; i++) { array.push(generator()); } return array; } // =========================== // Edge Case Generators // =========================== /** * Generate XSS attack payloads */ generateXssPayloads() { return [ '<script>alert("xss")</script>', '"><img src=x onerror=alert(1)>', "javascript:alert('xss')", '<svg onload=alert(1)>', '{{constructor.constructor("alert(1)")()}}' ]; } /** * Generate SQL injection payloads */ generateSqlInjectionPayloads() { return [ "'; DROP TABLE users; --", "1' OR '1'='1", "1; SELECT * FROM users", "' UNION SELECT * FROM passwords --", "admin'--" ]; } /** * Generate path traversal payloads */ generatePathTraversalPayloads() { return [ '../../../etc/passwd', '..\\..\\..\\windows\\system32\\config\\sam', '/etc/passwd%00', '....//....//....//etc/passwd', '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd' ]; } /** * Generate overflow/boundary strings */ generateOverflowStrings() { return [ '', // Empty ' ', // Single space ' ', // Multiple spaces 'a'.repeat(256), // 256 chars 'a'.repeat(1024), // 1KB 'a'.repeat(65536), // 64KB '\x00\x00\x00', // Null bytes '\n\r\t', // Control characters '🎉'.repeat(100) // Unicode emoji ]; } // =========================== // Private Helper Methods // =========================== generateValidValue(field) { if (field.defaultValue !== undefined) { return field.defaultValue; } const constraints = field.constraints || {}; switch (field.type) { case 'string': return this.generateString(constraints.minLength || 1, constraints.maxLength || 50, constraints.pattern); case 'number': return this.generateNumber(constraints.min || 0, constraints.max || 1000); case 'integer': return this.generateInteger(constraints.min || 0, constraints.max || 1000); case 'boolean': return this.generateBoolean(); case 'date': return this.generateDate().toISOString().split('T')[0]; case 'datetime': return this.generateDatetime().toISOString(); case 'email': return this.generateEmail(); case 'url': return this.generateUrl(); case 'uuid': return this.generateUuid(); case 'enum': return this.generateEnum(constraints.enumValues || []); case 'array': return []; case 'object': return {}; default: return null; } } generateInvalidValue(field) { const constraints = field.constraints || {}; switch (field.type) { case 'string': // Return string that's too long if (constraints.maxLength) { return 'x'.repeat(constraints.maxLength + 10); } return 12345; // Wrong type case 'number': case 'integer': // Return out of range value if (constraints.max !== undefined) { return constraints.max + 100; } return 'not-a-number'; case 'boolean': return 'not-a-boolean'; case 'date': case 'datetime': return 'not-a-date'; case 'email': return 'not-an-email'; case 'url': return 'not-a-url'; case 'uuid': return 'not-a-uuid'; case 'enum': return 'INVALID_ENUM_VALUE'; default: return undefined; } } generateBoundaryValues(field) { const values = []; const constraints = field.constraints || {}; switch (field.type) { case 'string': if (constraints.minLength !== undefined) { values.push('x'.repeat(constraints.minLength)); if (constraints.minLength > 0) { values.push('x'.repeat(constraints.minLength - 1)); } } if (constraints.maxLength !== undefined) { values.push('x'.repeat(constraints.maxLength)); values.push('x'.repeat(constraints.maxLength + 1)); } break; case 'number': case 'integer': if (constraints.min !== undefined) { values.push(constraints.min); values.push(constraints.min - 1); } if (constraints.max !== undefined) { values.push(constraints.max); values.push(constraints.max + 1); } // Zero and negative values.push(0, -1, -0.1); break; case 'date': case 'datetime': values.push(new Date('1970-01-01').toISOString(), new Date('2099-12-31').toISOString(), new Date('1900-01-01').toISOString()); break; } return values; } generateFromPattern(pattern) { // Simple pattern support: [a-z] for lowercase, [A-Z] for uppercase, [0-9] for digits let result = ''; let i = 0; while (i < pattern.length) { if (pattern[i] === '[' && pattern.indexOf(']', i) > i) { const end = pattern.indexOf(']', i); const charClass = pattern.substring(i + 1, end); if (charClass === 'a-z') { result += String.fromCharCode(97 + Math.floor(this.rng() * 26)); } else if (charClass === 'A-Z') { result += String.fromCharCode(65 + Math.floor(this.rng() * 26)); } else if (charClass === '0-9') { result += String(Math.floor(this.rng() * 10)); } else { result += charClass.charAt(Math.floor(this.rng() * charClass.length)); } i = end + 1; } else { result += pattern[i]; i++; } } return result; } randomInt(min, max) { return Math.floor(this.rng() * (max - min + 1)) + min; } createSeededRng(seed) { // Simple seeded random number generator (xorshift) let state = seed; return () => { state ^= state << 13; state ^= state >> 17; state ^= state << 5; return (state >>> 0) / 4294967296; }; } } // =========================== // Pre-configured Schemas // =========================== export const CommonSchemas = { user: { name: 'User', fields: [ { name: 'id', type: 'uuid' }, { name: 'email', type: 'email' }, { name: 'name', type: 'string', constraints: { minLength: 2, maxLength: 100 } }, { name: 'age', type: 'integer', constraints: { min: 0, max: 150 }, nullable: true }, { name: 'active', type: 'boolean', defaultValue: true }, { name: 'createdAt', type: 'datetime' } ] }, project: { name: 'Project', fields: [ { name: 'id', type: 'uuid' }, { name: 'name', type: 'string', constraints: { minLength: 1, maxLength: 200 } }, { name: 'description', type: 'string', constraints: { maxLength: 1000 }, nullable: true }, { name: 'status', type: 'enum', constraints: { enumValues: ['active', 'archived', 'draft'] } }, { name: 'priority', type: 'integer', constraints: { min: 1, max: 5 } } ] }, testCase: { name: 'TestCase', fields: [ { name: 'id', type: 'string', constraints: { pattern: 'TC-[0-9][0-9][0-9]' } }, { name: 'name', type: 'string', constraints: { minLength: 5, maxLength: 200 } }, { name: 'priority', type: 'enum', constraints: { enumValues: ['critical', 'high', 'medium', 'low'] } }, { name: 'automated', type: 'boolean', defaultValue: false }, { name: 'estimatedDuration', type: 'integer', constraints: { min: 0 }, nullable: true } ] } }; //# sourceMappingURL=test-data-factory.js.map