tlc-claude-code 1.3.0 → 1.4.1

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 (105) hide show
  1. package/dashboard/dist/components/AuditPane.d.ts +30 -0
  2. package/dashboard/dist/components/AuditPane.js +127 -0
  3. package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
  4. package/dashboard/dist/components/AuditPane.test.js +339 -0
  5. package/dashboard/dist/components/CompliancePane.d.ts +39 -0
  6. package/dashboard/dist/components/CompliancePane.js +96 -0
  7. package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
  8. package/dashboard/dist/components/CompliancePane.test.js +183 -0
  9. package/dashboard/dist/components/SSOPane.d.ts +36 -0
  10. package/dashboard/dist/components/SSOPane.js +71 -0
  11. package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
  12. package/dashboard/dist/components/SSOPane.test.js +155 -0
  13. package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
  14. package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
  15. package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
  16. package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
  17. package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
  18. package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
  19. package/package.json +1 -1
  20. package/server/lib/access-control-doc.js +541 -0
  21. package/server/lib/access-control-doc.test.js +672 -0
  22. package/server/lib/adr-generator.js +423 -0
  23. package/server/lib/adr-generator.test.js +586 -0
  24. package/server/lib/agent-progress-monitor.js +223 -0
  25. package/server/lib/agent-progress-monitor.test.js +202 -0
  26. package/server/lib/audit-attribution.js +191 -0
  27. package/server/lib/audit-attribution.test.js +359 -0
  28. package/server/lib/audit-classifier.js +202 -0
  29. package/server/lib/audit-classifier.test.js +209 -0
  30. package/server/lib/audit-command.js +275 -0
  31. package/server/lib/audit-command.test.js +325 -0
  32. package/server/lib/audit-exporter.js +380 -0
  33. package/server/lib/audit-exporter.test.js +464 -0
  34. package/server/lib/audit-logger.js +236 -0
  35. package/server/lib/audit-logger.test.js +364 -0
  36. package/server/lib/audit-query.js +257 -0
  37. package/server/lib/audit-query.test.js +352 -0
  38. package/server/lib/audit-storage.js +269 -0
  39. package/server/lib/audit-storage.test.js +272 -0
  40. package/server/lib/bulk-repo-init.js +342 -0
  41. package/server/lib/bulk-repo-init.test.js +388 -0
  42. package/server/lib/compliance-checklist.js +866 -0
  43. package/server/lib/compliance-checklist.test.js +476 -0
  44. package/server/lib/compliance-command.js +616 -0
  45. package/server/lib/compliance-command.test.js +551 -0
  46. package/server/lib/compliance-reporter.js +692 -0
  47. package/server/lib/compliance-reporter.test.js +707 -0
  48. package/server/lib/data-flow-doc.js +665 -0
  49. package/server/lib/data-flow-doc.test.js +659 -0
  50. package/server/lib/ephemeral-storage.js +249 -0
  51. package/server/lib/ephemeral-storage.test.js +254 -0
  52. package/server/lib/evidence-collector.js +627 -0
  53. package/server/lib/evidence-collector.test.js +901 -0
  54. package/server/lib/flow-diagram-generator.js +474 -0
  55. package/server/lib/flow-diagram-generator.test.js +446 -0
  56. package/server/lib/idp-manager.js +626 -0
  57. package/server/lib/idp-manager.test.js +587 -0
  58. package/server/lib/memory-exclusion.js +326 -0
  59. package/server/lib/memory-exclusion.test.js +241 -0
  60. package/server/lib/mfa-handler.js +452 -0
  61. package/server/lib/mfa-handler.test.js +490 -0
  62. package/server/lib/oauth-flow.js +375 -0
  63. package/server/lib/oauth-flow.test.js +487 -0
  64. package/server/lib/oauth-registry.js +190 -0
  65. package/server/lib/oauth-registry.test.js +306 -0
  66. package/server/lib/readme-generator.js +490 -0
  67. package/server/lib/readme-generator.test.js +493 -0
  68. package/server/lib/repo-dependency-tracker.js +261 -0
  69. package/server/lib/repo-dependency-tracker.test.js +350 -0
  70. package/server/lib/retention-policy.js +281 -0
  71. package/server/lib/retention-policy.test.js +486 -0
  72. package/server/lib/role-mapper.js +236 -0
  73. package/server/lib/role-mapper.test.js +395 -0
  74. package/server/lib/saml-provider.js +765 -0
  75. package/server/lib/saml-provider.test.js +643 -0
  76. package/server/lib/security-policy-generator.js +682 -0
  77. package/server/lib/security-policy-generator.test.js +544 -0
  78. package/server/lib/sensitive-detector.js +112 -0
  79. package/server/lib/sensitive-detector.test.js +209 -0
  80. package/server/lib/service-interaction-diagram.js +700 -0
  81. package/server/lib/service-interaction-diagram.test.js +638 -0
  82. package/server/lib/service-summary.js +553 -0
  83. package/server/lib/service-summary.test.js +619 -0
  84. package/server/lib/session-purge.js +460 -0
  85. package/server/lib/session-purge.test.js +312 -0
  86. package/server/lib/sso-command.js +544 -0
  87. package/server/lib/sso-command.test.js +552 -0
  88. package/server/lib/sso-session.js +492 -0
  89. package/server/lib/sso-session.test.js +670 -0
  90. package/server/lib/workspace-command.js +249 -0
  91. package/server/lib/workspace-command.test.js +264 -0
  92. package/server/lib/workspace-config.js +270 -0
  93. package/server/lib/workspace-config.test.js +312 -0
  94. package/server/lib/workspace-docs-command.js +547 -0
  95. package/server/lib/workspace-docs-command.test.js +692 -0
  96. package/server/lib/workspace-memory.js +451 -0
  97. package/server/lib/workspace-memory.test.js +403 -0
  98. package/server/lib/workspace-scanner.js +452 -0
  99. package/server/lib/workspace-scanner.test.js +677 -0
  100. package/server/lib/workspace-test-runner.js +315 -0
  101. package/server/lib/workspace-test-runner.test.js +294 -0
  102. package/server/lib/zero-retention-command.js +439 -0
  103. package/server/lib/zero-retention-command.test.js +448 -0
  104. package/server/lib/zero-retention.js +322 -0
  105. package/server/lib/zero-retention.test.js +258 -0
@@ -0,0 +1,638 @@
1
+ /**
2
+ * Service Interaction Diagram Generator Tests
3
+ * Generate detailed service interaction diagrams (sequence, component, deployment, ER)
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+
8
+ describe('ServiceInteractionDiagram', () => {
9
+ describe('sequence diagram generation', () => {
10
+ it('generates sequence diagram for API flow', async () => {
11
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
12
+ const generator = new ServiceInteractionDiagram();
13
+
14
+ const apiFlow = {
15
+ name: 'Create Order',
16
+ steps: [
17
+ { from: 'Client', to: 'API Gateway', action: 'POST /orders', type: 'request' },
18
+ { from: 'API Gateway', to: 'Order Service', action: 'createOrder()', type: 'call' },
19
+ { from: 'Order Service', to: 'Database', action: 'INSERT order', type: 'query' },
20
+ { from: 'Database', to: 'Order Service', action: 'order record', type: 'response' },
21
+ { from: 'Order Service', to: 'Event Bus', action: 'publish order.created', type: 'event' },
22
+ { from: 'Order Service', to: 'API Gateway', action: 'Order object', type: 'response' },
23
+ { from: 'API Gateway', to: 'Client', action: '201 Created', type: 'response' },
24
+ ],
25
+ };
26
+
27
+ const mermaid = generator.generateSequenceDiagram(apiFlow);
28
+
29
+ expect(mermaid).toContain('sequenceDiagram');
30
+ expect(mermaid).toContain('Client');
31
+ expect(mermaid).toContain('API Gateway');
32
+ expect(mermaid).toContain('Order Service');
33
+ expect(mermaid).toContain('Database');
34
+ expect(mermaid).toContain('POST /orders');
35
+ expect(mermaid).toContain('->>'); // Request arrow
36
+ });
37
+
38
+ it('uses different arrow styles for request vs response', async () => {
39
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
40
+ const generator = new ServiceInteractionDiagram();
41
+
42
+ const apiFlow = {
43
+ name: 'Simple Request',
44
+ steps: [
45
+ { from: 'A', to: 'B', action: 'request', type: 'request' },
46
+ { from: 'B', to: 'A', action: 'response', type: 'response' },
47
+ ],
48
+ };
49
+
50
+ const mermaid = generator.generateSequenceDiagram(apiFlow);
51
+
52
+ expect(mermaid).toContain('A->>B'); // Solid arrow for request
53
+ expect(mermaid).toContain('B-->>A'); // Dashed arrow for response
54
+ });
55
+
56
+ it('supports async messages', async () => {
57
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
58
+ const generator = new ServiceInteractionDiagram();
59
+
60
+ const apiFlow = {
61
+ name: 'Async Flow',
62
+ steps: [
63
+ { from: 'Producer', to: 'Queue', action: 'publish event', type: 'async' },
64
+ { from: 'Queue', to: 'Consumer', action: 'deliver event', type: 'async' },
65
+ ],
66
+ };
67
+
68
+ const mermaid = generator.generateSequenceDiagram(apiFlow);
69
+
70
+ expect(mermaid).toContain('-)'); // Async arrow style
71
+ });
72
+
73
+ it('adds notes for complex steps', async () => {
74
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
75
+ const generator = new ServiceInteractionDiagram();
76
+
77
+ const apiFlow = {
78
+ name: 'Flow with Notes',
79
+ steps: [
80
+ { from: 'Service', to: 'Database', action: 'query', type: 'query', note: 'Uses connection pool' },
81
+ ],
82
+ };
83
+
84
+ const mermaid = generator.generateSequenceDiagram(apiFlow);
85
+
86
+ expect(mermaid).toContain('Note');
87
+ expect(mermaid).toContain('Uses connection pool');
88
+ });
89
+
90
+ it('handles empty flow gracefully', async () => {
91
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
92
+ const generator = new ServiceInteractionDiagram();
93
+
94
+ const mermaid = generator.generateSequenceDiagram({ name: 'Empty', steps: [] });
95
+
96
+ expect(mermaid).toContain('sequenceDiagram');
97
+ expect(mermaid).toContain('No interactions');
98
+ });
99
+ });
100
+
101
+ describe('component diagram generation', () => {
102
+ it('generates component diagram with services', async () => {
103
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
104
+ const generator = new ServiceInteractionDiagram();
105
+
106
+ const services = {
107
+ components: [
108
+ { name: 'API Gateway', type: 'gateway', ports: ['HTTP 80', 'HTTPS 443'] },
109
+ { name: 'User Service', type: 'service', ports: ['gRPC 50051'] },
110
+ { name: 'Order Service', type: 'service', ports: ['HTTP 3001'] },
111
+ { name: 'PostgreSQL', type: 'database', ports: ['5432'] },
112
+ { name: 'Redis', type: 'cache', ports: ['6379'] },
113
+ ],
114
+ connections: [
115
+ { from: 'API Gateway', to: 'User Service', protocol: 'gRPC' },
116
+ { from: 'API Gateway', to: 'Order Service', protocol: 'HTTP' },
117
+ { from: 'User Service', to: 'PostgreSQL', protocol: 'TCP' },
118
+ { from: 'Order Service', to: 'PostgreSQL', protocol: 'TCP' },
119
+ { from: 'Order Service', to: 'Redis', protocol: 'TCP' },
120
+ ],
121
+ };
122
+
123
+ const mermaid = generator.generateComponentDiagram(services);
124
+
125
+ expect(mermaid).toContain('flowchart');
126
+ expect(mermaid).toContain('API Gateway');
127
+ expect(mermaid).toContain('User Service');
128
+ expect(mermaid).toContain('Order Service');
129
+ expect(mermaid).toContain('PostgreSQL');
130
+ expect(mermaid).toContain('Redis');
131
+ expect(mermaid).toContain('gRPC');
132
+ expect(mermaid).toContain('HTTP');
133
+ });
134
+
135
+ it('uses different shapes for component types', async () => {
136
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
137
+ const generator = new ServiceInteractionDiagram();
138
+
139
+ const services = {
140
+ components: [
141
+ { name: 'Gateway', type: 'gateway' },
142
+ { name: 'Service', type: 'service' },
143
+ { name: 'Database', type: 'database' },
144
+ { name: 'Cache', type: 'cache' },
145
+ { name: 'Queue', type: 'queue' },
146
+ ],
147
+ connections: [],
148
+ };
149
+
150
+ const mermaid = generator.generateComponentDiagram(services);
151
+
152
+ // Different node shapes for different types
153
+ expect(mermaid).toContain('{{'); // Gateway uses hexagon
154
+ expect(mermaid).toContain('[('); // Database uses cylinder
155
+ expect(mermaid).toContain('(['); // Cache uses stadium
156
+ });
157
+
158
+ it('groups components by layer', async () => {
159
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
160
+ const generator = new ServiceInteractionDiagram({ groupByLayer: true });
161
+
162
+ const services = {
163
+ components: [
164
+ { name: 'API Gateway', type: 'gateway', layer: 'edge' },
165
+ { name: 'User Service', type: 'service', layer: 'application' },
166
+ { name: 'PostgreSQL', type: 'database', layer: 'data' },
167
+ ],
168
+ connections: [],
169
+ };
170
+
171
+ const mermaid = generator.generateComponentDiagram(services);
172
+
173
+ expect(mermaid).toContain('subgraph edge');
174
+ expect(mermaid).toContain('subgraph application');
175
+ expect(mermaid).toContain('subgraph data');
176
+ });
177
+
178
+ it('handles components with no connections', async () => {
179
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
180
+ const generator = new ServiceInteractionDiagram();
181
+
182
+ const services = {
183
+ components: [
184
+ { name: 'Standalone', type: 'service' },
185
+ ],
186
+ connections: [],
187
+ };
188
+
189
+ const mermaid = generator.generateComponentDiagram(services);
190
+
191
+ expect(mermaid).toContain('Standalone');
192
+ expect(mermaid).not.toContain('-->');
193
+ });
194
+ });
195
+
196
+ describe('deployment diagram generation', () => {
197
+ it('generates deployment diagram from docker-compose', async () => {
198
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
199
+ const generator = new ServiceInteractionDiagram();
200
+
201
+ const dockerCompose = `
202
+ version: '3.8'
203
+ services:
204
+ api:
205
+ image: myapp/api:latest
206
+ ports:
207
+ - "3000:3000"
208
+ depends_on:
209
+ - db
210
+ - redis
211
+ environment:
212
+ - DATABASE_URL=postgres://db:5432/app
213
+ db:
214
+ image: postgres:15
215
+ ports:
216
+ - "5432:5432"
217
+ volumes:
218
+ - pgdata:/var/lib/postgresql/data
219
+ redis:
220
+ image: redis:7
221
+ ports:
222
+ - "6379:6379"
223
+ volumes:
224
+ pgdata:
225
+ `;
226
+
227
+ const mermaid = generator.generateDeploymentDiagram(dockerCompose);
228
+
229
+ expect(mermaid).toContain('flowchart');
230
+ expect(mermaid).toContain('api');
231
+ expect(mermaid).toContain('db');
232
+ expect(mermaid).toContain('redis');
233
+ expect(mermaid).toContain('3000'); // Port mapping
234
+ expect(mermaid).toContain('depends_on'); // Shows dependency relationship
235
+ });
236
+
237
+ it('shows port mappings in deployment diagram', async () => {
238
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
239
+ const generator = new ServiceInteractionDiagram();
240
+
241
+ const dockerCompose = `
242
+ services:
243
+ web:
244
+ image: nginx
245
+ ports:
246
+ - "80:80"
247
+ - "443:443"
248
+ `;
249
+
250
+ const mermaid = generator.generateDeploymentDiagram(dockerCompose);
251
+
252
+ expect(mermaid).toContain('80');
253
+ expect(mermaid).toContain('443');
254
+ });
255
+
256
+ it('shows volume mounts', async () => {
257
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
258
+ const generator = new ServiceInteractionDiagram();
259
+
260
+ const dockerCompose = `
261
+ services:
262
+ db:
263
+ image: postgres
264
+ volumes:
265
+ - pgdata:/var/lib/postgresql/data
266
+ - ./init.sql:/docker-entrypoint-initdb.d/init.sql
267
+ volumes:
268
+ pgdata:
269
+ `;
270
+
271
+ const mermaid = generator.generateDeploymentDiagram(dockerCompose);
272
+
273
+ expect(mermaid).toContain('pgdata');
274
+ });
275
+
276
+ it('shows network configuration', async () => {
277
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
278
+ const generator = new ServiceInteractionDiagram();
279
+
280
+ const dockerCompose = `
281
+ services:
282
+ api:
283
+ networks:
284
+ - frontend
285
+ - backend
286
+ db:
287
+ networks:
288
+ - backend
289
+ networks:
290
+ frontend:
291
+ backend:
292
+ `;
293
+
294
+ const mermaid = generator.generateDeploymentDiagram(dockerCompose);
295
+
296
+ expect(mermaid).toContain('frontend');
297
+ expect(mermaid).toContain('backend');
298
+ });
299
+
300
+ it('handles missing infrastructure config', async () => {
301
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
302
+ const generator = new ServiceInteractionDiagram();
303
+
304
+ // Empty or invalid YAML
305
+ const mermaid = generator.generateDeploymentDiagram('');
306
+
307
+ expect(mermaid).toContain('flowchart');
308
+ expect(mermaid).toContain('No infrastructure');
309
+ });
310
+
311
+ it('handles invalid docker-compose YAML', async () => {
312
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
313
+ const generator = new ServiceInteractionDiagram();
314
+
315
+ const invalidYaml = `
316
+ services:
317
+ api:
318
+ invalid: yaml: content
319
+ - not valid
320
+ `;
321
+
322
+ // Should not throw
323
+ const mermaid = generator.generateDeploymentDiagram(invalidYaml);
324
+
325
+ expect(mermaid).toContain('flowchart');
326
+ });
327
+ });
328
+
329
+ describe('ER diagram generation', () => {
330
+ it('generates ER diagram from schema files', async () => {
331
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
332
+ const generator = new ServiceInteractionDiagram();
333
+
334
+ const schemas = [
335
+ {
336
+ name: 'User',
337
+ tableName: 'users',
338
+ columns: [
339
+ { name: 'id', type: 'integer', primary: true },
340
+ { name: 'email', type: 'string', unique: true },
341
+ { name: 'name', type: 'string', nullable: true },
342
+ { name: 'created_at', type: 'timestamp' },
343
+ ],
344
+ },
345
+ {
346
+ name: 'Post',
347
+ tableName: 'posts',
348
+ columns: [
349
+ { name: 'id', type: 'integer', primary: true },
350
+ { name: 'title', type: 'string' },
351
+ { name: 'content', type: 'text' },
352
+ { name: 'user_id', type: 'integer', foreignKey: { table: 'users', column: 'id' } },
353
+ ],
354
+ },
355
+ {
356
+ name: 'Comment',
357
+ tableName: 'comments',
358
+ columns: [
359
+ { name: 'id', type: 'integer', primary: true },
360
+ { name: 'body', type: 'text' },
361
+ { name: 'post_id', type: 'integer', foreignKey: { table: 'posts', column: 'id' } },
362
+ { name: 'user_id', type: 'integer', foreignKey: { table: 'users', column: 'id' } },
363
+ ],
364
+ },
365
+ ];
366
+
367
+ const mermaid = generator.generateERDiagram(schemas);
368
+
369
+ expect(mermaid).toContain('erDiagram');
370
+ expect(mermaid).toContain('users');
371
+ expect(mermaid).toContain('posts');
372
+ expect(mermaid).toContain('comments');
373
+ expect(mermaid).toContain('id');
374
+ expect(mermaid).toContain('||'); // Relationship markers
375
+ });
376
+
377
+ it('shows primary keys in ER diagram', async () => {
378
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
379
+ const generator = new ServiceInteractionDiagram();
380
+
381
+ const schemas = [
382
+ {
383
+ name: 'User',
384
+ tableName: 'users',
385
+ columns: [
386
+ { name: 'id', type: 'integer', primary: true },
387
+ { name: 'email', type: 'string' },
388
+ ],
389
+ },
390
+ ];
391
+
392
+ const mermaid = generator.generateERDiagram(schemas);
393
+
394
+ expect(mermaid).toContain('PK'); // Primary key marker
395
+ });
396
+
397
+ it('shows foreign key relationships', async () => {
398
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
399
+ const generator = new ServiceInteractionDiagram();
400
+
401
+ const schemas = [
402
+ {
403
+ name: 'User',
404
+ tableName: 'users',
405
+ columns: [{ name: 'id', type: 'integer', primary: true }],
406
+ },
407
+ {
408
+ name: 'Post',
409
+ tableName: 'posts',
410
+ columns: [
411
+ { name: 'id', type: 'integer', primary: true },
412
+ { name: 'user_id', type: 'integer', foreignKey: { table: 'users', column: 'id' } },
413
+ ],
414
+ },
415
+ ];
416
+
417
+ const mermaid = generator.generateERDiagram(schemas);
418
+
419
+ expect(mermaid).toContain('FK'); // Foreign key marker
420
+ expect(mermaid).toContain('users ||--o{ posts'); // One-to-many relationship
421
+ });
422
+
423
+ it('shows column types in ER diagram', async () => {
424
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
425
+ const generator = new ServiceInteractionDiagram();
426
+
427
+ const schemas = [
428
+ {
429
+ name: 'User',
430
+ tableName: 'users',
431
+ columns: [
432
+ { name: 'id', type: 'integer', primary: true },
433
+ { name: 'email', type: 'string' },
434
+ { name: 'is_active', type: 'boolean' },
435
+ ],
436
+ },
437
+ ];
438
+
439
+ const mermaid = generator.generateERDiagram(schemas);
440
+
441
+ expect(mermaid).toContain('integer');
442
+ expect(mermaid).toContain('string');
443
+ expect(mermaid).toContain('boolean');
444
+ });
445
+
446
+ it('handles empty schema list', async () => {
447
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
448
+ const generator = new ServiceInteractionDiagram();
449
+
450
+ const mermaid = generator.generateERDiagram([]);
451
+
452
+ expect(mermaid).toContain('erDiagram');
453
+ expect(mermaid).toContain('No entities');
454
+ });
455
+
456
+ it('infers relationships from column naming conventions', async () => {
457
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
458
+ const generator = new ServiceInteractionDiagram();
459
+
460
+ const schemas = [
461
+ {
462
+ name: 'User',
463
+ tableName: 'users',
464
+ columns: [{ name: 'id', type: 'integer', primary: true }],
465
+ },
466
+ {
467
+ name: 'Order',
468
+ tableName: 'orders',
469
+ columns: [
470
+ { name: 'id', type: 'integer', primary: true },
471
+ { name: 'user_id', type: 'integer' }, // No explicit FK, but naming implies relationship
472
+ ],
473
+ },
474
+ ];
475
+
476
+ const mermaid = generator.generateERDiagram(schemas);
477
+
478
+ // Should infer relationship from user_id column name
479
+ expect(mermaid).toContain('users');
480
+ expect(mermaid).toContain('orders');
481
+ });
482
+ });
483
+
484
+ describe('API flow extraction', () => {
485
+ it('extracts sequence from route handler code', async () => {
486
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
487
+ const generator = new ServiceInteractionDiagram();
488
+
489
+ const routeCode = `
490
+ async function createOrder(req, res) {
491
+ const user = await userService.getUser(req.userId);
492
+ const order = await orderRepository.create(req.body);
493
+ await eventBus.publish('order.created', order);
494
+ return res.status(201).json(order);
495
+ }
496
+ `;
497
+
498
+ const flow = generator.extractAPIFlow('createOrder', routeCode);
499
+
500
+ expect(flow.steps.length).toBeGreaterThan(0);
501
+ expect(flow.steps).toContainEqual(
502
+ expect.objectContaining({
503
+ action: expect.stringContaining('userService'),
504
+ })
505
+ );
506
+ expect(flow.steps).toContainEqual(
507
+ expect.objectContaining({
508
+ action: expect.stringContaining('orderRepository'),
509
+ })
510
+ );
511
+ });
512
+
513
+ it('detects await calls as service interactions', async () => {
514
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
515
+ const generator = new ServiceInteractionDiagram();
516
+
517
+ const code = `
518
+ async function handler() {
519
+ const result = await externalAPI.fetchData();
520
+ await cache.set('key', result);
521
+ }
522
+ `;
523
+
524
+ const flow = generator.extractAPIFlow('handler', code);
525
+
526
+ expect(flow.steps.some(s => s.action.includes('externalAPI'))).toBe(true);
527
+ expect(flow.steps.some(s => s.action.includes('cache'))).toBe(true);
528
+ });
529
+ });
530
+
531
+ describe('diagram validation', () => {
532
+ it('all diagrams have valid Mermaid syntax', async () => {
533
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
534
+ const generator = new ServiceInteractionDiagram();
535
+
536
+ // Sequence diagram
537
+ const seq = generator.generateSequenceDiagram({
538
+ name: 'Test',
539
+ steps: [{ from: 'A', to: 'B', action: 'test', type: 'request' }],
540
+ });
541
+ expect(seq).toMatch(/^sequenceDiagram/);
542
+ expect(seq).not.toContain('undefined');
543
+ expect(seq).not.toContain('null');
544
+
545
+ // Component diagram
546
+ const comp = generator.generateComponentDiagram({
547
+ components: [{ name: 'Test', type: 'service' }],
548
+ connections: [],
549
+ });
550
+ expect(comp).toMatch(/^flowchart/);
551
+ expect(comp).not.toContain('undefined');
552
+
553
+ // ER diagram
554
+ const er = generator.generateERDiagram([
555
+ { name: 'Test', tableName: 'test', columns: [{ name: 'id', type: 'int', primary: true }] },
556
+ ]);
557
+ expect(er).toMatch(/^erDiagram/);
558
+ expect(er).not.toContain('undefined');
559
+ });
560
+
561
+ it('escapes special characters in labels', async () => {
562
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
563
+ const generator = new ServiceInteractionDiagram();
564
+
565
+ const flow = {
566
+ name: 'Test with "quotes"',
567
+ steps: [
568
+ { from: 'Service<A>', to: 'Service[B]', action: 'call with {data}', type: 'request' },
569
+ ],
570
+ };
571
+
572
+ const mermaid = generator.generateSequenceDiagram(flow);
573
+
574
+ // Should not have unescaped special chars
575
+ expect(mermaid).not.toMatch(/<A>/);
576
+ expect(mermaid).not.toMatch(/\[B\]/);
577
+ expect(mermaid).not.toContain('{data}');
578
+ });
579
+
580
+ it('sanitizes node IDs for Mermaid compatibility', async () => {
581
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
582
+ const generator = new ServiceInteractionDiagram();
583
+
584
+ const services = {
585
+ components: [
586
+ { name: 'my-service.v2', type: 'service' },
587
+ { name: '@scope/package', type: 'service' },
588
+ ],
589
+ connections: [],
590
+ };
591
+
592
+ const mermaid = generator.generateComponentDiagram(services);
593
+
594
+ // Should not contain invalid ID characters
595
+ expect(mermaid).not.toContain('@scope/package');
596
+ expect(mermaid).not.toContain('my-service.v2');
597
+ expect(mermaid).toContain('my_service_v2');
598
+ expect(mermaid).toContain('scope_package');
599
+ });
600
+ });
601
+
602
+ describe('combined workspace diagram', () => {
603
+ it('generates combined diagram for entire workspace', async () => {
604
+ const { ServiceInteractionDiagram } = await import('./service-interaction-diagram.js');
605
+ const generator = new ServiceInteractionDiagram();
606
+
607
+ const workspace = {
608
+ services: [
609
+ { name: 'api-gateway', type: 'gateway' },
610
+ { name: 'user-service', type: 'service' },
611
+ { name: 'order-service', type: 'service' },
612
+ ],
613
+ databases: [
614
+ { name: 'users-db', type: 'postgres' },
615
+ { name: 'orders-db', type: 'postgres' },
616
+ ],
617
+ queues: [
618
+ { name: 'event-bus', type: 'rabbitmq' },
619
+ ],
620
+ connections: [
621
+ { from: 'api-gateway', to: 'user-service', type: 'http' },
622
+ { from: 'api-gateway', to: 'order-service', type: 'http' },
623
+ { from: 'user-service', to: 'users-db', type: 'database' },
624
+ { from: 'order-service', to: 'orders-db', type: 'database' },
625
+ { from: 'order-service', to: 'event-bus', type: 'publish' },
626
+ ],
627
+ };
628
+
629
+ const mermaid = generator.generateWorkspaceDiagram(workspace);
630
+
631
+ expect(mermaid).toContain('flowchart');
632
+ expect(mermaid).toContain('api_gateway');
633
+ expect(mermaid).toContain('user_service');
634
+ expect(mermaid).toContain('users_db');
635
+ expect(mermaid).toContain('event_bus');
636
+ });
637
+ });
638
+ });