popeye-cli 1.7.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.
Files changed (132) hide show
  1. package/README.md +102 -5
  2. package/cheatsheet.md +407 -0
  3. package/dist/cli/commands/db.d.ts +10 -0
  4. package/dist/cli/commands/db.d.ts.map +1 -0
  5. package/dist/cli/commands/db.js +240 -0
  6. package/dist/cli/commands/db.js.map +1 -0
  7. package/dist/cli/commands/doctor.d.ts +18 -0
  8. package/dist/cli/commands/doctor.d.ts.map +1 -0
  9. package/dist/cli/commands/doctor.js +255 -0
  10. package/dist/cli/commands/doctor.js.map +1 -0
  11. package/dist/cli/commands/index.d.ts +2 -0
  12. package/dist/cli/commands/index.d.ts.map +1 -1
  13. package/dist/cli/commands/index.js +2 -0
  14. package/dist/cli/commands/index.js.map +1 -1
  15. package/dist/cli/index.d.ts.map +1 -1
  16. package/dist/cli/index.js +3 -1
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cli/interactive.d.ts.map +1 -1
  19. package/dist/cli/interactive.js +96 -0
  20. package/dist/cli/interactive.js.map +1 -1
  21. package/dist/generators/admin-wizard.d.ts +25 -0
  22. package/dist/generators/admin-wizard.d.ts.map +1 -0
  23. package/dist/generators/admin-wizard.js +123 -0
  24. package/dist/generators/admin-wizard.js.map +1 -0
  25. package/dist/generators/all.d.ts.map +1 -1
  26. package/dist/generators/all.js +10 -3
  27. package/dist/generators/all.js.map +1 -1
  28. package/dist/generators/database.d.ts +58 -0
  29. package/dist/generators/database.d.ts.map +1 -0
  30. package/dist/generators/database.js +229 -0
  31. package/dist/generators/database.js.map +1 -0
  32. package/dist/generators/fullstack.d.ts.map +1 -1
  33. package/dist/generators/fullstack.js +23 -7
  34. package/dist/generators/fullstack.js.map +1 -1
  35. package/dist/generators/index.d.ts +2 -0
  36. package/dist/generators/index.d.ts.map +1 -1
  37. package/dist/generators/index.js +2 -0
  38. package/dist/generators/index.js.map +1 -1
  39. package/dist/generators/templates/admin-wizard-python.d.ts +32 -0
  40. package/dist/generators/templates/admin-wizard-python.d.ts.map +1 -0
  41. package/dist/generators/templates/admin-wizard-python.js +425 -0
  42. package/dist/generators/templates/admin-wizard-python.js.map +1 -0
  43. package/dist/generators/templates/admin-wizard-react.d.ts +48 -0
  44. package/dist/generators/templates/admin-wizard-react.d.ts.map +1 -0
  45. package/dist/generators/templates/admin-wizard-react.js +554 -0
  46. package/dist/generators/templates/admin-wizard-react.js.map +1 -0
  47. package/dist/generators/templates/database-docker.d.ts +23 -0
  48. package/dist/generators/templates/database-docker.d.ts.map +1 -0
  49. package/dist/generators/templates/database-docker.js +221 -0
  50. package/dist/generators/templates/database-docker.js.map +1 -0
  51. package/dist/generators/templates/database-python.d.ts +54 -0
  52. package/dist/generators/templates/database-python.d.ts.map +1 -0
  53. package/dist/generators/templates/database-python.js +723 -0
  54. package/dist/generators/templates/database-python.js.map +1 -0
  55. package/dist/generators/templates/database-typescript.d.ts +34 -0
  56. package/dist/generators/templates/database-typescript.d.ts.map +1 -0
  57. package/dist/generators/templates/database-typescript.js +232 -0
  58. package/dist/generators/templates/database-typescript.js.map +1 -0
  59. package/dist/generators/templates/fullstack.d.ts.map +1 -1
  60. package/dist/generators/templates/fullstack.js +29 -0
  61. package/dist/generators/templates/fullstack.js.map +1 -1
  62. package/dist/generators/templates/index.d.ts +5 -0
  63. package/dist/generators/templates/index.d.ts.map +1 -1
  64. package/dist/generators/templates/index.js +5 -0
  65. package/dist/generators/templates/index.js.map +1 -1
  66. package/dist/state/index.d.ts +10 -0
  67. package/dist/state/index.d.ts.map +1 -1
  68. package/dist/state/index.js +21 -0
  69. package/dist/state/index.js.map +1 -1
  70. package/dist/types/database-runtime.d.ts +86 -0
  71. package/dist/types/database-runtime.d.ts.map +1 -0
  72. package/dist/types/database-runtime.js +61 -0
  73. package/dist/types/database-runtime.js.map +1 -0
  74. package/dist/types/database.d.ts +85 -0
  75. package/dist/types/database.d.ts.map +1 -0
  76. package/dist/types/database.js +71 -0
  77. package/dist/types/database.js.map +1 -0
  78. package/dist/types/index.d.ts +2 -0
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/index.js +4 -0
  81. package/dist/types/index.js.map +1 -1
  82. package/dist/types/workflow.d.ts +21 -0
  83. package/dist/types/workflow.d.ts.map +1 -1
  84. package/dist/types/workflow.js +2 -0
  85. package/dist/types/workflow.js.map +1 -1
  86. package/dist/workflow/db-setup-runner.d.ts +63 -0
  87. package/dist/workflow/db-setup-runner.d.ts.map +1 -0
  88. package/dist/workflow/db-setup-runner.js +336 -0
  89. package/dist/workflow/db-setup-runner.js.map +1 -0
  90. package/dist/workflow/db-state-machine.d.ts +30 -0
  91. package/dist/workflow/db-state-machine.d.ts.map +1 -0
  92. package/dist/workflow/db-state-machine.js +51 -0
  93. package/dist/workflow/db-state-machine.js.map +1 -0
  94. package/dist/workflow/index.d.ts +2 -0
  95. package/dist/workflow/index.d.ts.map +1 -1
  96. package/dist/workflow/index.js +2 -0
  97. package/dist/workflow/index.js.map +1 -1
  98. package/package.json +1 -1
  99. package/src/cli/commands/db.ts +281 -0
  100. package/src/cli/commands/doctor.ts +273 -0
  101. package/src/cli/commands/index.ts +2 -0
  102. package/src/cli/index.ts +4 -0
  103. package/src/cli/interactive.ts +102 -0
  104. package/src/generators/admin-wizard.ts +146 -0
  105. package/src/generators/all.ts +10 -3
  106. package/src/generators/database.ts +286 -0
  107. package/src/generators/fullstack.ts +26 -9
  108. package/src/generators/index.ts +12 -0
  109. package/src/generators/templates/admin-wizard-python.ts +431 -0
  110. package/src/generators/templates/admin-wizard-react.ts +560 -0
  111. package/src/generators/templates/database-docker.ts +227 -0
  112. package/src/generators/templates/database-python.ts +734 -0
  113. package/src/generators/templates/database-typescript.ts +238 -0
  114. package/src/generators/templates/fullstack.ts +29 -0
  115. package/src/generators/templates/index.ts +5 -0
  116. package/src/state/index.ts +28 -0
  117. package/src/types/database-runtime.ts +69 -0
  118. package/src/types/database.ts +84 -0
  119. package/src/types/index.ts +29 -0
  120. package/src/types/workflow.ts +5 -0
  121. package/src/workflow/db-setup-runner.ts +391 -0
  122. package/src/workflow/db-state-machine.ts +58 -0
  123. package/src/workflow/index.ts +2 -0
  124. package/tests/generators/admin-wizard-orchestrator.test.ts +64 -0
  125. package/tests/generators/admin-wizard-templates.test.ts +366 -0
  126. package/tests/generators/cross-phase-integration.test.ts +383 -0
  127. package/tests/generators/database.test.ts +456 -0
  128. package/tests/generators/fe-be-db-integration.test.ts +613 -0
  129. package/tests/types/database-runtime.test.ts +158 -0
  130. package/tests/types/database.test.ts +187 -0
  131. package/tests/workflow/db-setup-runner.test.ts +211 -0
  132. package/tests/workflow/db-state-machine.test.ts +117 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Tests for database templates and generator orchestration
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+
7
+ // Python template imports
8
+ import {
9
+ generateDbConnection,
10
+ generateDbModels,
11
+ generateDbInit,
12
+ generateDbSettings,
13
+ generateAlembicIni,
14
+ generateAlembicEnvPy,
15
+ generateAlembicScriptMako,
16
+ generateInitialMigration,
17
+ generateDbVectorHelpers,
18
+ generateDbStartupHook,
19
+ generateDbHealthRoute,
20
+ generateDbConftest,
21
+ } from '../../src/generators/templates/database-python.js';
22
+
23
+ // Docker template imports
24
+ import {
25
+ generatePostgresServiceYaml,
26
+ generateDockerComposeWithDb,
27
+ generateAllDockerComposeWithDb,
28
+ generateDbEnvExample,
29
+ } from '../../src/generators/templates/database-docker.js';
30
+
31
+ // TypeScript template imports
32
+ import {
33
+ generatePrismaSchema,
34
+ } from '../../src/generators/templates/database-typescript.js';
35
+
36
+ // Generator orchestration imports
37
+ import {
38
+ augmentRequirements,
39
+ getDatabaseFiles,
40
+ DB_PYTHON_DEPS,
41
+ } from '../../src/generators/database.js';
42
+
43
+ const TEST_PACKAGE = 'my_project';
44
+ const TEST_PROJECT = 'my-project';
45
+
46
+ // ============================================================
47
+ // Python template tests
48
+ // ============================================================
49
+
50
+ describe('generateDbConnection', () => {
51
+ it('should include AsyncEngine and get_session', () => {
52
+ const result = generateDbConnection(TEST_PACKAGE);
53
+ expect(result).toContain('create_async_engine');
54
+ expect(result).toContain('async_sessionmaker');
55
+ expect(result).toContain('async def get_session');
56
+ expect(result).toContain('AsyncSession');
57
+ });
58
+
59
+ it('should include check_db_connection health helper', () => {
60
+ const result = generateDbConnection(TEST_PACKAGE);
61
+ expect(result).toContain('async def check_db_connection');
62
+ });
63
+ });
64
+
65
+ describe('generateDbModels', () => {
66
+ it('should include Base declarative base', () => {
67
+ const result = generateDbModels(TEST_PACKAGE);
68
+ expect(result).toContain('class Base(DeclarativeBase)');
69
+ });
70
+
71
+ it('should include TimestampMixin', () => {
72
+ const result = generateDbModels(TEST_PACKAGE);
73
+ expect(result).toContain('class TimestampMixin');
74
+ expect(result).toContain('created_at');
75
+ expect(result).toContain('updated_at');
76
+ });
77
+
78
+ it('should include AppSettings model', () => {
79
+ const result = generateDbModels(TEST_PACKAGE);
80
+ expect(result).toContain('class AppSettings');
81
+ expect(result).toContain('app_settings');
82
+ });
83
+ });
84
+
85
+ describe('generateDbInit', () => {
86
+ it('should re-export core components', () => {
87
+ const result = generateDbInit(TEST_PACKAGE);
88
+ expect(result).toContain('from .connection import');
89
+ expect(result).toContain('from .models import');
90
+ expect(result).toContain('Base');
91
+ expect(result).toContain('get_session');
92
+ expect(result).toContain('engine');
93
+ });
94
+ });
95
+
96
+ describe('generateDbSettings', () => {
97
+ it('should include DatabaseSettings with BaseSettings', () => {
98
+ const result = generateDbSettings(TEST_PACKAGE);
99
+ expect(result).toContain('class DatabaseSettings(BaseSettings)');
100
+ expect(result).toContain('database_url');
101
+ expect(result).toContain('is_configured');
102
+ });
103
+ });
104
+
105
+ describe('generateAlembicIni', () => {
106
+ it('should point to migrations directory', () => {
107
+ const result = generateAlembicIni(TEST_PACKAGE);
108
+ expect(result).toContain('script_location = migrations');
109
+ });
110
+
111
+ it('should include standard logging config', () => {
112
+ const result = generateAlembicIni(TEST_PACKAGE);
113
+ expect(result).toContain('[loggers]');
114
+ expect(result).toContain('[logger_alembic]');
115
+ });
116
+ });
117
+
118
+ describe('generateAlembicEnvPy', () => {
119
+ it('should include async_engine_from_config', () => {
120
+ const result = generateAlembicEnvPy(TEST_PACKAGE);
121
+ expect(result).toContain('async_engine_from_config');
122
+ });
123
+
124
+ it('should include target_metadata', () => {
125
+ const result = generateAlembicEnvPy(TEST_PACKAGE);
126
+ expect(result).toContain('target_metadata = Base.metadata');
127
+ });
128
+
129
+ it('should include run_migrations_online', () => {
130
+ const result = generateAlembicEnvPy(TEST_PACKAGE);
131
+ expect(result).toContain('async def run_migrations_online');
132
+ });
133
+
134
+ it('should use the package name in model import', () => {
135
+ const result = generateAlembicEnvPy(TEST_PACKAGE);
136
+ expect(result).toContain(`from src.${TEST_PACKAGE}.database.models import Base`);
137
+ });
138
+
139
+ it('should support autogenerate via Base.metadata', () => {
140
+ const result = generateAlembicEnvPy(TEST_PACKAGE);
141
+ expect(result).toContain('target_metadata');
142
+ expect(result).toContain('Base.metadata');
143
+ });
144
+ });
145
+
146
+ describe('generateAlembicScriptMako', () => {
147
+ it('should be a valid mako template', () => {
148
+ const result = generateAlembicScriptMako();
149
+ expect(result).toContain('${message}');
150
+ expect(result).toContain('${up_revision}');
151
+ expect(result).toContain('def upgrade');
152
+ expect(result).toContain('def downgrade');
153
+ });
154
+ });
155
+
156
+ describe('generateInitialMigration', () => {
157
+ it('should include CREATE EXTENSION IF NOT EXISTS vector', () => {
158
+ const result = generateInitialMigration(TEST_PACKAGE);
159
+ expect(result).toContain('CREATE EXTENSION IF NOT EXISTS vector');
160
+ });
161
+
162
+ it('should create app_settings table', () => {
163
+ const result = generateInitialMigration(TEST_PACKAGE);
164
+ expect(result).toContain('app_settings');
165
+ expect(result).toContain('op.create_table');
166
+ });
167
+
168
+ it('should include popeye:requires_extension=vector comment', () => {
169
+ const result = generateInitialMigration(TEST_PACKAGE);
170
+ expect(result).toContain('# popeye:requires_extension=vector');
171
+ });
172
+
173
+ it('should include downgrade that drops extension and table', () => {
174
+ const result = generateInitialMigration(TEST_PACKAGE);
175
+ expect(result).toContain('op.drop_table');
176
+ expect(result).toContain('DROP EXTENSION IF EXISTS vector');
177
+ });
178
+ });
179
+
180
+ describe('generateDbVectorHelpers', () => {
181
+ it('should include Vector import from pgvector', () => {
182
+ const result = generateDbVectorHelpers(TEST_PACKAGE);
183
+ expect(result).toContain('from pgvector.sqlalchemy import Vector');
184
+ });
185
+
186
+ it('should include cosine similarity helper', () => {
187
+ const result = generateDbVectorHelpers(TEST_PACKAGE);
188
+ expect(result).toContain('async def cosine_similarity_search');
189
+ });
190
+
191
+ it('should include vector sanity check', () => {
192
+ const result = generateDbVectorHelpers(TEST_PACKAGE);
193
+ expect(result).toContain('async def check_vector_extension');
194
+ });
195
+ });
196
+
197
+ describe('generateDbStartupHook', () => {
198
+ it('should gracefully skip when DATABASE_URL is not set', () => {
199
+ const result = generateDbStartupHook(TEST_PACKAGE);
200
+ expect(result).toContain('DATABASE_URL is not set');
201
+ expect(result).toContain('limited mode');
202
+ });
203
+
204
+ it('should check database connection when URL is configured', () => {
205
+ const result = generateDbStartupHook(TEST_PACKAGE);
206
+ expect(result).toContain('check_db_connection');
207
+ expect(result).toContain('DATABASE_URL detected');
208
+ });
209
+ });
210
+
211
+ describe('generateDbHealthRoute', () => {
212
+ it('should return 503 DB_NOT_READY when DATABASE_URL is not set', () => {
213
+ const result = generateDbHealthRoute(TEST_PACKAGE);
214
+ expect(result).toContain('503');
215
+ expect(result).toContain('DB_NOT_READY');
216
+ expect(result).toContain('DATABASE_URL not configured');
217
+ });
218
+
219
+ it('should check alembic_version table for migration status', () => {
220
+ const result = generateDbHealthRoute(TEST_PACKAGE);
221
+ expect(result).toContain('alembic_version');
222
+ expect(result).toContain('version_num');
223
+ });
224
+
225
+ it('should use GET /health/db route', () => {
226
+ const result = generateDbHealthRoute(TEST_PACKAGE);
227
+ expect(result).toContain('@router.get("/health/db")');
228
+ });
229
+ });
230
+
231
+ describe('generateDbConftest', () => {
232
+ it('should include test database URL override', () => {
233
+ const result = generateDbConftest(TEST_PACKAGE);
234
+ expect(result).toContain('TEST_DATABASE_URL');
235
+ });
236
+
237
+ it('should include async session fixture', () => {
238
+ const result = generateDbConftest(TEST_PACKAGE);
239
+ expect(result).toContain('async def db_session');
240
+ expect(result).toContain('AsyncSession');
241
+ });
242
+
243
+ it('should reference the package models', () => {
244
+ const result = generateDbConftest(TEST_PACKAGE);
245
+ expect(result).toContain(`from src.${TEST_PACKAGE}.database.models import Base`);
246
+ });
247
+ });
248
+
249
+ // ============================================================
250
+ // Docker template tests
251
+ // ============================================================
252
+
253
+ describe('generatePostgresServiceYaml', () => {
254
+ it('should include postgres image and healthcheck', () => {
255
+ const result = generatePostgresServiceYaml(TEST_PROJECT);
256
+ expect(result).toContain('postgres:16-alpine');
257
+ expect(result).toContain('pg_isready');
258
+ expect(result).toContain(TEST_PROJECT);
259
+ });
260
+
261
+ it('should include environment variables', () => {
262
+ const result = generatePostgresServiceYaml(TEST_PROJECT);
263
+ expect(result).toContain('POSTGRES_USER');
264
+ expect(result).toContain('POSTGRES_PASSWORD');
265
+ expect(result).toContain('POSTGRES_DB');
266
+ });
267
+ });
268
+
269
+ describe('generateDockerComposeWithDb', () => {
270
+ it('should include postgres service', () => {
271
+ const result = generateDockerComposeWithDb(TEST_PROJECT);
272
+ expect(result).toContain('postgres:');
273
+ expect(result).toContain('postgres:16-alpine');
274
+ expect(result).toContain('pg_isready');
275
+ });
276
+
277
+ it('should have backend depends_on postgres with service_healthy condition', () => {
278
+ const result = generateDockerComposeWithDb(TEST_PROJECT);
279
+ expect(result).toContain('condition: service_healthy');
280
+ });
281
+
282
+ it('should preserve all 4 existing services (frontend, backend, frontend-dev, backend-dev)', () => {
283
+ const result = generateDockerComposeWithDb(TEST_PROJECT);
284
+ expect(result).toContain('frontend:');
285
+ expect(result).toContain('backend:');
286
+ expect(result).toContain('frontend-dev:');
287
+ expect(result).toContain('backend-dev:');
288
+ });
289
+
290
+ it('should include postgres-data volume', () => {
291
+ const result = generateDockerComposeWithDb(TEST_PROJECT);
292
+ expect(result).toContain('postgres-data:');
293
+ });
294
+
295
+ it('should include DATABASE_URL for backend services', () => {
296
+ const result = generateDockerComposeWithDb(TEST_PROJECT);
297
+ expect(result).toContain('DATABASE_URL=postgresql+asyncpg://');
298
+ });
299
+ });
300
+
301
+ describe('generateAllDockerComposeWithDb', () => {
302
+ it('should include postgres, frontend, backend, and website services', () => {
303
+ const result = generateAllDockerComposeWithDb(TEST_PROJECT);
304
+ expect(result).toContain('postgres:');
305
+ expect(result).toContain('frontend:');
306
+ expect(result).toContain('backend:');
307
+ expect(result).toContain('website:');
308
+ });
309
+
310
+ it('should have backend depends_on postgres with healthy condition', () => {
311
+ const result = generateAllDockerComposeWithDb(TEST_PROJECT);
312
+ expect(result).toContain('condition: service_healthy');
313
+ });
314
+ });
315
+
316
+ describe('generateDbEnvExample', () => {
317
+ it('should include DATABASE_URL with postgresql', () => {
318
+ const result = generateDbEnvExample(TEST_PROJECT);
319
+ expect(result).toContain('DATABASE_URL=postgresql');
320
+ expect(result).not.toContain('sqlite');
321
+ });
322
+
323
+ it('should include POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB', () => {
324
+ const result = generateDbEnvExample(TEST_PROJECT);
325
+ expect(result).toContain('POSTGRES_USER=');
326
+ expect(result).toContain('POSTGRES_PASSWORD=');
327
+ expect(result).toContain('POSTGRES_DB=');
328
+ });
329
+
330
+ it('should include DB_VECTOR_REQUIRED', () => {
331
+ const result = generateDbEnvExample(TEST_PROJECT);
332
+ expect(result).toContain('DB_VECTOR_REQUIRED=true');
333
+ });
334
+ });
335
+
336
+ // ============================================================
337
+ // TypeScript/Prisma template tests (lighter - not wired in Phase 1)
338
+ // ============================================================
339
+
340
+ describe('generatePrismaSchema', () => {
341
+ it('should include postgresql datasource', () => {
342
+ const result = generatePrismaSchema(TEST_PROJECT);
343
+ expect(result).toContain('provider = "postgresql"');
344
+ expect(result).toContain('datasource db');
345
+ });
346
+
347
+ it('should include pgvector extension', () => {
348
+ const result = generatePrismaSchema(TEST_PROJECT);
349
+ expect(result).toContain('pgvector');
350
+ expect(result).toContain('postgresqlExtensions');
351
+ });
352
+
353
+ it('should include AppSettings model', () => {
354
+ const result = generatePrismaSchema(TEST_PROJECT);
355
+ expect(result).toContain('model AppSettings');
356
+ });
357
+ });
358
+
359
+ // ============================================================
360
+ // Generator orchestration tests
361
+ // ============================================================
362
+
363
+ describe('augmentRequirements', () => {
364
+ const testDeps = ['sqlalchemy[asyncio]>=2.0.0', 'asyncpg>=0.29.0'];
365
+
366
+ it('should preserve existing deps and append DB section', () => {
367
+ const base = `fastapi>=0.109.0\nuvicorn>=0.27.0\n`;
368
+ const result = augmentRequirements(base, testDeps);
369
+ expect(result).toContain('fastapi>=0.109.0');
370
+ expect(result).toContain('uvicorn>=0.27.0');
371
+ expect(result).toContain('# Database');
372
+ expect(result).toContain('sqlalchemy[asyncio]>=2.0.0');
373
+ expect(result).toContain('asyncpg>=0.29.0');
374
+ });
375
+
376
+ it('should not duplicate when Database section already exists', () => {
377
+ const base = `fastapi>=0.109.0\n\n# Database\nsqlalchemy>=2.0.0\n`;
378
+ const result = augmentRequirements(base, testDeps);
379
+ // Should be unchanged
380
+ expect(result).toBe(base);
381
+ });
382
+
383
+ it('should handle empty input', () => {
384
+ const result = augmentRequirements('', testDeps);
385
+ expect(result).toContain('# Database');
386
+ expect(result).toContain('sqlalchemy[asyncio]>=2.0.0');
387
+ });
388
+
389
+ it('should handle input with trailing whitespace', () => {
390
+ const base = `fastapi>=0.109.0\n\n \n`;
391
+ const result = augmentRequirements(base, testDeps);
392
+ expect(result).toContain('# Database');
393
+ // Should not have excessive blank lines
394
+ expect(result).not.toContain('\n\n\n\n');
395
+ });
396
+ });
397
+
398
+ describe('getDatabaseFiles', () => {
399
+ it('should list correct paths for sqlalchemy ORM', () => {
400
+ const files = getDatabaseFiles(TEST_PACKAGE, 'sqlalchemy');
401
+ expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/database/__init__.py`);
402
+ expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/database/connection.py`);
403
+ expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/database/models.py`);
404
+ expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/database/settings.py`);
405
+ expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/database/vector.py`);
406
+ expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/routes/health_db.py`);
407
+ expect(files).toContain(`apps/backend/src/${TEST_PACKAGE}/startup.py`);
408
+ expect(files).toContain('apps/backend/alembic.ini');
409
+ expect(files).toContain('apps/backend/migrations/env.py');
410
+ expect(files).toContain('apps/backend/migrations/script.py.mako');
411
+ expect(files).toContain('apps/backend/migrations/versions/001_initial.py');
412
+ expect(files).toContain('apps/backend/tests/conftest_db.py');
413
+ expect(files).toHaveLength(12);
414
+ });
415
+
416
+ it('should list correct paths for prisma ORM', () => {
417
+ const files = getDatabaseFiles(TEST_PROJECT, 'prisma');
418
+ expect(files).toContain('prisma/schema.prisma');
419
+ expect(files).toContain('prisma/seed.ts');
420
+ expect(files).toContain('src/db/client.ts');
421
+ expect(files).toContain('src/db/health.ts');
422
+ expect(files).toContain('src/db/vector.ts');
423
+ expect(files).toContain('src/db/index.ts');
424
+ expect(files).toHaveLength(6);
425
+ });
426
+
427
+ it('should return empty array for drizzle (not yet implemented)', () => {
428
+ const files = getDatabaseFiles(TEST_PACKAGE, 'drizzle');
429
+ expect(files).toHaveLength(0);
430
+ });
431
+ });
432
+
433
+ describe('DB_PYTHON_DEPS', () => {
434
+ it('should include all required database packages', () => {
435
+ expect(DB_PYTHON_DEPS).toContain('sqlalchemy[asyncio]>=2.0.0');
436
+ expect(DB_PYTHON_DEPS).toContain('asyncpg>=0.29.0');
437
+ expect(DB_PYTHON_DEPS).toContain('alembic>=1.13.0');
438
+ expect(DB_PYTHON_DEPS).toContain('pgvector>=0.2.5');
439
+ });
440
+ });
441
+
442
+ // ============================================================
443
+ // Generated README content test
444
+ // ============================================================
445
+
446
+ describe('generateRootReadme (with DB section)', () => {
447
+ it('should include Database section in generated README', async () => {
448
+ const { generateRootReadme } = await import('../../src/generators/templates/fullstack.js');
449
+ const readme = generateRootReadme('test-project', 'A test project');
450
+ expect(readme).toContain('## Database');
451
+ expect(readme).toContain('UNCONFIGURED');
452
+ expect(readme).toContain('/health/db');
453
+ expect(readme).toContain('alembic upgrade head');
454
+ expect(readme).toContain('DATABASE_URL');
455
+ });
456
+ });