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.
- package/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
- package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- 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
|
+
});
|