/** * Property-Based Tests: Config Wizard Validation Functions * * Feature: config-wizard * * Tests the pure validation functions from `configure.js`. * * Validates: Requirements 5.1, 5.2, 5.3, 5.4, 5.6, 5.7 */ const fc = require('fast-check'); const { validatePort, validateCorsOrigins, validateDatabaseUrl, validateSessionSecret, validateRequired } = require('../../configure.js'); // --- Property 8: Port validation --- describe('Property 8: Port validation', () => { /** * **Validates: Requirements 5.2** * * For any string, validatePort returns true iff the trimmed value is an integer * in [1, 65535] with no leading zeros. */ test('validatePort returns true iff trimmed value is integer in [1, 65535] with no leading zeros', () => { fc.assert( fc.property(fc.string(), (input) => { const result = validatePort(input); const trimmed = input.trim(); // Compute expected result if (trimmed === '') return result === false; const parsed = parseInt(trimmed, 10); if (isNaN(parsed)) return result === false; // Must be exact string representation (no leading zeros, no floats, no extra chars) if (trimmed !== String(parsed)) return result === false; const expected = parsed >= 1 && parsed <= 65535; return result === expected; }), { numRuns: 100 } ); }); test('validatePort returns true for valid port numbers', () => { fc.assert( fc.property(fc.integer({ min: 1, max: 65535 }), (port) => { return validatePort(String(port)) === true; }), { numRuns: 100 } ); }); test('validatePort returns false for out-of-range integers', () => { fc.assert( fc.property( fc.oneof( fc.integer({ min: 65536, max: 999999 }), fc.integer({ min: -999999, max: 0 }) ), (port) => { return validatePort(String(port)) === false; } ), { numRuns: 100 } ); }); test('validatePort rejects leading zeros', () => { fc.assert( fc.property(fc.integer({ min: 1, max: 9999 }), (port) => { const withLeadingZero = '0' + String(port); return validatePort(withLeadingZero) === false; }), { numRuns: 100 } ); }); }); // --- Property 9: CORS origins validation --- describe('Property 9: CORS origins validation', () => { /** * **Validates: Requirements 5.3, 5.7** * * For any comma-separated string, validateCorsOrigins returns true iff at least * one valid entry remains after trim/discard and each starts with http:// or * https:// followed by non-whitespace. */ test('validateCorsOrigins returns true iff at least one valid entry remains after trim/discard', () => { fc.assert( fc.property(fc.string(), (input) => { const result = validateCorsOrigins(input); // Compute expected const entries = input.split(',') .map(entry => entry.trim()) .filter(entry => entry.length > 0); if (entries.length === 0) return result === false; const allValid = entries.every(entry => /^https?:\/\/\S+/.test(entry)); return result === allValid; }), { numRuns: 100 } ); }); test('validateCorsOrigins accepts valid http/https origins', () => { const validOriginArb = fc.oneof( fc.webUrl().map(url => url.split('/').slice(0, 3).join('/')), fc.constantFrom( 'http://localhost:3000', 'https://example.com', 'http://192.168.1.1:8080' ) ); fc.assert( fc.property( fc.array(validOriginArb, { minLength: 1, maxLength: 5 }), (origins) => { return validateCorsOrigins(origins.join(',')) === true; } ), { numRuns: 100 } ); }); test('validateCorsOrigins rejects entries without http/https prefix', () => { const invalidOriginArb = fc.stringMatching(/^[a-z][a-z0-9]*:\/\/\S+/, { minLength: 4, maxLength: 30 }) .filter(s => !s.startsWith('http://') && !s.startsWith('https://')); fc.assert( fc.property(invalidOriginArb, (origin) => { return validateCorsOrigins(origin) === false; }), { numRuns: 100 } ); }); }); // --- Property 10: DATABASE_URL validation --- describe('Property 10: DATABASE_URL validation', () => { /** * **Validates: Requirements 5.4** * * For any string, validateDatabaseUrl returns true iff it starts with * `postgresql://` or equals `sqlite`. */ test('validateDatabaseUrl returns true iff starts with postgresql:// or equals sqlite', () => { fc.assert( fc.property(fc.string(), (input) => { const result = validateDatabaseUrl(input); const expected = input.startsWith('postgresql://') || input === 'sqlite'; return result === expected; }), { numRuns: 100 } ); }); test('validateDatabaseUrl accepts any postgresql:// URL', () => { fc.assert( fc.property(fc.string({ minLength: 0, maxLength: 100 }), (suffix) => { return validateDatabaseUrl('postgresql://' + suffix) === true; }), { numRuns: 100 } ); }); test('validateDatabaseUrl accepts sqlite literal', () => { expect(validateDatabaseUrl('sqlite')).toBe(true); }); test('validateDatabaseUrl rejects other strings', () => { fc.assert( fc.property( fc.string({ minLength: 1, maxLength: 50 }).filter( s => !s.startsWith('postgresql://') && s !== 'sqlite' ), (input) => { return validateDatabaseUrl(input) === false; } ), { numRuns: 100 } ); }); }); // --- Property 11: SESSION_SECRET validation --- describe('Property 11: SESSION_SECRET validation', () => { /** * **Validates: Requirements 5.6** * * For any string, validateSessionSecret returns true iff length in [16, 256]. */ test('validateSessionSecret returns true iff length in [16, 256]', () => { fc.assert( fc.property(fc.string({ minLength: 0, maxLength: 300 }), (input) => { const result = validateSessionSecret(input); const expected = input.length >= 16 && input.length <= 256; return result === expected; }), { numRuns: 100 } ); }); test('validateSessionSecret accepts strings of length 16-256', () => { fc.assert( fc.property( fc.integer({ min: 16, max: 256 }).chain(len => fc.string({ minLength: len, maxLength: len }) ), (input) => { return validateSessionSecret(input) === true; } ), { numRuns: 100 } ); }); test('validateSessionSecret rejects strings shorter than 16', () => { fc.assert( fc.property(fc.string({ minLength: 0, maxLength: 15 }), (input) => { return validateSessionSecret(input) === false; }), { numRuns: 100 } ); }); }); // --- Property 12: Required variable rejection of whitespace --- describe('Property 12: Required variable rejection of whitespace', () => { /** * **Validates: Requirements 5.1** * * For any whitespace-only string, validateRequired returns false; * for any string with non-whitespace, returns true. */ test('validateRequired returns false for whitespace-only strings', () => { const whitespaceArb = fc.array( fc.constantFrom(' ', '\t', '\n', '\r', '\f', '\v'), { minLength: 0, maxLength: 20 } ).map(chars => chars.join('')); fc.assert( fc.property(whitespaceArb, (input) => { return validateRequired(input) === false; }), { numRuns: 100 } ); }); test('validateRequired returns true for strings with non-whitespace', () => { fc.assert( fc.property( fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0), (input) => { return validateRequired(input) === true; } ), { numRuns: 100 } ); }); test('validateRequired equivalence: result matches trim().length > 0', () => { fc.assert( fc.property(fc.string(), (input) => { const result = validateRequired(input); const expected = input.trim().length > 0; return result === expected; }), { numRuns: 100 } ); }); });