tlc-claude-code 1.2.29 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- 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/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -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/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -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/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -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/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -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/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -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/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -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/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -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-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -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/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -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/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -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
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture Command Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
|
|
7
|
+
describe('ArchitectureCommand', () => {
|
|
8
|
+
// Mock graph data for tests
|
|
9
|
+
const mockGraph = {
|
|
10
|
+
nodes: [
|
|
11
|
+
{ id: '/project/src/auth/login.js', name: 'src/auth/login.js', imports: 2, importedBy: 1 },
|
|
12
|
+
{ id: '/project/src/auth/logout.js', name: 'src/auth/logout.js', imports: 1, importedBy: 0 },
|
|
13
|
+
{ id: '/project/src/users/profile.js', name: 'src/users/profile.js', imports: 1, importedBy: 2 },
|
|
14
|
+
{ id: '/project/src/utils/helpers.js', name: 'src/utils/helpers.js', imports: 0, importedBy: 5 },
|
|
15
|
+
],
|
|
16
|
+
edges: [
|
|
17
|
+
{ from: '/project/src/auth/login.js', to: '/project/src/utils/helpers.js', fromName: 'src/auth/login.js', toName: 'src/utils/helpers.js' },
|
|
18
|
+
{ from: '/project/src/auth/login.js', to: '/project/src/users/profile.js', fromName: 'src/auth/login.js', toName: 'src/users/profile.js' },
|
|
19
|
+
{ from: '/project/src/auth/logout.js', to: '/project/src/utils/helpers.js', fromName: 'src/auth/logout.js', toName: 'src/utils/helpers.js' },
|
|
20
|
+
{ from: '/project/src/users/profile.js', to: '/project/src/utils/helpers.js', fromName: 'src/users/profile.js', toName: 'src/utils/helpers.js' },
|
|
21
|
+
],
|
|
22
|
+
external: ['express', 'lodash'],
|
|
23
|
+
stats: {
|
|
24
|
+
totalFiles: 4,
|
|
25
|
+
totalEdges: 4,
|
|
26
|
+
externalDeps: 2,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Helper to create mock dependencies
|
|
31
|
+
const createMocks = (overrides = {}) => {
|
|
32
|
+
const dependencyGraph = {
|
|
33
|
+
buildFromDirectory: vi.fn().mockResolvedValue(mockGraph),
|
|
34
|
+
getGraph: vi.fn().mockReturnValue(mockGraph),
|
|
35
|
+
getImporters: vi.fn().mockReturnValue([]),
|
|
36
|
+
getImports: vi.fn().mockReturnValue([]),
|
|
37
|
+
getFiles: vi.fn().mockReturnValue(mockGraph.nodes.map(n => n.id)),
|
|
38
|
+
...overrides.dependencyGraph,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const mermaidGenerator = {
|
|
42
|
+
generateFlowchart: vi.fn().mockReturnValue('flowchart TD\n A --> B'),
|
|
43
|
+
generateModuleDiagram: vi.fn().mockReturnValue('flowchart LR\n module --> dep'),
|
|
44
|
+
...overrides.mermaidGenerator,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const boundaryDetector = {
|
|
48
|
+
detect: vi.fn().mockReturnValue({
|
|
49
|
+
services: [
|
|
50
|
+
{ name: 'auth', fileCount: 2, quality: 80, dependencies: ['utils'] },
|
|
51
|
+
{ name: 'users', fileCount: 1, quality: 60, dependencies: ['utils'] },
|
|
52
|
+
{ name: 'utils', fileCount: 1, quality: 90, dependencies: [] },
|
|
53
|
+
],
|
|
54
|
+
shared: ['src/utils/helpers.js'],
|
|
55
|
+
suggestions: [
|
|
56
|
+
{ type: 'extract-shared', message: 'Consider creating shared kernel' },
|
|
57
|
+
],
|
|
58
|
+
stats: { totalServices: 3, totalShared: 1, averageCoupling: 2 },
|
|
59
|
+
}),
|
|
60
|
+
...overrides.boundaryDetector,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const couplingCalculator = {
|
|
64
|
+
getAllMetrics: vi.fn().mockReturnValue([
|
|
65
|
+
{ file: '/project/src/auth/login.js', afferentCoupling: 1, efferentCoupling: 2, instability: 0.67 },
|
|
66
|
+
{ file: '/project/src/utils/helpers.js', afferentCoupling: 5, efferentCoupling: 0, instability: 0 },
|
|
67
|
+
]),
|
|
68
|
+
getHubFiles: vi.fn().mockReturnValue([
|
|
69
|
+
{ file: '/project/src/utils/helpers.js', afferentCoupling: 5 },
|
|
70
|
+
]),
|
|
71
|
+
getDependentFiles: vi.fn().mockReturnValue([
|
|
72
|
+
{ file: '/project/src/auth/login.js', efferentCoupling: 3 },
|
|
73
|
+
]),
|
|
74
|
+
getIsolatedFiles: vi.fn().mockReturnValue([]),
|
|
75
|
+
getHighlyCoupledModules: vi.fn().mockReturnValue([
|
|
76
|
+
{ file: '/project/src/utils/helpers.js', totalCoupling: 5, afferentCoupling: 5, efferentCoupling: 0 },
|
|
77
|
+
]),
|
|
78
|
+
...overrides.couplingCalculator,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const cohesionAnalyzer = {
|
|
82
|
+
analyze: vi.fn().mockReturnValue({
|
|
83
|
+
modules: {
|
|
84
|
+
'src/auth': { path: 'src/auth', cohesion: 0.8, internalDeps: 2, externalDeps: 1 },
|
|
85
|
+
'src/users': { path: 'src/users', cohesion: 0.5, internalDeps: 0, externalDeps: 1 },
|
|
86
|
+
},
|
|
87
|
+
lowCohesion: [
|
|
88
|
+
{ module: 'src/users', cohesion: 0.25, internalDeps: 0, externalDeps: 3 },
|
|
89
|
+
],
|
|
90
|
+
suggestions: [],
|
|
91
|
+
summary: {
|
|
92
|
+
totalModules: 3,
|
|
93
|
+
averageCohesion: 0.65,
|
|
94
|
+
lowCohesionCount: 1,
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
...overrides.cohesionAnalyzer,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const circularDetector = {
|
|
101
|
+
detect: vi.fn().mockReturnValue({
|
|
102
|
+
hasCycles: false,
|
|
103
|
+
cycleCount: 0,
|
|
104
|
+
cycles: [],
|
|
105
|
+
suggestions: [],
|
|
106
|
+
visualization: 'No circular dependencies detected.',
|
|
107
|
+
stats: { totalNodes: 4, totalEdges: 4, nodesInCycles: 0 },
|
|
108
|
+
}),
|
|
109
|
+
...overrides.circularDetector,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
dependencyGraph,
|
|
114
|
+
mermaidGenerator,
|
|
115
|
+
boundaryDetector,
|
|
116
|
+
couplingCalculator,
|
|
117
|
+
cohesionAnalyzer,
|
|
118
|
+
circularDetector,
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
describe('--analyze produces full report', () => {
|
|
123
|
+
it('runs all analyses with --analyze flag', async () => {
|
|
124
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
125
|
+
const mocks = createMocks();
|
|
126
|
+
|
|
127
|
+
const command = new ArchitectureCommand({
|
|
128
|
+
...mocks,
|
|
129
|
+
basePath: '/project',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const result = await command.run({ analyze: true });
|
|
133
|
+
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
expect(result.analysis).toBeDefined();
|
|
136
|
+
expect(result.analysis.summary).toBeDefined();
|
|
137
|
+
expect(result.boundaries).toBeDefined();
|
|
138
|
+
expect(result.metrics).toBeDefined();
|
|
139
|
+
expect(result.circular).toBeDefined();
|
|
140
|
+
expect(result.diagram).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('includes summary statistics in full report', async () => {
|
|
144
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
145
|
+
const mocks = createMocks();
|
|
146
|
+
|
|
147
|
+
const command = new ArchitectureCommand({
|
|
148
|
+
...mocks,
|
|
149
|
+
basePath: '/project',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const result = await command.run({ analyze: true });
|
|
153
|
+
|
|
154
|
+
expect(result.analysis.summary.totalFiles).toBe(4);
|
|
155
|
+
expect(result.analysis.summary.totalDependencies).toBe(4);
|
|
156
|
+
expect(result.analysis.summary.externalDependencies).toBe(2);
|
|
157
|
+
expect(result.analysis.summary.suggestedServices).toBe(3);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('runs full analysis when no flags specified', async () => {
|
|
161
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
162
|
+
const mocks = createMocks();
|
|
163
|
+
|
|
164
|
+
const command = new ArchitectureCommand({
|
|
165
|
+
...mocks,
|
|
166
|
+
basePath: '/project',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// No flags = full analysis
|
|
170
|
+
const result = await command.run({});
|
|
171
|
+
|
|
172
|
+
expect(result.success).toBe(true);
|
|
173
|
+
expect(result.analysis).toBeDefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('--boundaries lists suggested services', () => {
|
|
178
|
+
it('returns service boundary analysis', async () => {
|
|
179
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
180
|
+
const mocks = createMocks();
|
|
181
|
+
|
|
182
|
+
const command = new ArchitectureCommand({
|
|
183
|
+
...mocks,
|
|
184
|
+
basePath: '/project',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const result = await command.run({ boundaries: true });
|
|
188
|
+
|
|
189
|
+
expect(result.success).toBe(true);
|
|
190
|
+
expect(result.boundaries).toBeDefined();
|
|
191
|
+
expect(result.boundaries.services).toHaveLength(3);
|
|
192
|
+
expect(result.boundaries.services[0].name).toBe('auth');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('includes service dependencies', async () => {
|
|
196
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
197
|
+
const mocks = createMocks();
|
|
198
|
+
|
|
199
|
+
const command = new ArchitectureCommand({
|
|
200
|
+
...mocks,
|
|
201
|
+
basePath: '/project',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const result = await command.run({ boundaries: true });
|
|
205
|
+
|
|
206
|
+
const authService = result.boundaries.services.find(s => s.name === 'auth');
|
|
207
|
+
expect(authService.dependencies).toContain('utils');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('includes boundary improvement suggestions', async () => {
|
|
211
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
212
|
+
const mocks = createMocks();
|
|
213
|
+
|
|
214
|
+
const command = new ArchitectureCommand({
|
|
215
|
+
...mocks,
|
|
216
|
+
basePath: '/project',
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const result = await command.run({ boundaries: true });
|
|
220
|
+
|
|
221
|
+
expect(result.boundaries.suggestions).toBeDefined();
|
|
222
|
+
expect(result.boundaries.suggestions.length).toBeGreaterThan(0);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('--diagram outputs valid Mermaid', () => {
|
|
227
|
+
it('generates Mermaid flowchart', async () => {
|
|
228
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
229
|
+
const mocks = createMocks();
|
|
230
|
+
|
|
231
|
+
const command = new ArchitectureCommand({
|
|
232
|
+
...mocks,
|
|
233
|
+
basePath: '/project',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const result = await command.run({ diagram: true });
|
|
237
|
+
|
|
238
|
+
expect(result.success).toBe(true);
|
|
239
|
+
expect(result.diagram).toBeDefined();
|
|
240
|
+
expect(result.diagram).toContain('flowchart');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('generates module-specific diagram with targetPath', async () => {
|
|
244
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
245
|
+
const mocks = createMocks();
|
|
246
|
+
|
|
247
|
+
const command = new ArchitectureCommand({
|
|
248
|
+
...mocks,
|
|
249
|
+
basePath: '/project',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const result = await command.run({ diagram: true, targetPath: 'src/auth' });
|
|
253
|
+
|
|
254
|
+
expect(result.success).toBe(true);
|
|
255
|
+
expect(mocks.mermaidGenerator.generateModuleDiagram).toHaveBeenCalledWith(
|
|
256
|
+
expect.anything(),
|
|
257
|
+
'src/auth',
|
|
258
|
+
expect.anything()
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('highlights cycles in diagram when present', async () => {
|
|
263
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
264
|
+
const mocks = createMocks({
|
|
265
|
+
circularDetector: {
|
|
266
|
+
detect: vi.fn().mockReturnValue({
|
|
267
|
+
hasCycles: true,
|
|
268
|
+
cycleCount: 1,
|
|
269
|
+
cycles: [{ path: ['a.js', 'b.js'], pathNames: ['a.js', 'b.js'], length: 2 }],
|
|
270
|
+
suggestions: [],
|
|
271
|
+
}),
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const command = new ArchitectureCommand({
|
|
276
|
+
...mocks,
|
|
277
|
+
basePath: '/project',
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await command.run({ diagram: true });
|
|
281
|
+
|
|
282
|
+
expect(mocks.mermaidGenerator.generateFlowchart).toHaveBeenCalledWith(
|
|
283
|
+
expect.anything(),
|
|
284
|
+
expect.objectContaining({
|
|
285
|
+
highlightCycles: true,
|
|
286
|
+
cycles: expect.arrayContaining([['a.js', 'b.js']]),
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('--metrics shows coupling data', () => {
|
|
293
|
+
it('returns coupling metrics for all files', async () => {
|
|
294
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
295
|
+
const mocks = createMocks();
|
|
296
|
+
|
|
297
|
+
const command = new ArchitectureCommand({
|
|
298
|
+
...mocks,
|
|
299
|
+
basePath: '/project',
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const result = await command.run({ metrics: true });
|
|
303
|
+
|
|
304
|
+
expect(result.success).toBe(true);
|
|
305
|
+
expect(result.metrics).toBeDefined();
|
|
306
|
+
expect(result.metrics.coupling).toBeDefined();
|
|
307
|
+
expect(result.metrics.coupling.files).toBeDefined();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('identifies hub files', async () => {
|
|
311
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
312
|
+
const mocks = createMocks();
|
|
313
|
+
|
|
314
|
+
const command = new ArchitectureCommand({
|
|
315
|
+
...mocks,
|
|
316
|
+
basePath: '/project',
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const result = await command.run({ metrics: true });
|
|
320
|
+
|
|
321
|
+
expect(result.metrics.coupling.hubs).toHaveLength(1);
|
|
322
|
+
expect(result.metrics.coupling.hubs[0].file).toContain('helpers.js');
|
|
323
|
+
expect(result.metrics.coupling.hubs[0].dependents).toBe(5);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('identifies highly coupled modules', async () => {
|
|
327
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
328
|
+
const mocks = createMocks();
|
|
329
|
+
|
|
330
|
+
const command = new ArchitectureCommand({
|
|
331
|
+
...mocks,
|
|
332
|
+
basePath: '/project',
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const result = await command.run({ metrics: true });
|
|
336
|
+
|
|
337
|
+
expect(result.metrics.coupling.highlyCoupled.length).toBeGreaterThan(0);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('includes cohesion analysis', async () => {
|
|
341
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
342
|
+
const mocks = createMocks();
|
|
343
|
+
|
|
344
|
+
const command = new ArchitectureCommand({
|
|
345
|
+
...mocks,
|
|
346
|
+
basePath: '/project',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const result = await command.run({ metrics: true });
|
|
350
|
+
|
|
351
|
+
expect(result.metrics.cohesion).toBeDefined();
|
|
352
|
+
expect(result.metrics.cohesion.summary).toBeDefined();
|
|
353
|
+
expect(result.metrics.cohesion.lowCohesion).toBeDefined();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('provides coupling summary statistics', async () => {
|
|
357
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
358
|
+
const mocks = createMocks();
|
|
359
|
+
|
|
360
|
+
const command = new ArchitectureCommand({
|
|
361
|
+
...mocks,
|
|
362
|
+
basePath: '/project',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const result = await command.run({ metrics: true });
|
|
366
|
+
|
|
367
|
+
expect(result.metrics.coupling.summary).toBeDefined();
|
|
368
|
+
expect(result.metrics.coupling.summary.hubCount).toBe(1);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('--circular lists all cycles', () => {
|
|
373
|
+
it('detects no cycles when codebase is clean', async () => {
|
|
374
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
375
|
+
const mocks = createMocks();
|
|
376
|
+
|
|
377
|
+
const command = new ArchitectureCommand({
|
|
378
|
+
...mocks,
|
|
379
|
+
basePath: '/project',
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const result = await command.run({ circular: true });
|
|
383
|
+
|
|
384
|
+
expect(result.success).toBe(true);
|
|
385
|
+
expect(result.circular.hasCycles).toBe(false);
|
|
386
|
+
expect(result.circular.cycleCount).toBe(0);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('lists all detected cycles', async () => {
|
|
390
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
391
|
+
const mocks = createMocks({
|
|
392
|
+
circularDetector: {
|
|
393
|
+
detect: vi.fn().mockReturnValue({
|
|
394
|
+
hasCycles: true,
|
|
395
|
+
cycleCount: 2,
|
|
396
|
+
cycles: [
|
|
397
|
+
{ path: ['a.js', 'b.js'], pathNames: ['a.js', 'b.js'], length: 2 },
|
|
398
|
+
{ path: ['c.js', 'd.js', 'e.js'], pathNames: ['c.js', 'd.js', 'e.js'], length: 3 },
|
|
399
|
+
],
|
|
400
|
+
suggestions: [
|
|
401
|
+
{ cycleIndex: 0, breakAt: 'a.js', reason: 'Fewest dependents' },
|
|
402
|
+
{ cycleIndex: 1, breakAt: 'c.js', reason: 'Fewest dependents' },
|
|
403
|
+
],
|
|
404
|
+
visualization: 'Cycles detected',
|
|
405
|
+
stats: { totalNodes: 5, totalEdges: 5, nodesInCycles: 5 },
|
|
406
|
+
}),
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const command = new ArchitectureCommand({
|
|
411
|
+
...mocks,
|
|
412
|
+
basePath: '/project',
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const result = await command.run({ circular: true });
|
|
416
|
+
|
|
417
|
+
expect(result.circular.hasCycles).toBe(true);
|
|
418
|
+
expect(result.circular.cycleCount).toBe(2);
|
|
419
|
+
expect(result.circular.cycles).toHaveLength(2);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('includes break-point suggestions for each cycle', async () => {
|
|
423
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
424
|
+
const mocks = createMocks({
|
|
425
|
+
circularDetector: {
|
|
426
|
+
detect: vi.fn().mockReturnValue({
|
|
427
|
+
hasCycles: true,
|
|
428
|
+
cycleCount: 1,
|
|
429
|
+
cycles: [{ path: ['a.js', 'b.js'], pathNames: ['a.js', 'b.js'], length: 2 }],
|
|
430
|
+
suggestions: [{ cycleIndex: 0, breakAt: 'a.js', reason: 'Fewest dependents' }],
|
|
431
|
+
}),
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const command = new ArchitectureCommand({
|
|
436
|
+
...mocks,
|
|
437
|
+
basePath: '/project',
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const result = await command.run({ circular: true });
|
|
441
|
+
|
|
442
|
+
expect(result.circular.suggestions).toBeDefined();
|
|
443
|
+
expect(result.circular.suggestions.length).toBeGreaterThan(0);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('provides cycle visualization', async () => {
|
|
447
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
448
|
+
const mocks = createMocks({
|
|
449
|
+
circularDetector: {
|
|
450
|
+
detect: vi.fn().mockReturnValue({
|
|
451
|
+
hasCycles: true,
|
|
452
|
+
cycleCount: 1,
|
|
453
|
+
cycles: [{ path: ['a.js', 'b.js'], pathNames: ['a.js', 'b.js'], length: 2 }],
|
|
454
|
+
suggestions: [],
|
|
455
|
+
visualization: 'a.js -> b.js -> a.js',
|
|
456
|
+
}),
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const command = new ArchitectureCommand({
|
|
461
|
+
...mocks,
|
|
462
|
+
basePath: '/project',
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const result = await command.run({ circular: true });
|
|
466
|
+
|
|
467
|
+
expect(result.circular.visualization).toContain('a.js');
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('path targeting limits scope', () => {
|
|
472
|
+
it('scans only specified path', async () => {
|
|
473
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
474
|
+
const mocks = createMocks();
|
|
475
|
+
|
|
476
|
+
const command = new ArchitectureCommand({
|
|
477
|
+
...mocks,
|
|
478
|
+
basePath: '/project',
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
await command.run({ analyze: true, targetPath: 'src/auth' });
|
|
482
|
+
|
|
483
|
+
expect(mocks.dependencyGraph.buildFromDirectory).toHaveBeenCalledWith('/project/src/auth');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('includes targetPath in result', async () => {
|
|
487
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
488
|
+
const mocks = createMocks();
|
|
489
|
+
|
|
490
|
+
const command = new ArchitectureCommand({
|
|
491
|
+
...mocks,
|
|
492
|
+
basePath: '/project',
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const result = await command.run({ analyze: true, targetPath: 'src/auth' });
|
|
496
|
+
|
|
497
|
+
expect(result.targetPath).toBe('src/auth');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('generates targeted module diagram', async () => {
|
|
501
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
502
|
+
const mocks = createMocks();
|
|
503
|
+
|
|
504
|
+
const command = new ArchitectureCommand({
|
|
505
|
+
...mocks,
|
|
506
|
+
basePath: '/project',
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
await command.run({ diagram: true, targetPath: 'src/users' });
|
|
510
|
+
|
|
511
|
+
expect(mocks.mermaidGenerator.generateModuleDiagram).toHaveBeenCalledWith(
|
|
512
|
+
expect.anything(),
|
|
513
|
+
'src/users',
|
|
514
|
+
expect.anything()
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
describe('report generation', () => {
|
|
520
|
+
it('generates text report by default', async () => {
|
|
521
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
522
|
+
const mocks = createMocks();
|
|
523
|
+
|
|
524
|
+
const command = new ArchitectureCommand({
|
|
525
|
+
...mocks,
|
|
526
|
+
basePath: '/project',
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const result = await command.run({ analyze: true });
|
|
530
|
+
|
|
531
|
+
expect(result.report).toContain('ARCHITECTURE ANALYSIS REPORT');
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('generates JSON report when format=json', async () => {
|
|
535
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
536
|
+
const mocks = createMocks();
|
|
537
|
+
|
|
538
|
+
const command = new ArchitectureCommand({
|
|
539
|
+
...mocks,
|
|
540
|
+
basePath: '/project',
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const result = await command.run({ analyze: true, format: 'json' });
|
|
544
|
+
|
|
545
|
+
const parsed = JSON.parse(result.report);
|
|
546
|
+
expect(parsed.success).toBe(true);
|
|
547
|
+
expect(parsed.stats).toBeDefined();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('generates Markdown report when format=markdown', async () => {
|
|
551
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
552
|
+
const mocks = createMocks();
|
|
553
|
+
|
|
554
|
+
const command = new ArchitectureCommand({
|
|
555
|
+
...mocks,
|
|
556
|
+
basePath: '/project',
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const result = await command.run({ analyze: true, format: 'markdown' });
|
|
560
|
+
|
|
561
|
+
expect(result.report).toContain('# Architecture Analysis Report');
|
|
562
|
+
expect(result.report).toContain('## Summary');
|
|
563
|
+
expect(result.report).toContain('|');
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it('includes Mermaid diagram in markdown report', async () => {
|
|
567
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
568
|
+
const mocks = createMocks();
|
|
569
|
+
|
|
570
|
+
const command = new ArchitectureCommand({
|
|
571
|
+
...mocks,
|
|
572
|
+
basePath: '/project',
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
const result = await command.run({ analyze: true, format: 'markdown' });
|
|
576
|
+
|
|
577
|
+
expect(result.report).toContain('```mermaid');
|
|
578
|
+
expect(result.report).toContain('flowchart');
|
|
579
|
+
expect(result.report).toContain('```');
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
describe('error handling', () => {
|
|
584
|
+
it('handles graph building errors gracefully', async () => {
|
|
585
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
586
|
+
const mocks = createMocks({
|
|
587
|
+
dependencyGraph: {
|
|
588
|
+
buildFromDirectory: vi.fn().mockRejectedValue(new Error('Directory not found')),
|
|
589
|
+
getGraph: vi.fn(),
|
|
590
|
+
},
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const command = new ArchitectureCommand({
|
|
594
|
+
...mocks,
|
|
595
|
+
basePath: '/project',
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
const result = await command.run({ analyze: true });
|
|
599
|
+
|
|
600
|
+
expect(result.success).toBe(false);
|
|
601
|
+
expect(result.error).toContain('Directory not found');
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('continues with partial results on analysis errors', async () => {
|
|
605
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
606
|
+
const mocks = createMocks({
|
|
607
|
+
boundaryDetector: {
|
|
608
|
+
detect: vi.fn().mockImplementation(() => {
|
|
609
|
+
throw new Error('Boundary detection failed');
|
|
610
|
+
}),
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const command = new ArchitectureCommand({
|
|
615
|
+
...mocks,
|
|
616
|
+
basePath: '/project',
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
const result = await command.run({ boundaries: true });
|
|
620
|
+
|
|
621
|
+
expect(result.success).toBe(false);
|
|
622
|
+
expect(result.error).toContain('Boundary detection failed');
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
describe('progress reporting', () => {
|
|
627
|
+
it('reports progress through callback', async () => {
|
|
628
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
629
|
+
const mocks = createMocks();
|
|
630
|
+
const progressUpdates = [];
|
|
631
|
+
|
|
632
|
+
const command = new ArchitectureCommand({
|
|
633
|
+
...mocks,
|
|
634
|
+
basePath: '/project',
|
|
635
|
+
onProgress: (update) => progressUpdates.push(update),
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
await command.run({ analyze: true });
|
|
639
|
+
|
|
640
|
+
expect(progressUpdates.length).toBeGreaterThan(0);
|
|
641
|
+
expect(progressUpdates.some(u => u.phase === 'building-graph')).toBe(true);
|
|
642
|
+
expect(progressUpdates.some(u => u.phase === 'complete')).toBe(true);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('reports each analysis phase', async () => {
|
|
646
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
647
|
+
const mocks = createMocks();
|
|
648
|
+
const progressUpdates = [];
|
|
649
|
+
|
|
650
|
+
const command = new ArchitectureCommand({
|
|
651
|
+
...mocks,
|
|
652
|
+
basePath: '/project',
|
|
653
|
+
onProgress: (update) => progressUpdates.push(update),
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
await command.run({ analyze: true });
|
|
657
|
+
|
|
658
|
+
const phases = progressUpdates.map(u => u.phase);
|
|
659
|
+
expect(phases).toContain('building-graph');
|
|
660
|
+
expect(phases).toContain('analyzing-boundaries');
|
|
661
|
+
expect(phases).toContain('generating-diagram');
|
|
662
|
+
expect(phases).toContain('calculating-metrics');
|
|
663
|
+
expect(phases).toContain('detecting-cycles');
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
describe('dependency injection', () => {
|
|
668
|
+
it('accepts injected DependencyGraph', async () => {
|
|
669
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
670
|
+
const customGraph = {
|
|
671
|
+
buildFromDirectory: vi.fn().mockResolvedValue(mockGraph),
|
|
672
|
+
getGraph: vi.fn().mockReturnValue(mockGraph),
|
|
673
|
+
getImporters: vi.fn().mockReturnValue([]),
|
|
674
|
+
getImports: vi.fn().mockReturnValue([]),
|
|
675
|
+
getFiles: vi.fn().mockReturnValue([]),
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const command = new ArchitectureCommand({
|
|
679
|
+
dependencyGraph: customGraph,
|
|
680
|
+
basePath: '/project',
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
await command.run({ analyze: true });
|
|
684
|
+
|
|
685
|
+
expect(customGraph.buildFromDirectory).toHaveBeenCalled();
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('accepts injected MermaidGenerator', async () => {
|
|
689
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
690
|
+
const customMermaid = {
|
|
691
|
+
generateFlowchart: vi.fn().mockReturnValue('custom diagram'),
|
|
692
|
+
};
|
|
693
|
+
const mocks = createMocks({ mermaidGenerator: customMermaid });
|
|
694
|
+
|
|
695
|
+
const command = new ArchitectureCommand({
|
|
696
|
+
...mocks,
|
|
697
|
+
basePath: '/project',
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
const result = await command.run({ diagram: true });
|
|
701
|
+
|
|
702
|
+
expect(customMermaid.generateFlowchart).toHaveBeenCalled();
|
|
703
|
+
expect(result.diagram).toBe('custom diagram');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('accepts injected CircularDetector', async () => {
|
|
707
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
708
|
+
const customDetector = {
|
|
709
|
+
detect: vi.fn().mockReturnValue({
|
|
710
|
+
hasCycles: true,
|
|
711
|
+
cycleCount: 99,
|
|
712
|
+
cycles: [],
|
|
713
|
+
suggestions: [],
|
|
714
|
+
}),
|
|
715
|
+
};
|
|
716
|
+
const mocks = createMocks({ circularDetector: customDetector });
|
|
717
|
+
|
|
718
|
+
const command = new ArchitectureCommand({
|
|
719
|
+
...mocks,
|
|
720
|
+
basePath: '/project',
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const result = await command.run({ circular: true });
|
|
724
|
+
|
|
725
|
+
expect(customDetector.detect).toHaveBeenCalled();
|
|
726
|
+
expect(result.circular.cycleCount).toBe(99);
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
describe('combined flags', () => {
|
|
731
|
+
it('can run multiple analyses together', async () => {
|
|
732
|
+
const { ArchitectureCommand } = await import('./architecture-command.js');
|
|
733
|
+
const mocks = createMocks();
|
|
734
|
+
|
|
735
|
+
const command = new ArchitectureCommand({
|
|
736
|
+
...mocks,
|
|
737
|
+
basePath: '/project',
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const result = await command.run({
|
|
741
|
+
boundaries: true,
|
|
742
|
+
metrics: true,
|
|
743
|
+
circular: true,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
expect(result.success).toBe(true);
|
|
747
|
+
expect(result.boundaries).toBeDefined();
|
|
748
|
+
expect(result.metrics).toBeDefined();
|
|
749
|
+
expect(result.circular).toBeDefined();
|
|
750
|
+
// No diagram unless explicitly requested
|
|
751
|
+
expect(result.diagram).toBeNull();
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
});
|