tlc-claude-code 1.2.28 → 1.3.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 (87) hide show
  1. package/README.md +9 -4
  2. package/dashboard/dist/components/UsagePane.d.ts +13 -0
  3. package/dashboard/dist/components/UsagePane.js +51 -0
  4. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  5. package/dashboard/dist/components/UsagePane.test.js +142 -0
  6. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  7. package/dashboard/dist/components/WorkspaceDocsPane.js +146 -0
  8. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  9. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  10. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  11. package/dashboard/dist/components/WorkspacePane.js +17 -0
  12. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  13. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  14. package/package.json +15 -4
  15. package/scripts/capture-screenshots.js +170 -0
  16. package/scripts/docs-update.js +253 -0
  17. package/scripts/generate-screenshots.js +321 -0
  18. package/scripts/project-docs.js +377 -0
  19. package/scripts/vps-setup.sh +477 -0
  20. package/server/lib/architecture-command.js +450 -0
  21. package/server/lib/architecture-command.test.js +754 -0
  22. package/server/lib/ast-analyzer.js +324 -0
  23. package/server/lib/ast-analyzer.test.js +437 -0
  24. package/server/lib/auth-system.test.js +4 -1
  25. package/server/lib/boundary-detector.js +427 -0
  26. package/server/lib/boundary-detector.test.js +320 -0
  27. package/server/lib/budget-alerts.js +138 -0
  28. package/server/lib/budget-alerts.test.js +235 -0
  29. package/server/lib/candidates-tracker.js +210 -0
  30. package/server/lib/candidates-tracker.test.js +300 -0
  31. package/server/lib/checkpoint-manager.js +251 -0
  32. package/server/lib/checkpoint-manager.test.js +474 -0
  33. package/server/lib/circular-detector.js +337 -0
  34. package/server/lib/circular-detector.test.js +353 -0
  35. package/server/lib/cohesion-analyzer.js +310 -0
  36. package/server/lib/cohesion-analyzer.test.js +447 -0
  37. package/server/lib/contract-testing.js +625 -0
  38. package/server/lib/contract-testing.test.js +342 -0
  39. package/server/lib/conversion-planner.js +469 -0
  40. package/server/lib/conversion-planner.test.js +361 -0
  41. package/server/lib/convert-command.js +351 -0
  42. package/server/lib/convert-command.test.js +608 -0
  43. package/server/lib/coupling-calculator.js +189 -0
  44. package/server/lib/coupling-calculator.test.js +509 -0
  45. package/server/lib/dependency-graph.js +367 -0
  46. package/server/lib/dependency-graph.test.js +516 -0
  47. package/server/lib/duplication-detector.js +349 -0
  48. package/server/lib/duplication-detector.test.js +401 -0
  49. package/server/lib/example-service.js +616 -0
  50. package/server/lib/example-service.test.js +397 -0
  51. package/server/lib/impact-scorer.js +184 -0
  52. package/server/lib/impact-scorer.test.js +211 -0
  53. package/server/lib/mermaid-generator.js +358 -0
  54. package/server/lib/mermaid-generator.test.js +301 -0
  55. package/server/lib/messaging-patterns.js +750 -0
  56. package/server/lib/messaging-patterns.test.js +213 -0
  57. package/server/lib/microservice-template.js +386 -0
  58. package/server/lib/microservice-template.test.js +325 -0
  59. package/server/lib/new-project-microservice.js +450 -0
  60. package/server/lib/new-project-microservice.test.js +600 -0
  61. package/server/lib/refactor-command.js +326 -0
  62. package/server/lib/refactor-command.test.js +528 -0
  63. package/server/lib/refactor-executor.js +254 -0
  64. package/server/lib/refactor-executor.test.js +305 -0
  65. package/server/lib/refactor-observer.js +292 -0
  66. package/server/lib/refactor-observer.test.js +422 -0
  67. package/server/lib/refactor-progress.js +193 -0
  68. package/server/lib/refactor-progress.test.js +251 -0
  69. package/server/lib/refactor-reporter.js +237 -0
  70. package/server/lib/refactor-reporter.test.js +247 -0
  71. package/server/lib/semantic-analyzer.js +198 -0
  72. package/server/lib/semantic-analyzer.test.js +474 -0
  73. package/server/lib/service-scaffold.js +486 -0
  74. package/server/lib/service-scaffold.test.js +373 -0
  75. package/server/lib/shared-kernel.js +578 -0
  76. package/server/lib/shared-kernel.test.js +255 -0
  77. package/server/lib/traefik-config.js +282 -0
  78. package/server/lib/traefik-config.test.js +312 -0
  79. package/server/lib/usage-command.js +218 -0
  80. package/server/lib/usage-command.test.js +391 -0
  81. package/server/lib/usage-formatter.js +192 -0
  82. package/server/lib/usage-formatter.test.js +267 -0
  83. package/server/lib/usage-history.js +122 -0
  84. package/server/lib/usage-history.test.js +206 -0
  85. package/server/package-lock.json +14 -0
  86. package/server/package.json +1 -0
  87. package/templates/docs-sync.yml +91 -0
@@ -0,0 +1,600 @@
1
+ /**
2
+ * New Project Microservice Command Tests
3
+ * Integration orchestrator for /tlc:new-project --architecture microservice
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+
8
+ describe('NewProjectMicroservice', () => {
9
+ // Mock dependencies
10
+ const createMockDependencies = () => ({
11
+ microserviceTemplate: {
12
+ generateStructure: vi.fn().mockReturnValue({
13
+ directories: ['project', 'project/services'],
14
+ files: ['project/package.json', 'project/docker-compose.yml'],
15
+ }),
16
+ generateRootPackageJson: vi.fn().mockReturnValue('{"name": "project"}'),
17
+ generateDockerCompose: vi.fn().mockReturnValue('version: 3.8'),
18
+ generateEnvExample: vi.fn().mockReturnValue('NODE_ENV=development'),
19
+ generateReadme: vi.fn().mockReturnValue('# Project'),
20
+ },
21
+ traefikConfig: {
22
+ generateTraefikYml: vi.fn().mockReturnValue('entryPoints:'),
23
+ generateDynamicConfig: vi.fn().mockReturnValue('http:'),
24
+ },
25
+ sharedKernel: {
26
+ generate: vi.fn().mockReturnValue({
27
+ directories: ['shared', 'shared/types'],
28
+ files: [{ path: 'shared/index.ts', content: 'export {}' }],
29
+ }),
30
+ },
31
+ messagingPatterns: {
32
+ generate: vi.fn().mockReturnValue({
33
+ files: [{ path: 'messaging/index.js', content: 'module.exports = {}' }],
34
+ }),
35
+ },
36
+ contractTesting: {
37
+ generate: vi.fn().mockReturnValue({
38
+ files: [{ name: 'pact.config.js', content: 'module.exports = {}' }],
39
+ }),
40
+ },
41
+ exampleService: {
42
+ generate: vi.fn().mockReturnValue({
43
+ directories: ['user', 'user/src'],
44
+ files: [{ path: 'user/package.json', content: '{}' }],
45
+ }),
46
+ },
47
+ });
48
+
49
+ describe('constructor', () => {
50
+ it('accepts dependency injection', async () => {
51
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
52
+ const deps = createMockDependencies();
53
+
54
+ const cmd = new NewProjectMicroservice(deps);
55
+
56
+ expect(cmd).toBeDefined();
57
+ });
58
+ });
59
+
60
+ describe('parseArgs', () => {
61
+ it('extracts --architecture flag', async () => {
62
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
63
+ const cmd = new NewProjectMicroservice(createMockDependencies());
64
+
65
+ const result = cmd.parseArgs(['--architecture', 'microservice']);
66
+
67
+ expect(result.architecture).toBe('microservice');
68
+ });
69
+
70
+ it('extracts --services as array', async () => {
71
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
72
+ const cmd = new NewProjectMicroservice(createMockDependencies());
73
+
74
+ const result = cmd.parseArgs(['--services', 'user,order,notification']);
75
+
76
+ expect(result.services).toEqual(['user', 'order', 'notification']);
77
+ });
78
+
79
+ it('extracts --database', async () => {
80
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
81
+ const cmd = new NewProjectMicroservice(createMockDependencies());
82
+
83
+ const result = cmd.parseArgs(['--database', 'postgres']);
84
+
85
+ expect(result.database).toBe('postgres');
86
+ });
87
+
88
+ it('extracts --events as array', async () => {
89
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
90
+ const cmd = new NewProjectMicroservice(createMockDependencies());
91
+
92
+ const result = cmd.parseArgs(['--events', 'UserCreated,OrderPlaced']);
93
+
94
+ expect(result.events).toEqual(['UserCreated', 'OrderPlaced']);
95
+ });
96
+
97
+ it('extracts --gateway', async () => {
98
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
99
+ const cmd = new NewProjectMicroservice(createMockDependencies());
100
+
101
+ const result = cmd.parseArgs(['--gateway', 'nginx']);
102
+
103
+ expect(result.gateway).toBe('nginx');
104
+ });
105
+
106
+ it('extracts --contracts', async () => {
107
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
108
+ const cmd = new NewProjectMicroservice(createMockDependencies());
109
+
110
+ const result = cmd.parseArgs(['--contracts', 'pactflow']);
111
+
112
+ expect(result.contracts).toBe('pactflow');
113
+ });
114
+
115
+ it('handles missing optional args with defaults', async () => {
116
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
117
+ const cmd = new NewProjectMicroservice(createMockDependencies());
118
+
119
+ const result = cmd.parseArgs([]);
120
+
121
+ expect(result.services).toEqual([]);
122
+ expect(result.events).toEqual([]);
123
+ expect(result.database).toBe('postgres');
124
+ expect(result.gateway).toBe('traefik');
125
+ expect(result.contracts).toBe('local');
126
+ });
127
+
128
+ it('default gateway is traefik', async () => {
129
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
130
+ const cmd = new NewProjectMicroservice(createMockDependencies());
131
+
132
+ const result = cmd.parseArgs(['--services', 'api']);
133
+
134
+ expect(result.gateway).toBe('traefik');
135
+ });
136
+
137
+ it('default contracts is local', async () => {
138
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
139
+ const cmd = new NewProjectMicroservice(createMockDependencies());
140
+
141
+ const result = cmd.parseArgs(['--services', 'api']);
142
+
143
+ expect(result.contracts).toBe('local');
144
+ });
145
+
146
+ it('extracts project name', async () => {
147
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
148
+ const cmd = new NewProjectMicroservice(createMockDependencies());
149
+
150
+ const result = cmd.parseArgs(['my-platform', '--architecture', 'microservice']);
151
+
152
+ expect(result.projectName).toBe('my-platform');
153
+ });
154
+ });
155
+
156
+ describe('generate', () => {
157
+ it('calls MicroserviceTemplate', async () => {
158
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
159
+ const deps = createMockDependencies();
160
+ const cmd = new NewProjectMicroservice(deps);
161
+
162
+ const config = {
163
+ projectName: 'my-project',
164
+ services: ['user', 'order'],
165
+ database: 'postgres',
166
+ events: [],
167
+ };
168
+
169
+ cmd.generate(config);
170
+
171
+ expect(deps.microserviceTemplate.generateStructure).toHaveBeenCalledWith(
172
+ expect.objectContaining({
173
+ projectName: 'my-project',
174
+ services: ['user', 'order'],
175
+ })
176
+ );
177
+ });
178
+
179
+ it('calls TraefikConfig', async () => {
180
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
181
+ const deps = createMockDependencies();
182
+ const cmd = new NewProjectMicroservice(deps);
183
+
184
+ const config = {
185
+ projectName: 'my-project',
186
+ services: ['user'],
187
+ database: 'postgres',
188
+ events: [],
189
+ gateway: 'traefik',
190
+ };
191
+
192
+ cmd.generate(config);
193
+
194
+ expect(deps.traefikConfig.generateDynamicConfig).toHaveBeenCalledWith(
195
+ expect.objectContaining({
196
+ services: ['user'],
197
+ })
198
+ );
199
+ });
200
+
201
+ it('calls SharedKernel', async () => {
202
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
203
+ const deps = createMockDependencies();
204
+ const cmd = new NewProjectMicroservice(deps);
205
+
206
+ const config = {
207
+ projectName: 'my-project',
208
+ services: ['user'],
209
+ database: 'postgres',
210
+ events: ['UserCreated'],
211
+ };
212
+
213
+ cmd.generate(config);
214
+
215
+ expect(deps.sharedKernel.generate).toHaveBeenCalledWith(
216
+ expect.objectContaining({
217
+ services: ['user'],
218
+ events: ['UserCreated'],
219
+ })
220
+ );
221
+ });
222
+
223
+ it('calls MessagingPatterns', async () => {
224
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
225
+ const deps = createMockDependencies();
226
+ const cmd = new NewProjectMicroservice(deps);
227
+
228
+ const config = {
229
+ projectName: 'my-project',
230
+ services: ['user'],
231
+ database: 'postgres',
232
+ events: ['UserCreated', 'OrderPlaced'],
233
+ };
234
+
235
+ cmd.generate(config);
236
+
237
+ expect(deps.messagingPatterns.generate).toHaveBeenCalledWith(
238
+ expect.objectContaining({
239
+ events: ['UserCreated', 'OrderPlaced'],
240
+ })
241
+ );
242
+ });
243
+
244
+ it('calls ContractTesting', async () => {
245
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
246
+ const deps = createMockDependencies();
247
+ const cmd = new NewProjectMicroservice(deps);
248
+
249
+ const config = {
250
+ projectName: 'my-project',
251
+ services: ['user', 'order'],
252
+ database: 'postgres',
253
+ events: [],
254
+ contracts: 'local',
255
+ };
256
+
257
+ cmd.generate(config);
258
+
259
+ expect(deps.contractTesting.generate).toHaveBeenCalledWith(
260
+ expect.objectContaining({
261
+ services: ['user', 'order'],
262
+ broker: 'local',
263
+ })
264
+ );
265
+ });
266
+
267
+ it('calls ExampleService for each service', async () => {
268
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
269
+ const deps = createMockDependencies();
270
+ const cmd = new NewProjectMicroservice(deps);
271
+
272
+ const config = {
273
+ projectName: 'my-project',
274
+ services: ['user', 'order', 'notification'],
275
+ database: 'postgres',
276
+ events: [],
277
+ };
278
+
279
+ cmd.generate(config);
280
+
281
+ expect(deps.exampleService.generate).toHaveBeenCalledTimes(3);
282
+ expect(deps.exampleService.generate).toHaveBeenCalledWith(
283
+ expect.objectContaining({ name: 'user' })
284
+ );
285
+ expect(deps.exampleService.generate).toHaveBeenCalledWith(
286
+ expect.objectContaining({ name: 'order' })
287
+ );
288
+ expect(deps.exampleService.generate).toHaveBeenCalledWith(
289
+ expect.objectContaining({ name: 'notification' })
290
+ );
291
+ });
292
+
293
+ it('combines all directories', async () => {
294
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
295
+ const deps = createMockDependencies();
296
+ const cmd = new NewProjectMicroservice(deps);
297
+
298
+ const config = {
299
+ projectName: 'combined',
300
+ services: ['api'],
301
+ database: 'postgres',
302
+ events: [],
303
+ };
304
+
305
+ const result = cmd.generate(config);
306
+
307
+ expect(result.directories).toBeDefined();
308
+ expect(Array.isArray(result.directories)).toBe(true);
309
+ // Should include directories from multiple generators
310
+ expect(result.directories.length).toBeGreaterThan(0);
311
+ });
312
+
313
+ it('combines all files', async () => {
314
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
315
+ const deps = createMockDependencies();
316
+ const cmd = new NewProjectMicroservice(deps);
317
+
318
+ const config = {
319
+ projectName: 'combined',
320
+ services: ['api'],
321
+ database: 'postgres',
322
+ events: [],
323
+ };
324
+
325
+ const result = cmd.generate(config);
326
+
327
+ expect(result.files).toBeDefined();
328
+ expect(Array.isArray(result.files)).toBe(true);
329
+ expect(result.files.length).toBeGreaterThan(0);
330
+ });
331
+
332
+ it('handles single service', async () => {
333
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
334
+ const deps = createMockDependencies();
335
+ const cmd = new NewProjectMicroservice(deps);
336
+
337
+ const config = {
338
+ projectName: 'single',
339
+ services: ['api'],
340
+ database: 'postgres',
341
+ events: [],
342
+ };
343
+
344
+ const result = cmd.generate(config);
345
+
346
+ expect(deps.exampleService.generate).toHaveBeenCalledTimes(1);
347
+ expect(result.directories).toBeDefined();
348
+ expect(result.files).toBeDefined();
349
+ });
350
+
351
+ it('handles no events', async () => {
352
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
353
+ const deps = createMockDependencies();
354
+ const cmd = new NewProjectMicroservice(deps);
355
+
356
+ const config = {
357
+ projectName: 'no-events',
358
+ services: ['user'],
359
+ database: 'postgres',
360
+ events: [],
361
+ };
362
+
363
+ const result = cmd.generate(config);
364
+
365
+ expect(deps.messagingPatterns.generate).toHaveBeenCalledWith(
366
+ expect.objectContaining({ events: [] })
367
+ );
368
+ expect(result).toBeDefined();
369
+ });
370
+
371
+ it('handles no database', async () => {
372
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
373
+ const deps = createMockDependencies();
374
+ const cmd = new NewProjectMicroservice(deps);
375
+
376
+ const config = {
377
+ projectName: 'no-db',
378
+ services: ['user'],
379
+ database: 'none',
380
+ events: [],
381
+ };
382
+
383
+ const result = cmd.generate(config);
384
+
385
+ expect(deps.exampleService.generate).toHaveBeenCalledWith(
386
+ expect.objectContaining({ database: 'none' })
387
+ );
388
+ expect(result).toBeDefined();
389
+ });
390
+ });
391
+
392
+ describe('interactivePrompts', () => {
393
+ it('returns config object', async () => {
394
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
395
+ const deps = createMockDependencies();
396
+ const cmd = new NewProjectMicroservice(deps);
397
+
398
+ const prompts = cmd.interactivePrompts();
399
+
400
+ expect(prompts).toBeDefined();
401
+ expect(Array.isArray(prompts)).toBe(true);
402
+ });
403
+
404
+ it('includes project name prompt', async () => {
405
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
406
+ const deps = createMockDependencies();
407
+ const cmd = new NewProjectMicroservice(deps);
408
+
409
+ const prompts = cmd.interactivePrompts();
410
+
411
+ const projectPrompt = prompts.find(p => p.name === 'projectName');
412
+ expect(projectPrompt).toBeDefined();
413
+ });
414
+
415
+ it('includes services prompt', async () => {
416
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
417
+ const deps = createMockDependencies();
418
+ const cmd = new NewProjectMicroservice(deps);
419
+
420
+ const prompts = cmd.interactivePrompts();
421
+
422
+ const servicesPrompt = prompts.find(p => p.name === 'services');
423
+ expect(servicesPrompt).toBeDefined();
424
+ });
425
+
426
+ it('includes database prompt', async () => {
427
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
428
+ const deps = createMockDependencies();
429
+ const cmd = new NewProjectMicroservice(deps);
430
+
431
+ const prompts = cmd.interactivePrompts();
432
+
433
+ const dbPrompt = prompts.find(p => p.name === 'database');
434
+ expect(dbPrompt).toBeDefined();
435
+ });
436
+
437
+ it('includes events prompt', async () => {
438
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
439
+ const deps = createMockDependencies();
440
+ const cmd = new NewProjectMicroservice(deps);
441
+
442
+ const prompts = cmd.interactivePrompts();
443
+
444
+ const eventsPrompt = prompts.find(p => p.name === 'events');
445
+ expect(eventsPrompt).toBeDefined();
446
+ });
447
+
448
+ it('includes gateway prompt', async () => {
449
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
450
+ const deps = createMockDependencies();
451
+ const cmd = new NewProjectMicroservice(deps);
452
+
453
+ const prompts = cmd.interactivePrompts();
454
+
455
+ const gatewayPrompt = prompts.find(p => p.name === 'gateway');
456
+ expect(gatewayPrompt).toBeDefined();
457
+ });
458
+ });
459
+
460
+ describe('generateNextSteps', () => {
461
+ it('includes cd command', async () => {
462
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
463
+ const deps = createMockDependencies();
464
+ const cmd = new NewProjectMicroservice(deps);
465
+
466
+ const config = { projectName: 'my-app', services: ['api'] };
467
+ const result = cmd.generateNextSteps(config);
468
+
469
+ expect(result).toContain('cd my-app');
470
+ });
471
+
472
+ it('includes npm install', async () => {
473
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
474
+ const deps = createMockDependencies();
475
+ const cmd = new NewProjectMicroservice(deps);
476
+
477
+ const config = { projectName: 'my-app', services: ['api'] };
478
+ const result = cmd.generateNextSteps(config);
479
+
480
+ expect(result).toContain('npm install');
481
+ });
482
+
483
+ it('includes docker-compose up', async () => {
484
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
485
+ const deps = createMockDependencies();
486
+ const cmd = new NewProjectMicroservice(deps);
487
+
488
+ const config = { projectName: 'my-app', services: ['api'] };
489
+ const result = cmd.generateNextSteps(config);
490
+
491
+ expect(result).toContain('docker-compose up');
492
+ });
493
+
494
+ it('includes service URLs', async () => {
495
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
496
+ const deps = createMockDependencies();
497
+ const cmd = new NewProjectMicroservice(deps);
498
+
499
+ const config = { projectName: 'my-app', services: ['user', 'order'] };
500
+ const result = cmd.generateNextSteps(config);
501
+
502
+ expect(result).toContain('http://localhost');
503
+ expect(result).toMatch(/user/i);
504
+ expect(result).toMatch(/order/i);
505
+ });
506
+ });
507
+
508
+ describe('createProjectFiles', () => {
509
+ it('returns success on valid input', async () => {
510
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
511
+ const deps = createMockDependencies();
512
+ const cmd = new NewProjectMicroservice(deps);
513
+
514
+ const result = {
515
+ directories: ['test-project'],
516
+ files: [{ path: 'test-project/package.json', content: '{}' }],
517
+ };
518
+
519
+ // Mock fs operations
520
+ const mockFs = {
521
+ mkdirSync: vi.fn(),
522
+ writeFileSync: vi.fn(),
523
+ };
524
+ cmd.setFs(mockFs);
525
+
526
+ const status = cmd.createProjectFiles('/tmp/output', result);
527
+
528
+ expect(status.success).toBe(true);
529
+ });
530
+
531
+ it('creates directories', async () => {
532
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
533
+ const deps = createMockDependencies();
534
+ const cmd = new NewProjectMicroservice(deps);
535
+
536
+ const result = {
537
+ directories: ['test-project', 'test-project/src'],
538
+ files: [],
539
+ };
540
+
541
+ const mockFs = {
542
+ mkdirSync: vi.fn(),
543
+ writeFileSync: vi.fn(),
544
+ };
545
+ cmd.setFs(mockFs);
546
+
547
+ cmd.createProjectFiles('/tmp/output', result);
548
+
549
+ expect(mockFs.mkdirSync).toHaveBeenCalledTimes(2);
550
+ });
551
+
552
+ it('writes all files', async () => {
553
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
554
+ const deps = createMockDependencies();
555
+ const cmd = new NewProjectMicroservice(deps);
556
+
557
+ const result = {
558
+ directories: [],
559
+ files: [
560
+ { path: 'test-project/package.json', content: '{}' },
561
+ { path: 'test-project/README.md', content: '# Test' },
562
+ ],
563
+ };
564
+
565
+ const mockFs = {
566
+ mkdirSync: vi.fn(),
567
+ writeFileSync: vi.fn(),
568
+ };
569
+ cmd.setFs(mockFs);
570
+
571
+ cmd.createProjectFiles('/tmp/output', result);
572
+
573
+ expect(mockFs.writeFileSync).toHaveBeenCalledTimes(2);
574
+ });
575
+
576
+ it('returns failure on error', async () => {
577
+ const { NewProjectMicroservice } = await import('./new-project-microservice.js');
578
+ const deps = createMockDependencies();
579
+ const cmd = new NewProjectMicroservice(deps);
580
+
581
+ const result = {
582
+ directories: ['test-project'],
583
+ files: [],
584
+ };
585
+
586
+ const mockFs = {
587
+ mkdirSync: vi.fn().mockImplementation(() => {
588
+ throw new Error('Permission denied');
589
+ }),
590
+ writeFileSync: vi.fn(),
591
+ };
592
+ cmd.setFs(mockFs);
593
+
594
+ const status = cmd.createProjectFiles('/tmp/output', result);
595
+
596
+ expect(status.success).toBe(false);
597
+ expect(status.error).toBeDefined();
598
+ });
599
+ });
600
+ });