/** * Input fuzzer — generate test data for form fields. * * For each field type, produces a set of test values covering: * - Happy path (valid, normal input) * - Boundary (min/max length, min/max value) * - Edge cases (empty, very long, special chars, SQL injection probe, XSS probe) * - Type-invalid (wrong type for the field) * * This feeds ZeTa's test generator to automatically create form validation tests. * Values are safe probes (NOT actual exploits) — they test whether the app * properly rejects/accepts boundary inputs. */ import type { FormField } from './form-detector.js'; export type InputCase = { label: string; // human-readable test case name value: string; expectedValid: boolean; // true if input should be accepted category: 'happy' | 'boundary' | 'edge' | 'invalid'; }; const LOREM = 'Lorem ipsum dolor sit amet consectetur adipiscing elit'; function generateEmailCases(): InputCase[] { return [ { label: 'valid email', value: 'test@example.com', expectedValid: true, category: 'happy' }, { label: 'email with plus', value: 'test+tag@example.com', expectedValid: true, category: 'happy' }, { label: 'missing @', value: 'notanemail.com', expectedValid: false, category: 'invalid' }, { label: 'missing domain', value: 'test@', expectedValid: false, category: 'invalid' }, { label: 'empty', value: '', expectedValid: false, category: 'edge' }, { label: 'very long email', value: 'a'.repeat(100) + '@example.com', expectedValid: false, category: 'boundary' }, { label: 'special chars', value: 'test', expectedValid: true, category: 'edge' }, { label: 'SQL probe', value: "' OR '1'='1", expectedValid: true, category: 'edge' }, ]; if (minLength) cases.push({ label: `below min (${minLength})`, value: 'x'.repeat(Math.max(0, minLength - 1)), expectedValid: false, category: 'boundary' }); if (maxLength) cases.push({ label: `above max (${maxLength})`, value: 'x'.repeat(maxLength + 1), expectedValid: false, category: 'boundary' }); return cases; } function generateNumberCases(min?: string, max?: string): InputCase[] { const minN = min !== undefined ? parseFloat(min) : undefined; const maxN = max !== undefined ? parseFloat(max) : undefined; return [ { label: 'valid number', value: '42', expectedValid: true, category: 'happy' }, { label: 'zero', value: '0', expectedValid: minN === undefined || minN <= 0, category: 'boundary' }, { label: 'negative', value: '-1', expectedValid: minN === undefined || minN < 0, category: 'boundary' }, { label: 'decimal', value: '3.14', expectedValid: true, category: 'happy' }, { label: 'text as number', value: 'notanumber', expectedValid: false, category: 'invalid' }, { label: 'empty', value: '', expectedValid: false, category: 'edge' }, ...(minN !== undefined ? [{ label: `below min (${min})`, value: String(minN - 1), expectedValid: false, category: 'boundary' as const }] : []), ...(maxN !== undefined ? [{ label: `above max (${max})`, value: String(maxN + 1), expectedValid: false, category: 'boundary' as const }] : []), ]; } function generateSelectCases(options?: string[]): InputCase[] { if (!options?.length) return [{ label: 'first option', value: '', expectedValid: true, category: 'happy' }]; return [ { label: `first option (${options[0]})`, value: options[0], expectedValid: true, category: 'happy' }, { label: 'invalid option', value: '__INVALID_OPTION__', expectedValid: false, category: 'invalid' }, { label: 'empty selection', value: '', expectedValid: false, category: 'edge' }, ...(options.length > 1 ? [{ label: `last option (${options[options.length - 1]})`, value: options[options.length - 1], expectedValid: true, category: 'happy' as const }] : []), ]; } export function generateFieldCases(field: FormField): InputCase[] { switch (field.type) { case 'email': return generateEmailCases(); case 'password': return generatePasswordCases(field.minLength, field.maxLength); case 'number': return generateNumberCases(field.min, field.max); case 'select': return generateSelectCases(field.options); case 'checkbox': return [ { label: 'checked', value: 'true', expectedValid: true, category: 'happy' }, { label: 'unchecked', value: 'false', expectedValid: !field.required, category: field.required ? 'invalid' : 'happy' }, ]; case 'date': return [ { label: 'valid date', value: '2026-01-15', expectedValid: true, category: 'happy' }, { label: 'invalid date', value: '9999-99-99', expectedValid: false, category: 'invalid' }, { label: 'empty', value: '', expectedValid: !field.required, category: 'edge' }, ]; case 'url': return [ { label: 'valid URL', value: 'https://example.com', expectedValid: true, category: 'happy' }, { label: 'missing protocol', value: 'example.com', expectedValid: false, category: 'invalid' }, { label: 'empty', value: '', expectedValid: !field.required, category: 'edge' }, ]; default: return generateTextCases(field.minLength, field.maxLength, field.required); } } export interface FuzzedForm { formIndex: number; purpose: string; fieldCases: Array<{ field: FormField; cases: InputCase[] }>; } export function fuzzForm(form: import('./form-detector.js').DetectedForm): FuzzedForm { return { formIndex: form.index, purpose: form.purpose, fieldCases: form.fields.map(field => ({ field, cases: generateFieldCases(field) })), }; }