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:
120
backend/__tests__/config-wizard-registry.property.test.js
Normal file
120
backend/__tests__/config-wizard-registry.property.test.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Property-Based Tests: Config Wizard Registry Invariants
|
||||
*
|
||||
* Feature: config-wizard
|
||||
*
|
||||
* Tests the structural invariants of the VARIABLE_DESCRIPTORS registry
|
||||
* from `configure.js`.
|
||||
*
|
||||
* Validates: Requirements 2.1, 2.4, 2.5
|
||||
*/
|
||||
|
||||
const {
|
||||
VARIABLE_DESCRIPTORS,
|
||||
GROUP_ORDER
|
||||
} = require('../../configure.js');
|
||||
|
||||
// --- Property 1: Descriptor registry uniqueness ---
|
||||
describe('Property 1: Descriptor registry uniqueness', () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.5**
|
||||
*
|
||||
* Every variable name appears exactly once across all groups in the
|
||||
* VARIABLE_DESCRIPTORS registry.
|
||||
*/
|
||||
test('every variable name appears exactly once in the registry', () => {
|
||||
const names = VARIABLE_DESCRIPTORS.map(d => d.name);
|
||||
const nameSet = new Set(names);
|
||||
|
||||
// No duplicates: set size equals array length
|
||||
expect(nameSet.size).toBe(names.length);
|
||||
|
||||
// Each name appears exactly once
|
||||
const nameCounts = {};
|
||||
for (const name of names) {
|
||||
nameCounts[name] = (nameCounts[name] || 0) + 1;
|
||||
}
|
||||
for (const [name, count] of Object.entries(nameCounts)) {
|
||||
expect(count).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test('no variable is assigned to multiple groups', () => {
|
||||
const nameToGroups = {};
|
||||
for (const desc of VARIABLE_DESCRIPTORS) {
|
||||
if (!nameToGroups[desc.name]) {
|
||||
nameToGroups[desc.name] = [];
|
||||
}
|
||||
nameToGroups[desc.name].push(desc.group);
|
||||
}
|
||||
|
||||
for (const [name, groups] of Object.entries(nameToGroups)) {
|
||||
expect(groups.length).toBe(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- Property 2: Group presentation order ---
|
||||
describe('Property 2: Group presentation order', () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.1**
|
||||
*
|
||||
* Consecutive descriptors have non-decreasing group index in GROUP_ORDER,
|
||||
* ensuring variables are presented in group order.
|
||||
*/
|
||||
test('consecutive descriptors have non-decreasing group index', () => {
|
||||
for (let i = 1; i < VARIABLE_DESCRIPTORS.length; i++) {
|
||||
const prevGroupIndex = GROUP_ORDER.indexOf(VARIABLE_DESCRIPTORS[i - 1].group);
|
||||
const currGroupIndex = GROUP_ORDER.indexOf(VARIABLE_DESCRIPTORS[i].group);
|
||||
|
||||
// Both groups must exist in GROUP_ORDER
|
||||
expect(prevGroupIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(currGroupIndex).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Current group index must be >= previous group index
|
||||
expect(currGroupIndex).toBeGreaterThanOrEqual(prevGroupIndex);
|
||||
}
|
||||
});
|
||||
|
||||
test('all descriptor groups are present in GROUP_ORDER', () => {
|
||||
const descriptorGroups = new Set(VARIABLE_DESCRIPTORS.map(d => d.group));
|
||||
for (const group of descriptorGroups) {
|
||||
expect(GROUP_ORDER).toContain(group);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- Property 3: Required-before-optional ordering ---
|
||||
describe('Property 3: Required-before-optional ordering', () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.4**
|
||||
*
|
||||
* Within each group, all required descriptors precede optional ones
|
||||
* in the registry ordering.
|
||||
*/
|
||||
test('within each group, all required descriptors precede optional ones', () => {
|
||||
for (const group of GROUP_ORDER) {
|
||||
const groupDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.group === group);
|
||||
|
||||
let seenOptional = false;
|
||||
for (const desc of groupDescriptors) {
|
||||
if (desc.required) {
|
||||
// Once we've seen an optional, no more required should appear
|
||||
expect(seenOptional).toBe(false);
|
||||
} else {
|
||||
seenOptional = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('required count + optional count equals total for each group', () => {
|
||||
for (const group of GROUP_ORDER) {
|
||||
const groupDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.group === group);
|
||||
const requiredCount = groupDescriptors.filter(d => d.required).length;
|
||||
const optionalCount = groupDescriptors.filter(d => !d.required).length;
|
||||
|
||||
expect(requiredCount + optionalCount).toBe(groupDescriptors.length);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user