Auto-sync .kiro/ from master (post-checkout hook)
This commit is contained in:
486
.kiro/specs/config-wizard/design.md
Normal file
486
.kiro/specs/config-wizard/design.md
Normal file
@@ -0,0 +1,486 @@
|
||||
# Design Document: Config Wizard
|
||||
|
||||
## Overview
|
||||
|
||||
The Config Wizard is a single-file Node.js CLI script (`configure.js`) at the project root that interactively guides deployers through configuring all environment variables for the CVE Dashboard. It replaces the manual process of copying `.env.example` and editing values by hand.
|
||||
|
||||
The wizard uses only the Node.js built-in `readline` module — no external dependencies. It writes two output files: `backend/.env` and `frontend/.env`. It derives smart defaults by parsing `docker-compose.yml` for Postgres credentials and propagates the backend PORT into frontend URL defaults.
|
||||
|
||||
Key design goals:
|
||||
- Zero dependencies beyond Node.js 18+ standard library
|
||||
- Single file implementation for easy maintenance
|
||||
- Idempotent re-runs that preserve existing values and unmanaged variables
|
||||
- Group-based flow with skip capability for unused integrations
|
||||
|
||||
## Architecture
|
||||
|
||||
The wizard follows a linear pipeline architecture:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Start: node configure.js] --> B[Validate project root]
|
||||
B --> C[Parse existing .env files]
|
||||
C --> D[Parse docker-compose.yml for defaults]
|
||||
D --> E[Display welcome message]
|
||||
E --> F[Group loop: prompt variables]
|
||||
F --> G[Display summary]
|
||||
G --> H{User confirms?}
|
||||
H -->|Yes| I[Handle existing file backup/overwrite]
|
||||
I --> J[Write backend/.env]
|
||||
J --> K[Write frontend/.env]
|
||||
K --> L[Display success + next steps]
|
||||
H -->|No| M{Restart or exit?}
|
||||
M -->|Restart| E
|
||||
M -->|Exit| N[Exit code 1]
|
||||
B -->|Missing dirs| O[Error + exit code 1]
|
||||
```
|
||||
|
||||
### Execution Flow
|
||||
|
||||
1. **Validation** — Confirm `backend/` and `frontend/` directories exist relative to CWD
|
||||
2. **Parse existing** — Read current `backend/.env` and `frontend/.env` if they exist, extract key-value pairs
|
||||
3. **Parse docker-compose** — Extract Postgres credentials from `docker-compose.yml` using line-by-line parsing
|
||||
4. **Welcome** — Display purpose and instructions
|
||||
5. **Group loop** — Iterate through variable groups in order, prompting for each variable
|
||||
6. **Summary** — Display all configured values (sensitive ones masked) and target file paths
|
||||
7. **Confirmation** — User approves or rejects; rejection offers restart or exit
|
||||
8. **Write** — Generate env file content and write to disk with backup handling
|
||||
|
||||
### Signal Handling
|
||||
|
||||
A `SIGINT` handler (Ctrl+C) is registered at startup. When triggered:
|
||||
- Close the readline interface
|
||||
- Print a cancellation message
|
||||
- Exit with code 1
|
||||
- No files are written regardless of progress
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### Module Structure
|
||||
|
||||
All code lives in a single `configure.js` file organized into these logical sections:
|
||||
|
||||
```
|
||||
configure.js
|
||||
├── Constants & Configuration
|
||||
│ ├── VARIABLE_DESCRIPTORS[] — metadata for all managed variables
|
||||
│ ├── GROUP_ORDER[] — ordered list of group names
|
||||
│ ├── GROUP_DESCRIPTIONS{} — one-line description per group
|
||||
│ └── SENSITIVE_VARS[] — list of variable names to mask
|
||||
├── Parsing Functions
|
||||
│ ├── parseEnvFile(filePath) — read existing .env into key-value map
|
||||
│ ├── parseDockerCompose(filePath) — extract Postgres config
|
||||
│ └── resolveShellDefault(str) — extract default from ${VAR:-default} syntax
|
||||
├── Validation Functions
|
||||
│ ├── validatePort(value)
|
||||
│ ├── validateCorsOrigins(value)
|
||||
│ ├── validateDatabaseUrl(value)
|
||||
│ ├── validateSessionSecret(value)
|
||||
│ └── validateRequired(value)
|
||||
├── Display Functions
|
||||
│ ├── printWelcome()
|
||||
│ ├── printGroupHeader(group)
|
||||
│ ├── printSummary(config, skippedGroups)
|
||||
│ └── maskSensitive(name, value)
|
||||
├── Prompt Functions
|
||||
│ ├── promptVariable(rl, descriptor, currentValue)
|
||||
│ ├── promptYesNo(rl, question, defaultNo)
|
||||
│ └── promptOverwrite(rl, filePath)
|
||||
├── File Writing
|
||||
│ ├── generateEnvContent(variables, groups)
|
||||
│ ├── writeEnvFile(filePath, content)
|
||||
│ └── createBackup(filePath)
|
||||
└── Main Flow
|
||||
└── main()
|
||||
```
|
||||
|
||||
### Key Interfaces
|
||||
|
||||
#### Variable Descriptor
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: String, // e.g. "PORT"
|
||||
group: String, // e.g. "Core Settings"
|
||||
required: Boolean, // true = must have a value
|
||||
default: String|null, // factory default value
|
||||
description: String, // max 120 chars
|
||||
docUrl: String|null, // URL for obtaining the value
|
||||
sensitive: Boolean, // true = mask in display
|
||||
validator: String|null // name of validation function to apply
|
||||
}
|
||||
```
|
||||
|
||||
#### Parsed Config State
|
||||
|
||||
```javascript
|
||||
{
|
||||
values: Map<String, String>, // variable name → entered value
|
||||
skippedGroups: Set<String>, // groups the user declined
|
||||
existingBackend: Map<String, String>, // parsed from existing backend/.env
|
||||
existingFrontend: Map<String, String>, // parsed from existing frontend/.env
|
||||
unmanagedBackend: String[], // lines not matching managed vars
|
||||
unmanagedFrontend: String[], // lines not matching managed vars
|
||||
derivedDefaults: { // computed from docker-compose/port
|
||||
DATABASE_URL: String|null,
|
||||
databaseUrlSource: 'compose'|'fallback',
|
||||
REACT_APP_API_BASE: String|null,
|
||||
CORS_ORIGINS: String|null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### parseEnvFile(filePath) → { managed: Map, unmanaged: String[] }
|
||||
|
||||
Reads a `.env` file line by line. For each non-empty, non-comment line, splits on the first `=` character. If the key matches a managed variable, stores it in `managed`. Otherwise, preserves the raw line (including preceding comments) in `unmanaged`.
|
||||
|
||||
#### parseDockerCompose(filePath) → { user, password, database, port } | null
|
||||
|
||||
Line-by-line parser that extracts:
|
||||
- `POSTGRES_USER` from `POSTGRES_USER: value` or `POSTGRES_USER: ${VAR:-default}`
|
||||
- `POSTGRES_PASSWORD` from `POSTGRES_PASSWORD: ${VAR:-default}` (resolves the default)
|
||||
- `POSTGRES_DB` from `POSTGRES_DB: value`
|
||||
- Host port from `- "host:container"` under the `ports:` key
|
||||
|
||||
Returns `null` if the file doesn't exist or can't be parsed.
|
||||
|
||||
#### generateEnvContent(variables, groupOrder, groupDescriptions) → String
|
||||
|
||||
Produces the final `.env` file content:
|
||||
- Group header comments (`# --- Group Name ---`)
|
||||
- `KEY=value` lines, with values containing spaces/`#`/quotes wrapped in double quotes
|
||||
- Omits optional variables with no value
|
||||
- Appends unmanaged variables in a `# Custom Variables` section
|
||||
|
||||
## Data Models
|
||||
|
||||
### Variable Descriptor Registry
|
||||
|
||||
The complete list of managed variables with their metadata:
|
||||
|
||||
| Name | Group | Required | Default | Sensitive | Validator |
|
||||
|------|-------|----------|---------|-----------|-----------|
|
||||
| `PORT` | Core Settings | yes | `3001` | no | `validatePort` |
|
||||
| `API_HOST` | Core Settings | yes | `localhost` | no | — |
|
||||
| `CORS_ORIGINS` | Core Settings | yes | (derived) | no | `validateCorsOrigins` |
|
||||
| `DATABASE_URL` | Database | yes | (derived) | yes | `validateDatabaseUrl` |
|
||||
| `SESSION_SECRET` | Session | yes | — | yes | `validateSessionSecret` |
|
||||
| `NVD_API_KEY` | NVD API | no | — | yes | — |
|
||||
| `IVANTI_API_KEY` | Ivanti Integration | no | — | yes | — |
|
||||
| `IVANTI_CLIENT_ID` | Ivanti Integration | no | `1550` | no | — |
|
||||
| `IVANTI_FIRST_NAME` | Ivanti Integration | no | — | no | — |
|
||||
| `IVANTI_LAST_NAME` | Ivanti Integration | no | — | no | — |
|
||||
| `IVANTI_BU_FILTER` | Ivanti Integration | no | `NTS-AEO-ACCESS-ENG,NTS-AEO-STEAM` | no | — |
|
||||
| `IVANTI_MANAGED_BUS` | Ivanti Integration | no | `NTS-AEO-ACCESS-ENG,NTS-AEO-STEAM` | no | — |
|
||||
| `IVANTI_SKIP_TLS` | Ivanti Integration | no | `false` | no | — |
|
||||
| `ATLAS_API_URL` | Atlas Integration | no | — | no | — |
|
||||
| `ATLAS_API_USER` | Atlas Integration | no | — | no | — |
|
||||
| `ATLAS_API_PASS` | Atlas Integration | no | — | yes | — |
|
||||
| `ATLAS_SKIP_TLS` | Atlas Integration | no | `false` | no | — |
|
||||
| `JIRA_BASE_URL` | Jira Integration | no | — | no | — |
|
||||
| `JIRA_AUTH_METHOD` | Jira Integration | no | `basic` | no | — |
|
||||
| `JIRA_API_USER` | Jira Integration | no | — | no | — |
|
||||
| `JIRA_API_TOKEN` | Jira Integration | no | — | yes | — |
|
||||
| `JIRA_PAT` | Jira Integration | no | — | yes | — |
|
||||
| `JIRA_PROJECT_KEY` | Jira Integration | no | — | no | — |
|
||||
| `JIRA_ISSUE_TYPE` | Jira Integration | no | `Task` | no | — |
|
||||
| `JIRA_SKIP_TLS` | Jira Integration | no | `false` | no | — |
|
||||
| `CARD_API_URL` | CARD Integration | no | — | no | — |
|
||||
| `CARD_API_USER` | CARD Integration | no | — | no | — |
|
||||
| `CARD_API_PASS` | CARD Integration | no | — | yes | — |
|
||||
| `CARD_SKIP_TLS` | CARD Integration | no | `false` | no | — |
|
||||
| `GITLAB_URL` | GitLab Integration | no | `http://steam-gitlab.charterlab.com` | no | — |
|
||||
| `GITLAB_PROJECT_ID` | GitLab Integration | no | — | no | — |
|
||||
| `GITLAB_PAT` | GitLab Integration | no | — | yes | — |
|
||||
| `REACT_APP_API_BASE` | Frontend Settings | yes | (derived) | no | — |
|
||||
| `REACT_APP_API_HOST` | Frontend Settings | yes | (derived) | no | — |
|
||||
|
||||
### Group Order and Descriptions
|
||||
|
||||
```javascript
|
||||
const GROUP_ORDER = [
|
||||
'Core Settings',
|
||||
'Database',
|
||||
'Session',
|
||||
'NVD API',
|
||||
'Ivanti Integration',
|
||||
'Atlas Integration',
|
||||
'Jira Integration',
|
||||
'CARD Integration',
|
||||
'GitLab Integration',
|
||||
'Frontend Settings'
|
||||
];
|
||||
|
||||
const GROUP_DESCRIPTIONS = {
|
||||
'Core Settings': 'Server port, hostname, and CORS configuration',
|
||||
'Database': 'PostgreSQL connection string for persistent storage',
|
||||
'Session': 'Secret key for signing session cookies',
|
||||
'NVD API': 'National Vulnerability Database API key for CVE lookups',
|
||||
'Ivanti Integration': 'RiskSense platform credentials for vulnerability sync',
|
||||
'Atlas Integration': 'Atlas InfoSec API for action plan management',
|
||||
'Jira Integration': 'Jira Data Center for ticket creation and tracking',
|
||||
'CARD Integration': 'CARD asset ownership API for host lookups',
|
||||
'GitLab Integration': 'GitLab API for feedback submission (bug reports)',
|
||||
'Frontend Settings': 'React app API endpoint configuration'
|
||||
};
|
||||
```
|
||||
|
||||
### Optional (Skippable) Groups
|
||||
|
||||
Groups that present a skip prompt before entering: `NVD API`, `Ivanti Integration`, `Atlas Integration`, `Jira Integration`, `CARD Integration`, `GitLab Integration`.
|
||||
|
||||
Core Settings, Database, Session, and Frontend Settings are always prompted.
|
||||
|
||||
### Docker-Compose Parsing Approach
|
||||
|
||||
The parser reads `docker-compose.yml` line by line without a full YAML parser. It uses a simple state machine:
|
||||
|
||||
1. **Find service** — Look for a line matching `postgres:` (indented under `services:`)
|
||||
2. **Find environment** — Within the postgres service block, look for `environment:`
|
||||
3. **Extract vars** — Read `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` values
|
||||
4. **Find ports** — Look for `ports:` within the postgres service block
|
||||
5. **Extract host port** — Parse `- "host:container"` to get the host port (left side)
|
||||
|
||||
Shell variable substitution (`${VAR:-default}`) is resolved by extracting the default value after `:-`.
|
||||
|
||||
```javascript
|
||||
function resolveShellDefault(value) {
|
||||
const match = value.match(/\$\{[^:}]+:-([^}]+)\}/);
|
||||
return match ? match[1] : value;
|
||||
}
|
||||
```
|
||||
|
||||
If parsing fails at any step, the function returns `null` and the caller falls back to the hardcoded default: `postgresql://steam:sV4xmC9xAUCFop0ypxMVS056QgPqGrX@localhost:5433/cve_dashboard`.
|
||||
|
||||
### Env File Output Format
|
||||
|
||||
```
|
||||
# --- Core Settings ---
|
||||
PORT=3001
|
||||
API_HOST=localhost
|
||||
CORS_ORIGINS=http://localhost:3000
|
||||
|
||||
# --- Database ---
|
||||
DATABASE_URL=postgresql://steam:sV4xmC9xAUCFop0ypxMVS056QgPqGrX@localhost:5433/cve_dashboard
|
||||
|
||||
# --- Session ---
|
||||
SESSION_SECRET=my-very-long-secret-key-here
|
||||
|
||||
# --- Custom Variables ---
|
||||
MY_CUSTOM_VAR=preserved_value
|
||||
```
|
||||
|
||||
Values containing spaces, `#`, or quote characters are wrapped in double quotes:
|
||||
```
|
||||
SOME_VAR="value with spaces"
|
||||
ANOTHER_VAR="value#with#hashes"
|
||||
```
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||||
|
||||
### Property 1: Descriptor registry invariants
|
||||
|
||||
*For any* variable descriptor in the registry, the variable name must appear exactly once across all groups, and the group description must be non-empty and at most 120 characters long.
|
||||
|
||||
**Validates: Requirements 2.3, 2.5**
|
||||
|
||||
### Property 2: Variable ordering within groups
|
||||
|
||||
*For any* Variable_Group, all required variables in that group must appear before all optional variables in the descriptor list ordering.
|
||||
|
||||
**Validates: Requirements 2.4**
|
||||
|
||||
### Property 3: Group presentation order
|
||||
|
||||
*For any* two consecutive variables in the descriptor list, the first variable's group index in GROUP_ORDER must be less than or equal to the second variable's group index.
|
||||
|
||||
**Validates: Requirements 2.1**
|
||||
|
||||
### Property 4: Sensitive value masking
|
||||
|
||||
*For any* string value longer than 8 characters, `maskSensitive` should return a string showing only the first 4 and last 4 characters with asterisks in between. *For any* string value of 8 characters or fewer, `maskSensitive` should return the full value unchanged.
|
||||
|
||||
**Validates: Requirements 3.4**
|
||||
|
||||
### Property 5: Shell variable default resolution
|
||||
|
||||
*For any* string containing the pattern `${VARNAME:-defaultvalue}`, `resolveShellDefault` should extract and return `defaultvalue`. *For any* string not containing that pattern, it should return the original string.
|
||||
|
||||
**Validates: Requirements 4.1**
|
||||
|
||||
### Property 6: DATABASE_URL construction
|
||||
|
||||
*For any* valid Postgres credentials tuple (user, password, port, database) where port is an integer in [1, 65535], the constructed DATABASE_URL should equal `postgresql://{user}:{password}@localhost:{port}/{database}`.
|
||||
|
||||
**Validates: Requirements 4.2**
|
||||
|
||||
### Property 7: Derived URL defaults from PORT
|
||||
|
||||
*For any* valid port number P, the derived `REACT_APP_API_BASE` should equal `http://localhost:{P}/api`, `REACT_APP_API_HOST` should equal `http://localhost:{P}`, and `CORS_ORIGINS` should equal `http://localhost:3000`.
|
||||
|
||||
**Validates: Requirements 4.6**
|
||||
|
||||
### Property 8: Port validation
|
||||
|
||||
*For any* string input, `validatePort` should return true if and only if the trimmed value is a string representation of an integer in the range [1, 65535].
|
||||
|
||||
**Validates: Requirements 5.2**
|
||||
|
||||
### Property 9: CORS origins validation
|
||||
|
||||
*For any* comma-separated string, `validateCorsOrigins` should return true if and only if every trimmed entry starts with `http://` or `https://`.
|
||||
|
||||
**Validates: Requirements 5.3**
|
||||
|
||||
### Property 10: DATABASE_URL validation
|
||||
|
||||
*For any* string, `validateDatabaseUrl` should return true if and only if the string starts with `postgresql://` or equals the literal string `sqlite`.
|
||||
|
||||
**Validates: Requirements 5.4**
|
||||
|
||||
### Property 11: SESSION_SECRET validation
|
||||
|
||||
*For any* string, `validateSessionSecret` should return true if and only if its length is at least 16 characters.
|
||||
|
||||
**Validates: Requirements 5.6**
|
||||
|
||||
### Property 12: Required variable rejection of whitespace
|
||||
|
||||
*For any* string composed entirely of whitespace characters (including empty string), `validateRequired` should return false.
|
||||
|
||||
**Validates: Requirements 5.1**
|
||||
|
||||
### Property 13: Env value quoting
|
||||
|
||||
*For any* key-value pair, `generateEnvContent` should wrap the value in double quotes if and only if the value contains a space, `#`, or quote character. Values without those characters should appear unquoted.
|
||||
|
||||
**Validates: Requirements 6.3**
|
||||
|
||||
### Property 14: Optional variable omission
|
||||
|
||||
*For any* optional variable descriptor with no user-provided value and no default value, `generateEnvContent` should not include a line for that variable in the output.
|
||||
|
||||
**Validates: Requirements 6.4**
|
||||
|
||||
### Property 15: Skipped group exclusion
|
||||
|
||||
*For any* Variable_Group that the user declines, the generated env file content should contain no `KEY=value` lines for any variable belonging to that group.
|
||||
|
||||
**Validates: Requirements 7.2, 7.3**
|
||||
|
||||
### Property 16: Env file round-trip parsing
|
||||
|
||||
*For any* valid env file content produced by `generateEnvContent`, parsing it with `parseEnvFile` should recover all the original key-value pairs for managed variables.
|
||||
|
||||
**Validates: Requirements 9.1, 9.2**
|
||||
|
||||
### Property 17: Unmanaged variable preservation
|
||||
|
||||
*For any* existing env file containing lines with keys not in the managed variable list, those lines should appear unchanged in the "Custom Variables" section of the generated output.
|
||||
|
||||
**Validates: Requirements 9.4**
|
||||
|
||||
### Property 18: Managed key deduplication
|
||||
|
||||
*For any* managed variable name that appears both in the wizard-entered values and in the unmanaged lines parsed from an existing file, the generated output should contain exactly one occurrence of that key, using the wizard-entered value.
|
||||
|
||||
**Validates: Requirements 9.5**
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Categories
|
||||
|
||||
| Error Type | Handling Strategy |
|
||||
|---|---|
|
||||
| Missing project structure | Print error to stderr, exit code 1 |
|
||||
| SIGINT (Ctrl+C) | Close readline, print cancellation, exit code 1, no files written |
|
||||
| Invalid user input | Display format error, re-prompt same variable |
|
||||
| Docker-compose parse failure | Log warning, fall back to hardcoded defaults |
|
||||
| Existing env file unreadable | Log warning, use factory defaults, preserve file as backup |
|
||||
| File write failure | Print error with path and reason, exit immediately without writing remaining files |
|
||||
| Overwrite declined | Skip that file, continue with other files |
|
||||
|
||||
### Error Messages
|
||||
|
||||
All error messages follow the pattern: `Error: {what went wrong}. {what to do about it}.`
|
||||
|
||||
Examples:
|
||||
- `Error: This script must be run from the project root (backend/ and frontend/ directories not found). Run from the directory containing both folders.`
|
||||
- `Error: PORT must be an integer between 1 and 65535.`
|
||||
- `Error: Could not write to backend/.env (Permission denied). Check file permissions and try again.`
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
The wizard degrades gracefully when optional infrastructure is missing:
|
||||
- No `docker-compose.yml` → uses hardcoded DATABASE_URL default
|
||||
- Malformed `docker-compose.yml` → warns and uses hardcoded default
|
||||
- Existing `.env` unreadable → warns, backs up, uses factory defaults
|
||||
- No existing `.env` files → normal first-run behavior
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Unit tests cover the pure functions in isolation:
|
||||
|
||||
- `resolveShellDefault()` — various `${VAR:-default}` patterns
|
||||
- `parseDockerCompose()` — valid compose files, missing files, malformed content
|
||||
- `parseEnvFile()` — standard files, quoted values, comments, empty lines, malformed lines
|
||||
- `validatePort()` — boundary values (0, 1, 65535, 65536), non-numeric, floats
|
||||
- `validateCorsOrigins()` — single/multiple origins, invalid schemes, whitespace
|
||||
- `validateDatabaseUrl()` — postgresql://, sqlite, invalid prefixes
|
||||
- `validateSessionSecret()` — boundary at 15/16 characters
|
||||
- `validateRequired()` — empty, whitespace-only, valid values
|
||||
- `maskSensitive()` — short values (≤8), long values, exact boundary (8, 9 chars)
|
||||
- `generateEnvContent()` — quoting rules, group headers, omission of empty optionals
|
||||
|
||||
### Property-Based Tests
|
||||
|
||||
Property-based tests use [fast-check](https://github.com/dubzzz/fast-check) to verify universal properties across generated inputs. Each property test runs a minimum of 100 iterations.
|
||||
|
||||
Tests are tagged with: `Feature: config-wizard, Property {N}: {title}`
|
||||
|
||||
Properties to implement:
|
||||
1. Descriptor registry invariants (uniqueness, description length)
|
||||
2. Variable ordering within groups (required before optional)
|
||||
3. Group presentation order (monotonic group index)
|
||||
4. Sensitive value masking (length-based behavior)
|
||||
5. Shell variable default resolution (pattern extraction)
|
||||
6. DATABASE_URL construction (format correctness)
|
||||
7. Derived URL defaults from PORT (format correctness)
|
||||
8. Port validation (range check)
|
||||
9. CORS origins validation (scheme check)
|
||||
10. DATABASE_URL validation (prefix check)
|
||||
11. SESSION_SECRET validation (length check)
|
||||
12. Required variable rejection of whitespace
|
||||
13. Env value quoting (conditional wrapping)
|
||||
14. Optional variable omission
|
||||
15. Skipped group exclusion
|
||||
16. Env file round-trip parsing
|
||||
17. Unmanaged variable preservation
|
||||
18. Managed key deduplication
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Integration tests verify end-to-end flows using a temporary directory:
|
||||
|
||||
- Full wizard run with all defaults accepted → correct files written
|
||||
- Wizard run with existing `.env` files → values pre-filled correctly
|
||||
- Wizard run with skipped groups → those groups absent from output
|
||||
- SIGINT at various points → no files written
|
||||
- Missing project structure → error exit
|
||||
- File write permission error → graceful failure
|
||||
|
||||
### Test Runner
|
||||
|
||||
Tests use Jest (already configured in the project via `react-scripts test` for frontend). Backend tests run with:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npx jest __tests__/config-wizard*.test.js
|
||||
```
|
||||
|
||||
Property tests use `fast-check` as the generator library within Jest test cases.
|
||||
|
||||
Reference in New Issue
Block a user