Add unified setup script (configure.js) merging deploy + config wizard
Single-file Node.js CLI that orchestrates the full setup lifecycle: - Interactive env var configuration with validation and smart defaults - Postgres provisioning via Docker Compose with readiness polling - Schema initialization (psql with docker exec fallback) - npm dependency installation with 120s timeout - Optional SQLite-to-Postgres data migration with retry logic - Frontend build with smart skip on reconfiguration Includes 84 tests: 50 property-based (fast-check) covering 19 correctness properties, and 34 integration tests for filesystem and parsing flows.
This commit is contained in:
121
backend/__tests__/config-wizard-buildskip.property.test.js
Normal file
121
backend/__tests__/config-wizard-buildskip.property.test.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Property-Based Tests: Config Wizard Frontend Build Skip Logic
|
||||
*
|
||||
* Feature: config-wizard
|
||||
*
|
||||
* Tests the shouldSkipFrontendBuild function from `configure.js`.
|
||||
*
|
||||
* Validates: Requirements 14.4, 14.5
|
||||
*/
|
||||
|
||||
const fc = require('fast-check');
|
||||
const { shouldSkipFrontendBuild } = require('../../configure.js');
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Generate a REACT_APP_* key name */
|
||||
const reactAppKeyArb = fc.stringMatching(/^REACT_APP_[A-Z][A-Z0-9_]{0,15}$/)
|
||||
.filter(k => k.length > 10);
|
||||
|
||||
/** Generate a non-empty env value */
|
||||
const envValueArb = fc.string({ minLength: 1, maxLength: 50 })
|
||||
.filter(s => s.trim().length > 0 && !s.includes('\n'));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Property 19: Frontend build skip determination
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Property 19: Frontend build skip determination', () => {
|
||||
/**
|
||||
* **Validates: Requirements 14.4, 14.5**
|
||||
*
|
||||
* shouldSkipFrontendBuild returns true iff all REACT_APP_* keys have identical
|
||||
* values in old and new maps and old map is non-null.
|
||||
*/
|
||||
|
||||
test('when old map is null, always returns false', () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.array(fc.tuple(reactAppKeyArb, envValueArb), { minLength: 1, maxLength: 5 }),
|
||||
(entries) => {
|
||||
const newMap = new Map(entries);
|
||||
return shouldSkipFrontendBuild(null, newMap) === false;
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
|
||||
test('when old and new have identical REACT_APP_* values, returns true', () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.array(fc.tuple(reactAppKeyArb, envValueArb), { minLength: 1, maxLength: 5 }),
|
||||
(entries) => {
|
||||
// Deduplicate keys by using a Map
|
||||
const deduped = [...new Map(entries).entries()];
|
||||
const oldMap = new Map(deduped);
|
||||
const newMap = new Map(deduped);
|
||||
return shouldSkipFrontendBuild(oldMap, newMap) === true;
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
|
||||
test('when any REACT_APP_* value differs, returns false', () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.array(fc.tuple(reactAppKeyArb, envValueArb), { minLength: 1, maxLength: 5 }),
|
||||
envValueArb,
|
||||
(entries, differentValue) => {
|
||||
// Deduplicate keys
|
||||
const deduped = [...new Map(entries).entries()];
|
||||
if (deduped.length === 0) return true; // skip trivial case
|
||||
|
||||
const oldMap = new Map(deduped);
|
||||
const newMap = new Map(deduped);
|
||||
|
||||
// Change one value in the new map to be different
|
||||
const keyToChange = deduped[0][0];
|
||||
const originalValue = deduped[0][1];
|
||||
// Ensure the new value is actually different
|
||||
const newValue = differentValue === originalValue
|
||||
? differentValue + '_changed'
|
||||
: differentValue;
|
||||
newMap.set(keyToChange, newValue);
|
||||
|
||||
return shouldSkipFrontendBuild(oldMap, newMap) === false;
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
|
||||
test('when new map has additional REACT_APP_* keys not in old, returns false', () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.array(fc.tuple(reactAppKeyArb, envValueArb), { minLength: 1, maxLength: 3 }),
|
||||
reactAppKeyArb,
|
||||
envValueArb,
|
||||
(entries, extraKey, extraValue) => {
|
||||
// Deduplicate keys
|
||||
const deduped = [...new Map(entries).entries()];
|
||||
const oldMap = new Map(deduped);
|
||||
const newMap = new Map(deduped);
|
||||
|
||||
// Add an extra key to new that doesn't exist in old
|
||||
// Ensure the extra key is not already in the map
|
||||
const uniqueExtraKey = deduped.some(([k]) => k === extraKey)
|
||||
? extraKey + '_EXTRA'
|
||||
: extraKey;
|
||||
newMap.set(uniqueExtraKey, extraValue);
|
||||
|
||||
return shouldSkipFrontendBuild(oldMap, newMap) === false;
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user