popeye-cli 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -32
- package/cheatsheet.md +407 -0
- package/dist/cli/commands/db.d.ts +10 -0
- package/dist/cli/commands/db.d.ts.map +1 -0
- package/dist/cli/commands/db.js +240 -0
- package/dist/cli/commands/db.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +18 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +255 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +96 -0
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/admin-wizard.d.ts +25 -0
- package/dist/generators/admin-wizard.d.ts.map +1 -0
- package/dist/generators/admin-wizard.js +123 -0
- package/dist/generators/admin-wizard.js.map +1 -0
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +10 -3
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/database.d.ts +58 -0
- package/dist/generators/database.d.ts.map +1 -0
- package/dist/generators/database.js +229 -0
- package/dist/generators/database.js.map +1 -0
- package/dist/generators/fullstack.d.ts.map +1 -1
- package/dist/generators/fullstack.js +23 -7
- package/dist/generators/fullstack.js.map +1 -1
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/templates/admin-wizard-python.d.ts +32 -0
- package/dist/generators/templates/admin-wizard-python.d.ts.map +1 -0
- package/dist/generators/templates/admin-wizard-python.js +425 -0
- package/dist/generators/templates/admin-wizard-python.js.map +1 -0
- package/dist/generators/templates/admin-wizard-react.d.ts +48 -0
- package/dist/generators/templates/admin-wizard-react.d.ts.map +1 -0
- package/dist/generators/templates/admin-wizard-react.js +554 -0
- package/dist/generators/templates/admin-wizard-react.js.map +1 -0
- package/dist/generators/templates/database-docker.d.ts +23 -0
- package/dist/generators/templates/database-docker.d.ts.map +1 -0
- package/dist/generators/templates/database-docker.js +221 -0
- package/dist/generators/templates/database-docker.js.map +1 -0
- package/dist/generators/templates/database-python.d.ts +54 -0
- package/dist/generators/templates/database-python.d.ts.map +1 -0
- package/dist/generators/templates/database-python.js +723 -0
- package/dist/generators/templates/database-python.js.map +1 -0
- package/dist/generators/templates/database-typescript.d.ts +34 -0
- package/dist/generators/templates/database-typescript.d.ts.map +1 -0
- package/dist/generators/templates/database-typescript.js +232 -0
- package/dist/generators/templates/database-typescript.js.map +1 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -1
- package/dist/generators/templates/fullstack.js +29 -0
- package/dist/generators/templates/fullstack.js.map +1 -1
- package/dist/generators/templates/index.d.ts +5 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +5 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/state/index.d.ts +10 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +22 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +3 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +1 -0
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/database-runtime.d.ts +86 -0
- package/dist/types/database-runtime.d.ts.map +1 -0
- package/dist/types/database-runtime.js +61 -0
- package/dist/types/database-runtime.js.map +1 -0
- package/dist/types/database.d.ts +85 -0
- package/dist/types/database.d.ts.map +1 -0
- package/dist/types/database.js +71 -0
- package/dist/types/database.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/tester.d.ts +138 -0
- package/dist/types/tester.d.ts.map +1 -0
- package/dist/types/tester.js +110 -0
- package/dist/types/tester.js.map +1 -0
- package/dist/types/workflow.d.ts +166 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +14 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/db-setup-runner.d.ts +63 -0
- package/dist/workflow/db-setup-runner.d.ts.map +1 -0
- package/dist/workflow/db-setup-runner.js +336 -0
- package/dist/workflow/db-setup-runner.js.map +1 -0
- package/dist/workflow/db-state-machine.d.ts +30 -0
- package/dist/workflow/db-state-machine.d.ts.map +1 -0
- package/dist/workflow/db-state-machine.js +51 -0
- package/dist/workflow/db-state-machine.js.map +1 -0
- package/dist/workflow/execution-mode.js +2 -2
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +3 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/task-workflow.d.ts +5 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +172 -6
- package/dist/workflow/task-workflow.js.map +1 -1
- package/dist/workflow/tester.d.ts +120 -0
- package/dist/workflow/tester.d.ts.map +1 -0
- package/dist/workflow/tester.js +589 -0
- package/dist/workflow/tester.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +1 -1
- package/dist/workflow/workflow-logger.d.ts.map +1 -1
- package/dist/workflow/workflow-logger.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/db.ts +281 -0
- package/src/cli/commands/doctor.ts +273 -0
- package/src/cli/commands/index.ts +2 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/interactive.ts +102 -0
- package/src/generators/admin-wizard.ts +146 -0
- package/src/generators/all.ts +10 -3
- package/src/generators/database.ts +286 -0
- package/src/generators/fullstack.ts +26 -9
- package/src/generators/index.ts +12 -0
- package/src/generators/templates/admin-wizard-python.ts +431 -0
- package/src/generators/templates/admin-wizard-react.ts +560 -0
- package/src/generators/templates/database-docker.ts +227 -0
- package/src/generators/templates/database-python.ts +734 -0
- package/src/generators/templates/database-typescript.ts +238 -0
- package/src/generators/templates/fullstack.ts +29 -0
- package/src/generators/templates/index.ts +5 -0
- package/src/state/index.ts +29 -0
- package/src/types/consensus.ts +3 -0
- package/src/types/database-runtime.ts +69 -0
- package/src/types/database.ts +84 -0
- package/src/types/index.ts +50 -0
- package/src/types/tester.ts +136 -0
- package/src/types/workflow.ts +31 -0
- package/src/workflow/db-setup-runner.ts +391 -0
- package/src/workflow/db-state-machine.ts +58 -0
- package/src/workflow/execution-mode.ts +2 -2
- package/src/workflow/index.ts +3 -0
- package/src/workflow/task-workflow.ts +227 -5
- package/src/workflow/tester.ts +723 -0
- package/src/workflow/workflow-logger.ts +2 -0
- package/tests/generators/admin-wizard-orchestrator.test.ts +64 -0
- package/tests/generators/admin-wizard-templates.test.ts +366 -0
- package/tests/generators/cross-phase-integration.test.ts +383 -0
- package/tests/generators/database.test.ts +456 -0
- package/tests/generators/fe-be-db-integration.test.ts +613 -0
- package/tests/types/database-runtime.test.ts +158 -0
- package/tests/types/database.test.ts +187 -0
- package/tests/types/tester.test.ts +174 -0
- package/tests/workflow/db-setup-runner.test.ts +211 -0
- package/tests/workflow/db-state-machine.test.ts +117 -0
- package/tests/workflow/tester.test.ts +401 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-phase integration tests
|
|
3
|
+
* Verifies that Phase 1 (DB types/templates), Phase 2 (state machine/runner),
|
|
4
|
+
* and Phase 3 (admin wizard) work together correctly with no disconnections.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
|
|
9
|
+
// Phase 1: types + templates
|
|
10
|
+
import { DbStatusSchema, DbSetupStepSchema } from '../../src/types/database.js';
|
|
11
|
+
import { SetupResultSchema } from '../../src/types/database-runtime.js';
|
|
12
|
+
import { getDatabaseFiles } from '../../src/generators/database.js';
|
|
13
|
+
import { generateDbEnvExample } from '../../src/generators/templates/database-docker.js';
|
|
14
|
+
|
|
15
|
+
// Phase 2: state machine
|
|
16
|
+
import {
|
|
17
|
+
canTransition,
|
|
18
|
+
getAvailableTransitions,
|
|
19
|
+
} from '../../src/workflow/db-state-machine.js';
|
|
20
|
+
|
|
21
|
+
// Phase 3: admin wizard
|
|
22
|
+
import { getAdminWizardFiles } from '../../src/generators/admin-wizard.js';
|
|
23
|
+
import {
|
|
24
|
+
generateFastAPIMainWithAdmin,
|
|
25
|
+
generateAdminDbRoutes,
|
|
26
|
+
} from '../../src/generators/templates/admin-wizard-python.js';
|
|
27
|
+
import {
|
|
28
|
+
generateAppTsxWithAdmin,
|
|
29
|
+
generateDbStatusBanner,
|
|
30
|
+
} from '../../src/generators/templates/admin-wizard-react.js';
|
|
31
|
+
|
|
32
|
+
// Composite: fullstack + all file lists
|
|
33
|
+
import { getFullstackProjectFiles } from '../../src/generators/fullstack.js';
|
|
34
|
+
import { getAllProjectFiles } from '../../src/generators/all.js';
|
|
35
|
+
|
|
36
|
+
const TEST_PACKAGE = 'my_project';
|
|
37
|
+
const TEST_PROJECT = 'my-project';
|
|
38
|
+
|
|
39
|
+
// ============================================================
|
|
40
|
+
// Phase 1 ↔ Phase 3: Generated backend wiring
|
|
41
|
+
// ============================================================
|
|
42
|
+
|
|
43
|
+
describe('Phase 1 ↔ Phase 3: Backend router wiring', () => {
|
|
44
|
+
const mainPy = generateFastAPIMainWithAdmin(TEST_PROJECT, TEST_PACKAGE);
|
|
45
|
+
|
|
46
|
+
it('should include Phase 1 health_db_router in main.py', () => {
|
|
47
|
+
expect(mainPy).toContain('health_db_router');
|
|
48
|
+
expect(mainPy).toContain(
|
|
49
|
+
`from ${TEST_PACKAGE}.routes.health_db import router as health_db_router`
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should include Phase 3 admin_db_router in main.py', () => {
|
|
54
|
+
expect(mainPy).toContain('admin_db_router');
|
|
55
|
+
expect(mainPy).toContain(
|
|
56
|
+
`from ${TEST_PACKAGE}.routes.admin_db import router as admin_db_router`
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should wire both routers via app.include_router()', () => {
|
|
61
|
+
expect(mainPy).toContain('app.include_router(health_db_router)');
|
|
62
|
+
expect(mainPy).toContain('app.include_router(admin_db_router)');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should preserve base endpoints alongside both routers', () => {
|
|
66
|
+
expect(mainPy).toContain('@app.get("/")');
|
|
67
|
+
expect(mainPy).toContain('@app.get("/health")');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Phase 1 ↔ Phase 3: Admin routes reference Phase 1 concepts', () => {
|
|
72
|
+
const adminRoutes = generateAdminDbRoutes(TEST_PACKAGE);
|
|
73
|
+
|
|
74
|
+
it('should reference alembic for migrations', () => {
|
|
75
|
+
expect(adminRoutes).toContain('alembic upgrade head');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should reference asyncpg for DB connectivity', () => {
|
|
79
|
+
expect(adminRoutes).toContain('asyncpg.connect');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should check alembic_version table for migration status', () => {
|
|
83
|
+
expect(adminRoutes).toContain('alembic_version');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ============================================================
|
|
88
|
+
// Phase 1 ↔ Phase 3: Environment variable alignment
|
|
89
|
+
// ============================================================
|
|
90
|
+
|
|
91
|
+
describe('Phase 1 ↔ Phase 3: Environment variable alignment', () => {
|
|
92
|
+
const envExample = generateDbEnvExample(TEST_PROJECT);
|
|
93
|
+
|
|
94
|
+
it('should include Phase 1 DATABASE_URL', () => {
|
|
95
|
+
expect(envExample).toContain('DATABASE_URL=postgresql+asyncpg://');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should include Phase 1 POSTGRES vars', () => {
|
|
99
|
+
expect(envExample).toContain('POSTGRES_USER=postgres');
|
|
100
|
+
expect(envExample).toContain('POSTGRES_PASSWORD=postgres');
|
|
101
|
+
expect(envExample).toContain('POSTGRES_DB=');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should include Phase 1 vector support flag', () => {
|
|
105
|
+
expect(envExample).toContain('DB_VECTOR_REQUIRED=true');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should include Phase 3 ADMIN_SETUP_TOKEN', () => {
|
|
109
|
+
expect(envExample).toContain('ADMIN_SETUP_TOKEN=change-me-to-a-random-string');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should have both Database and Admin sections', () => {
|
|
113
|
+
expect(envExample).toContain('# Database');
|
|
114
|
+
expect(envExample).toContain('# Admin Wizard');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ============================================================
|
|
119
|
+
// Phase 2 ↔ Phase 3: Status value alignment
|
|
120
|
+
// ============================================================
|
|
121
|
+
|
|
122
|
+
describe('Phase 2 ↔ Phase 3: Admin wizard status values match DbStatusSchema', () => {
|
|
123
|
+
const validStatuses = DbStatusSchema.options;
|
|
124
|
+
const adminRoutes = generateAdminDbRoutes(TEST_PACKAGE);
|
|
125
|
+
|
|
126
|
+
it('DbStatusSchema should have all 5 lifecycle states', () => {
|
|
127
|
+
expect(validStatuses).toContain('unconfigured');
|
|
128
|
+
expect(validStatuses).toContain('configured');
|
|
129
|
+
expect(validStatuses).toContain('applying');
|
|
130
|
+
expect(validStatuses).toContain('ready');
|
|
131
|
+
expect(validStatuses).toContain('error');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('admin GET /status should only use valid DbStatus values', () => {
|
|
135
|
+
// Extract all status = "..." assignments from the generated Python code
|
|
136
|
+
const statusAssignments = adminRoutes.match(/status\s*=\s*"([^"]+)"/g) || [];
|
|
137
|
+
const assignedValues = statusAssignments.map((s) =>
|
|
138
|
+
s.match(/"([^"]+)"/)?.[1]
|
|
139
|
+
).filter(Boolean);
|
|
140
|
+
|
|
141
|
+
for (const value of assignedValues) {
|
|
142
|
+
expect(
|
|
143
|
+
validStatuses.includes(value as typeof validStatuses[number]),
|
|
144
|
+
`Admin route status "${value}" is not in DbStatusSchema: [${validStatuses.join(', ')}]`
|
|
145
|
+
).toBe(true);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('admin POST /apply should return valid final status values', () => {
|
|
150
|
+
// /apply endpoint returns "ready" or "error" as final_status
|
|
151
|
+
expect(adminRoutes).toContain('final_status = "ready" if');
|
|
152
|
+
expect(adminRoutes).toContain('else "error"');
|
|
153
|
+
expect(validStatuses).toContain('ready');
|
|
154
|
+
expect(validStatuses).toContain('error');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('admin GET /status default should be "unconfigured"', () => {
|
|
158
|
+
expect(adminRoutes).toContain('status = "unconfigured"');
|
|
159
|
+
expect(validStatuses).toContain('unconfigured');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should NOT contain any non-schema status values', () => {
|
|
163
|
+
// Ensure we never use invented statuses like "pending_migration"
|
|
164
|
+
expect(adminRoutes).not.toContain('"pending_migration"');
|
|
165
|
+
expect(adminRoutes).not.toContain('"pending"');
|
|
166
|
+
expect(adminRoutes).not.toContain('"migrating"');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Phase 2 ↔ Phase 3: State transitions match admin wizard flow', () => {
|
|
171
|
+
it('configured -> applying should be valid (admin /apply triggers this)', () => {
|
|
172
|
+
expect(canTransition('configured', 'applying')).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('applying -> ready should be valid (successful /apply result)', () => {
|
|
176
|
+
expect(canTransition('applying', 'ready')).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('applying -> error should be valid (failed /apply result)', () => {
|
|
180
|
+
expect(canTransition('applying', 'error')).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('error -> configured should be valid (admin /retry resets to configured)', () => {
|
|
184
|
+
expect(canTransition('error', 'configured')).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('unconfigured -> configured should be valid (initial setup)', () => {
|
|
188
|
+
expect(canTransition('unconfigured', 'configured')).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('ready -> configured should be valid (reconfiguration)', () => {
|
|
192
|
+
expect(canTransition('ready', 'configured')).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('applying should only go to ready or error', () => {
|
|
196
|
+
const targets = getAvailableTransitions('applying');
|
|
197
|
+
expect(targets).toEqual(['ready', 'error']);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('Phase 2 ↔ Phase 3: React banner uses valid statuses', () => {
|
|
202
|
+
const banner = generateDbStatusBanner();
|
|
203
|
+
|
|
204
|
+
it('should check for "ready" status to hide banner', () => {
|
|
205
|
+
expect(banner).toContain("status === 'ready'");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should check for "error" status to show error message', () => {
|
|
209
|
+
expect(banner).toContain("status === 'error'");
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ============================================================
|
|
214
|
+
// Phase 1 ↔ Phase 2: Schema consistency
|
|
215
|
+
// ============================================================
|
|
216
|
+
|
|
217
|
+
describe('Phase 1 ↔ Phase 2: Schema types used in runtime', () => {
|
|
218
|
+
it('SetupResultSchema.finalStatus should use DbStatusSchema', () => {
|
|
219
|
+
// Verify SetupResult validates with a valid DbStatus
|
|
220
|
+
const result = SetupResultSchema.safeParse({
|
|
221
|
+
success: true,
|
|
222
|
+
steps: [],
|
|
223
|
+
totalDurationMs: 100,
|
|
224
|
+
finalStatus: 'ready',
|
|
225
|
+
});
|
|
226
|
+
expect(result.success).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('SetupResultSchema should reject invalid finalStatus', () => {
|
|
230
|
+
const result = SetupResultSchema.safeParse({
|
|
231
|
+
success: true,
|
|
232
|
+
steps: [],
|
|
233
|
+
totalDurationMs: 100,
|
|
234
|
+
finalStatus: 'pending_migration',
|
|
235
|
+
});
|
|
236
|
+
expect(result.success).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('DbSetupStepSchema should have all 6 pipeline steps', () => {
|
|
240
|
+
const steps = DbSetupStepSchema.options;
|
|
241
|
+
expect(steps).toHaveLength(6);
|
|
242
|
+
expect(steps).toContain('check_connection');
|
|
243
|
+
expect(steps).toContain('ensure_extensions');
|
|
244
|
+
expect(steps).toContain('apply_migrations');
|
|
245
|
+
expect(steps).toContain('seed_minimal');
|
|
246
|
+
expect(steps).toContain('readiness_tests');
|
|
247
|
+
expect(steps).toContain('mark_ready');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ============================================================
|
|
252
|
+
// All phases combined: File list completeness
|
|
253
|
+
// ============================================================
|
|
254
|
+
|
|
255
|
+
describe('All phases: getFullstackProjectFiles includes all layers', () => {
|
|
256
|
+
const files = getFullstackProjectFiles(TEST_PROJECT);
|
|
257
|
+
const dbFiles = getDatabaseFiles(TEST_PACKAGE, 'sqlalchemy');
|
|
258
|
+
const adminFiles = getAdminWizardFiles(TEST_PACKAGE);
|
|
259
|
+
|
|
260
|
+
it('should include all Phase 1 database files', () => {
|
|
261
|
+
for (const f of dbFiles) {
|
|
262
|
+
expect(files, `Missing Phase 1 file: ${f}`).toContain(f);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should include all Phase 3 admin wizard files', () => {
|
|
267
|
+
for (const f of adminFiles) {
|
|
268
|
+
expect(files, `Missing Phase 3 file: ${f}`).toContain(f);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should include Phase 1 health route', () => {
|
|
273
|
+
expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/routes/health_db.py`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should include Phase 3 admin route', () => {
|
|
277
|
+
expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/routes/admin_db.py`);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should include Phase 1 alembic config', () => {
|
|
281
|
+
expect(files).toContain('apps/backend/alembic.ini');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should include Phase 3 admin middleware', () => {
|
|
285
|
+
expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/middleware/admin_auth.py`);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('All phases: getAllProjectFiles includes all layers', () => {
|
|
290
|
+
const files = getAllProjectFiles(TEST_PROJECT);
|
|
291
|
+
const dbFiles = getDatabaseFiles(TEST_PACKAGE, 'sqlalchemy');
|
|
292
|
+
const adminFiles = getAdminWizardFiles(TEST_PACKAGE);
|
|
293
|
+
|
|
294
|
+
it('should include all Phase 1 database files', () => {
|
|
295
|
+
for (const f of dbFiles) {
|
|
296
|
+
expect(files, `Missing Phase 1 file in all: ${f}`).toContain(f);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should include all Phase 3 admin wizard files', () => {
|
|
301
|
+
for (const f of adminFiles) {
|
|
302
|
+
expect(files, `Missing Phase 3 file in all: ${f}`).toContain(f);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should include website files (unique to all projects)', () => {
|
|
307
|
+
expect(files).toContain('apps/website/package.json');
|
|
308
|
+
expect(files).toContain('apps/website/src/app/page.tsx');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should include shared packages (unique to all projects)', () => {
|
|
312
|
+
expect(files).toContain('packages/design-tokens/package.json');
|
|
313
|
+
expect(files).toContain('packages/ui/package.json');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ============================================================
|
|
318
|
+
// All phases: Frontend wiring
|
|
319
|
+
// ============================================================
|
|
320
|
+
|
|
321
|
+
describe('All phases: Generated App.tsx integrates admin wizard', () => {
|
|
322
|
+
const appTsx = generateAppTsxWithAdmin(TEST_PROJECT);
|
|
323
|
+
|
|
324
|
+
it('should import DbStatusBanner (Phase 3)', () => {
|
|
325
|
+
expect(appTsx).toContain('DbStatusBanner');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should import DbSetupStepper (Phase 3)', () => {
|
|
329
|
+
expect(appTsx).toContain('DbSetupStepper');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should manage showWizard state for overlay', () => {
|
|
333
|
+
expect(appTsx).toContain('showWizard');
|
|
334
|
+
expect(appTsx).toContain('setShowWizard');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should render the banner and stepper', () => {
|
|
338
|
+
expect(appTsx).toContain('<DbStatusBanner');
|
|
339
|
+
expect(appTsx).toContain('<DbSetupStepper');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should still include the project name', () => {
|
|
343
|
+
expect(appTsx).toContain(TEST_PROJECT);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ============================================================
|
|
348
|
+
// Cross-phase: No duplicate/conflicting file paths
|
|
349
|
+
// ============================================================
|
|
350
|
+
|
|
351
|
+
describe('Cross-phase: No file path conflicts between layers', () => {
|
|
352
|
+
const dbFiles = getDatabaseFiles(TEST_PACKAGE, 'sqlalchemy');
|
|
353
|
+
const adminFiles = getAdminWizardFiles(TEST_PACKAGE);
|
|
354
|
+
|
|
355
|
+
it('should have no overlapping files between Phase 1 and Phase 3', () => {
|
|
356
|
+
const overlap = dbFiles.filter((f) => adminFiles.includes(f));
|
|
357
|
+
expect(overlap, `Overlapping files: ${overlap.join(', ')}`).toHaveLength(0);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('Phase 1 files should be in database/ and migrations/', () => {
|
|
361
|
+
const dbBackendFiles = dbFiles.filter((f) => f.startsWith('apps/backend/'));
|
|
362
|
+
for (const f of dbBackendFiles) {
|
|
363
|
+
const hasExpectedPath =
|
|
364
|
+
f.includes('/database/') ||
|
|
365
|
+
f.includes('/routes/health_db') ||
|
|
366
|
+
f.includes('/startup.py') ||
|
|
367
|
+
f.includes('alembic') ||
|
|
368
|
+
f.includes('migrations/') ||
|
|
369
|
+
f.includes('conftest_db');
|
|
370
|
+
expect(hasExpectedPath, `Unexpected Phase 1 path: ${f}`).toBe(true);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('Phase 3 files should be in middleware/ and admin/', () => {
|
|
375
|
+
for (const f of adminFiles) {
|
|
376
|
+
const hasExpectedPath =
|
|
377
|
+
f.includes('/middleware/') ||
|
|
378
|
+
f.includes('/routes/admin_db') ||
|
|
379
|
+
f.includes('/admin/');
|
|
380
|
+
expect(hasExpectedPath, `Unexpected Phase 3 path: ${f}`).toBe(true);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
});
|