/** * Property-Based Tests: Config Wizard Parsing Functions * * Feature: config-wizard * * Tests the parsing and derived-default functions from `configure.js`. * * Validates: Requirements 4.1, 4.2, 4.6 */ const fc = require('fast-check'); const { resolveShellDefault, computeDerivedDefaults } = require('../../configure.js'); // --- Property 5: Shell variable default resolution --- describe('Property 5: Shell variable default resolution', () => { /** * **Validates: Requirements 4.1** * * For any string containing the pattern ${VARNAME:-defaultvalue}, * resolveShellDefault extracts and returns defaultvalue. * For any string not containing that pattern, it returns the original * string (with surrounding quotes stripped). */ test('resolveShellDefault extracts default from ${VAR:-default} pattern', () => { // Generate valid variable names and default values const varNameArb = fc.stringMatching(/^[A-Z][A-Z0-9_]{0,19}$/); const defaultValueArb = fc.string({ minLength: 1, maxLength: 50 }) .filter(s => !s.includes('}')); fc.assert( fc.property(varNameArb, defaultValueArb, (varName, defaultValue) => { const input = `\${${varName}:-${defaultValue}}`; const result = resolveShellDefault(input); return result === defaultValue; }), { numRuns: 100 } ); }); test('resolveShellDefault returns original string (quotes stripped) for non-matching patterns', () => { // Generate strings that do NOT contain the ${VAR:-default} pattern // and do not have leading/trailing quotes (which would be stripped) const plainStringArb = fc.string({ minLength: 1, maxLength: 50 }) .filter(s => !/\$\{[^:}]+:-[^}]+\}/.test(s) && !s.startsWith("'") && !s.startsWith('"') && !s.endsWith("'") && !s.endsWith('"') ); fc.assert( fc.property(plainStringArb, (input) => { const result = resolveShellDefault(input); return result === input; }), { numRuns: 100 } ); }); test('resolveShellDefault strips surrounding quotes from non-matching strings', () => { const innerStringArb = fc.string({ minLength: 1, maxLength: 30 }) .filter(s => !s.includes("'") && !s.includes('"') && !/\$\{[^:}]+:-[^}]+\}/.test(s)); fc.assert( fc.property( innerStringArb, fc.constantFrom("'", '"'), (inner, quote) => { const input = `${quote}${inner}${quote}`; const result = resolveShellDefault(input); return result === inner; } ), { numRuns: 100 } ); }); }); // --- Property 6: DATABASE_URL construction --- describe('Property 6: DATABASE_URL construction', () => { /** * **Validates: Requirements 4.2** * * For any valid credentials tuple (user, password, port in [1,65535], database), * the constructed URL equals postgresql://{user}:{password}@localhost:{port}/{database}. */ test('computeDerivedDefaults constructs correct DATABASE_URL from compose result', () => { const credentialArb = fc.record({ user: fc.string({ minLength: 1, maxLength: 30 }).filter(s => s.trim().length > 0 && !s.includes(':') && !s.includes('@') && !s.includes('/')), password: fc.string({ minLength: 1, maxLength: 30 }).filter(s => s.trim().length > 0 && !s.includes('@') && !s.includes('/')), port: fc.integer({ min: 1, max: 65535 }).map(String), database: fc.string({ minLength: 1, maxLength: 30 }).filter(s => s.trim().length > 0 && !s.includes('/') && !s.includes('@') && !s.includes(':')) }); fc.assert( fc.property(credentialArb, (creds) => { const result = computeDerivedDefaults('3001', 'localhost', creds); const expected = `postgresql://${creds.user}:${creds.password}@localhost:${creds.port}/${creds.database}`; return result.DATABASE_URL === expected; }), { numRuns: 100 } ); }); test('computeDerivedDefaults sets databaseUrlSource to compose when compose result provided', () => { const credentialArb = fc.record({ user: fc.string({ minLength: 1, maxLength: 20 }).filter(s => s.trim().length > 0), password: fc.string({ minLength: 1, maxLength: 20 }).filter(s => s.trim().length > 0), port: fc.integer({ min: 1, max: 65535 }).map(String), database: fc.string({ minLength: 1, maxLength: 20 }).filter(s => s.trim().length > 0) }); fc.assert( fc.property(credentialArb, (creds) => { const result = computeDerivedDefaults('3001', 'localhost', creds); return result.databaseUrlSource === 'compose'; }), { numRuns: 100 } ); }); }); // --- Property 7: Derived URL defaults from PORT and API_HOST --- describe('Property 7: Derived URL defaults from PORT and API_HOST', () => { /** * **Validates: Requirements 4.6** * * For any valid port P and host H, REACT_APP_API_BASE equals * http://{H}:{P}/api, REACT_APP_API_HOST equals http://{H}:{P}, * CORS_ORIGINS equals http://localhost:3000. */ test('derived defaults produce correct REACT_APP_API_BASE, REACT_APP_API_HOST, and CORS_ORIGINS', () => { const portArb = fc.integer({ min: 1, max: 65535 }).map(String); const hostArb = fc.string({ minLength: 1, maxLength: 50 }) .filter(s => s.trim().length > 0 && !s.includes(':') && !s.includes('/')); fc.assert( fc.property(portArb, hostArb, (port, host) => { const result = computeDerivedDefaults(port, host, null); const apiBaseCorrect = result.REACT_APP_API_BASE === `http://${host}:${port}/api`; const apiHostCorrect = result.REACT_APP_API_HOST === `http://${host}:${port}`; const corsCorrect = result.CORS_ORIGINS === 'http://localhost:3000'; return apiBaseCorrect && apiHostCorrect && corsCorrect; }), { numRuns: 100 } ); }); test('CORS_ORIGINS is always http://localhost:3000 regardless of port and host', () => { const portArb = fc.integer({ min: 1, max: 65535 }).map(String); const hostArb = fc.string({ minLength: 1, maxLength: 30 }) .filter(s => s.trim().length > 0); fc.assert( fc.property(portArb, hostArb, (port, host) => { const result = computeDerivedDefaults(port, host, null); return result.CORS_ORIGINS === 'http://localhost:3000'; }), { numRuns: 100 } ); }); test('when composeResult is null, databaseUrlSource is fallback', () => { const portArb = fc.integer({ min: 1, max: 65535 }).map(String); const hostArb = fc.constantFrom('localhost', '0.0.0.0', '192.168.1.1'); fc.assert( fc.property(portArb, hostArb, (port, host) => { const result = computeDerivedDefaults(port, host, null); return result.databaseUrlSource === 'fallback'; }), { numRuns: 100 } ); }); });