pnpm-catalog-updates 1.0.2 → 1.1.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/README.md +15 -0
- package/dist/index.js +22031 -10684
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/src/cli/__tests__/commandRegistrar.test.ts +248 -0
- package/src/cli/commandRegistrar.ts +785 -0
- package/src/cli/commands/__tests__/aiCommand.test.ts +161 -0
- package/src/cli/commands/__tests__/analyzeCommand.test.ts +283 -0
- package/src/cli/commands/__tests__/checkCommand.test.ts +435 -0
- package/src/cli/commands/__tests__/graphCommand.test.ts +312 -0
- package/src/cli/commands/__tests__/initCommand.test.ts +317 -0
- package/src/cli/commands/__tests__/rollbackCommand.test.ts +400 -0
- package/src/cli/commands/__tests__/securityCommand.test.ts +467 -0
- package/src/cli/commands/__tests__/themeCommand.test.ts +166 -0
- package/src/cli/commands/__tests__/updateCommand.test.ts +720 -0
- package/src/cli/commands/__tests__/workspaceCommand.test.ts +286 -0
- package/src/cli/commands/aiCommand.ts +163 -0
- package/src/cli/commands/analyzeCommand.ts +219 -0
- package/src/cli/commands/checkCommand.ts +91 -98
- package/src/cli/commands/graphCommand.ts +475 -0
- package/src/cli/commands/initCommand.ts +64 -54
- package/src/cli/commands/rollbackCommand.ts +334 -0
- package/src/cli/commands/securityCommand.ts +165 -100
- package/src/cli/commands/themeCommand.ts +148 -0
- package/src/cli/commands/updateCommand.ts +215 -263
- package/src/cli/commands/workspaceCommand.ts +73 -0
- package/src/cli/constants/cliChoices.ts +93 -0
- package/src/cli/formatters/__tests__/__snapshots__/outputFormatter.test.ts.snap +557 -0
- package/src/cli/formatters/__tests__/ciFormatter.test.ts +526 -0
- package/src/cli/formatters/__tests__/outputFormatter.test.ts +448 -0
- package/src/cli/formatters/__tests__/progressBar.test.ts +709 -0
- package/src/cli/formatters/ciFormatter.ts +964 -0
- package/src/cli/formatters/colorUtils.ts +145 -0
- package/src/cli/formatters/outputFormatter.ts +615 -332
- package/src/cli/formatters/progressBar.ts +43 -52
- package/src/cli/formatters/versionFormatter.ts +132 -0
- package/src/cli/handlers/aiAnalysisHandler.ts +205 -0
- package/src/cli/handlers/changelogHandler.ts +113 -0
- package/src/cli/handlers/index.ts +9 -0
- package/src/cli/handlers/installHandler.ts +130 -0
- package/src/cli/index.ts +175 -726
- package/src/cli/interactive/InteractiveOptionsCollector.ts +387 -0
- package/src/cli/interactive/interactivePrompts.ts +189 -83
- package/src/cli/interactive/optionUtils.ts +89 -0
- package/src/cli/themes/colorTheme.ts +43 -16
- package/src/cli/utils/cliOutput.ts +118 -0
- package/src/cli/utils/commandHelpers.ts +249 -0
- package/src/cli/validators/commandValidator.ts +321 -336
- package/src/cli/validators/index.ts +37 -2
- package/src/cli/options/globalOptions.ts +0 -437
- package/src/cli/options/index.ts +0 -5
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Command Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
|
+
|
|
7
|
+
// Use vi.hoisted to ensure mocks are available during vi.mock hoisting
|
|
8
|
+
const mocks = vi.hoisted(() => ({
|
|
9
|
+
analyzeUpdates: vi.fn(),
|
|
10
|
+
getDetectionSummary: vi.fn(),
|
|
11
|
+
detectAvailableProviders: vi.fn(),
|
|
12
|
+
getBestProvider: vi.fn(),
|
|
13
|
+
cacheClear: vi.fn(),
|
|
14
|
+
cacheGetStats: vi.fn(),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
// Mock @pcu/utils
|
|
18
|
+
vi.mock('@pcu/utils', () => ({
|
|
19
|
+
t: (key: string) => key,
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
// Mock chalk
|
|
23
|
+
vi.mock('chalk', () => ({
|
|
24
|
+
default: {
|
|
25
|
+
gray: (text: string) => text,
|
|
26
|
+
red: (text: string) => text,
|
|
27
|
+
blue: (text: string) => text,
|
|
28
|
+
yellow: (text: string) => text,
|
|
29
|
+
green: (text: string) => text,
|
|
30
|
+
cyan: (text: string) => text,
|
|
31
|
+
},
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
// Mock @pcu/core - use class syntax for proper constructor mocking
|
|
35
|
+
vi.mock('@pcu/core', () => {
|
|
36
|
+
// Create a proper class mock for AIDetector
|
|
37
|
+
const AIDetectorMock = vi.fn(function (this: Record<string, unknown>) {
|
|
38
|
+
this.getDetectionSummary = mocks.getDetectionSummary
|
|
39
|
+
this.detectAvailableProviders = mocks.detectAvailableProviders
|
|
40
|
+
this.getBestProvider = mocks.getBestProvider
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Create a proper class mock for AIAnalysisService
|
|
44
|
+
const AIAnalysisServiceMock = vi.fn(function (this: Record<string, unknown>) {
|
|
45
|
+
this.analyzeUpdates = mocks.analyzeUpdates
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
AIAnalysisService: AIAnalysisServiceMock,
|
|
50
|
+
AIDetector: AIDetectorMock,
|
|
51
|
+
analysisCache: {
|
|
52
|
+
clear: mocks.cacheClear,
|
|
53
|
+
getStats: mocks.cacheGetStats,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Import after mock setup
|
|
59
|
+
const { AiCommand } = await import('../aiCommand.js')
|
|
60
|
+
|
|
61
|
+
describe('AiCommand', () => {
|
|
62
|
+
let command: InstanceType<typeof AiCommand>
|
|
63
|
+
let consoleSpy: ReturnType<typeof vi.spyOn>
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
vi.clearAllMocks()
|
|
67
|
+
|
|
68
|
+
// Set up default mock return values
|
|
69
|
+
mocks.analyzeUpdates.mockResolvedValue({
|
|
70
|
+
provider: 'rule-engine',
|
|
71
|
+
confidence: 0.85,
|
|
72
|
+
summary: 'Test analysis completed successfully',
|
|
73
|
+
})
|
|
74
|
+
mocks.getDetectionSummary.mockResolvedValue('AI Provider Summary:\n - Claude: Available')
|
|
75
|
+
mocks.detectAvailableProviders.mockResolvedValue([
|
|
76
|
+
{ name: 'Claude', available: true, priority: 1, path: '/usr/bin/claude' },
|
|
77
|
+
{ name: 'Gemini', available: false, priority: 2 },
|
|
78
|
+
{ name: 'Codex', available: false, priority: 3 },
|
|
79
|
+
])
|
|
80
|
+
mocks.getBestProvider.mockResolvedValue({ name: 'Claude', available: true })
|
|
81
|
+
mocks.cacheGetStats.mockReturnValue({
|
|
82
|
+
totalEntries: 10,
|
|
83
|
+
hits: 8,
|
|
84
|
+
misses: 2,
|
|
85
|
+
hitRate: 0.8,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
89
|
+
|
|
90
|
+
command = new AiCommand()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
consoleSpy.mockRestore()
|
|
95
|
+
vi.resetAllMocks()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('execute', () => {
|
|
99
|
+
it('should show status by default', async () => {
|
|
100
|
+
await command.execute({})
|
|
101
|
+
|
|
102
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
103
|
+
expect(mocks.getDetectionSummary).toHaveBeenCalled()
|
|
104
|
+
expect(mocks.detectAvailableProviders).toHaveBeenCalled()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should clear cache when --clear-cache flag is set', async () => {
|
|
108
|
+
await command.execute({ clearCache: true })
|
|
109
|
+
|
|
110
|
+
expect(mocks.cacheClear).toHaveBeenCalled()
|
|
111
|
+
const calls = consoleSpy.mock.calls.flat().join(' ')
|
|
112
|
+
expect(calls).toContain('command.ai.cacheCleared')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should show cache stats when --cache-stats flag is set', async () => {
|
|
116
|
+
await command.execute({ cacheStats: true })
|
|
117
|
+
|
|
118
|
+
expect(mocks.cacheGetStats).toHaveBeenCalled()
|
|
119
|
+
const calls = consoleSpy.mock.calls.flat().join(' ')
|
|
120
|
+
expect(calls).toContain('command.ai.cacheStats')
|
|
121
|
+
expect(calls).toContain('command.ai.totalEntries')
|
|
122
|
+
expect(calls).toContain('command.ai.cacheHits')
|
|
123
|
+
expect(calls).toContain('command.ai.cacheMisses')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should run test when --test flag is set', async () => {
|
|
127
|
+
await command.execute({ test: true })
|
|
128
|
+
|
|
129
|
+
const calls = consoleSpy.mock.calls.flat().join(' ')
|
|
130
|
+
expect(calls).toContain('command.ai.testingAnalysis')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should show provider details in status', async () => {
|
|
134
|
+
await command.execute({ status: true })
|
|
135
|
+
|
|
136
|
+
expect(mocks.detectAvailableProviders).toHaveBeenCalled()
|
|
137
|
+
const calls = consoleSpy.mock.calls.flat().join(' ')
|
|
138
|
+
expect(calls).toContain('Claude')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should show best provider when available', async () => {
|
|
142
|
+
await command.execute({})
|
|
143
|
+
|
|
144
|
+
expect(mocks.getBestProvider).toHaveBeenCalled()
|
|
145
|
+
const calls = consoleSpy.mock.calls.flat().join(' ')
|
|
146
|
+
expect(calls).toContain('command.ai.bestProvider')
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe('getHelpText', () => {
|
|
151
|
+
it('should return help text with all options', () => {
|
|
152
|
+
const helpText = AiCommand.getHelpText()
|
|
153
|
+
|
|
154
|
+
expect(helpText).toContain('Check AI provider status')
|
|
155
|
+
expect(helpText).toContain('--status')
|
|
156
|
+
expect(helpText).toContain('--test')
|
|
157
|
+
expect(helpText).toContain('--cache-stats')
|
|
158
|
+
expect(helpText).toContain('--clear-cache')
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
})
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze Command Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CatalogUpdateService, WorkspaceService } from '@pcu/core'
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
+
|
|
8
|
+
// Use vi.hoisted to ensure mocks are available during vi.mock hoisting
|
|
9
|
+
const mocks = vi.hoisted(() => ({
|
|
10
|
+
analyzeUpdates: vi.fn(),
|
|
11
|
+
getLatestVersion: vi.fn(),
|
|
12
|
+
loadConfig: vi.fn(),
|
|
13
|
+
// Create a chainable chalk mock that supports all color combinations
|
|
14
|
+
createChalkMock: () => {
|
|
15
|
+
const createColorFn = (text: string) => text
|
|
16
|
+
const colorFn = Object.assign(createColorFn, {
|
|
17
|
+
bold: Object.assign((text: string) => text, {
|
|
18
|
+
cyan: (text: string) => text,
|
|
19
|
+
white: (text: string) => text,
|
|
20
|
+
}),
|
|
21
|
+
dim: Object.assign((text: string) => text, {
|
|
22
|
+
white: (text: string) => text,
|
|
23
|
+
}),
|
|
24
|
+
red: Object.assign((text: string) => text, {
|
|
25
|
+
bold: (text: string) => text,
|
|
26
|
+
}),
|
|
27
|
+
green: (text: string) => text,
|
|
28
|
+
yellow: (text: string) => text,
|
|
29
|
+
blue: (text: string) => text,
|
|
30
|
+
gray: (text: string) => text,
|
|
31
|
+
cyan: (text: string) => text,
|
|
32
|
+
white: (text: string) => text,
|
|
33
|
+
})
|
|
34
|
+
return colorFn
|
|
35
|
+
},
|
|
36
|
+
}))
|
|
37
|
+
|
|
38
|
+
// Mock @pcu/utils
|
|
39
|
+
vi.mock('@pcu/utils', () => ({
|
|
40
|
+
logger: {
|
|
41
|
+
error: vi.fn(),
|
|
42
|
+
warn: vi.fn(),
|
|
43
|
+
info: vi.fn(),
|
|
44
|
+
debug: vi.fn(),
|
|
45
|
+
},
|
|
46
|
+
Logger: {
|
|
47
|
+
setGlobalLevel: vi.fn(),
|
|
48
|
+
},
|
|
49
|
+
ConfigLoader: {
|
|
50
|
+
loadConfig: mocks.loadConfig,
|
|
51
|
+
},
|
|
52
|
+
t: (key: string, params?: Record<string, unknown>) => {
|
|
53
|
+
if (params) {
|
|
54
|
+
let result = key
|
|
55
|
+
for (const [k, v] of Object.entries(params)) {
|
|
56
|
+
result = result.replace(`{{${k}}}`, String(v))
|
|
57
|
+
}
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
60
|
+
return key
|
|
61
|
+
},
|
|
62
|
+
// Include async utilities
|
|
63
|
+
timeout: vi.fn().mockImplementation((promise: Promise<unknown>) => promise),
|
|
64
|
+
delay: vi.fn().mockResolvedValue(undefined),
|
|
65
|
+
retry: vi.fn().mockImplementation((fn: () => Promise<unknown>) => fn()),
|
|
66
|
+
}))
|
|
67
|
+
|
|
68
|
+
// Mock chalk with chainable functions
|
|
69
|
+
vi.mock('chalk', () => ({
|
|
70
|
+
default: mocks.createChalkMock(),
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
// Mock @pcu/core - use class syntax for proper constructor mocking
|
|
74
|
+
vi.mock('@pcu/core', async (importOriginal) => {
|
|
75
|
+
const actual = await importOriginal()
|
|
76
|
+
|
|
77
|
+
// Create a proper class mock for AIAnalysisService
|
|
78
|
+
const AIAnalysisServiceMock = vi.fn(function (this: Record<string, unknown>) {
|
|
79
|
+
this.analyzeUpdates = mocks.analyzeUpdates
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Create a proper class mock for NpmRegistryService
|
|
83
|
+
const NpmRegistryServiceMock = vi.fn(function (this: Record<string, unknown>) {
|
|
84
|
+
this.getLatestVersion = mocks.getLatestVersion
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
...(actual as object),
|
|
89
|
+
AIAnalysisService: AIAnalysisServiceMock,
|
|
90
|
+
NpmRegistryService: NpmRegistryServiceMock,
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Import after mock setup
|
|
95
|
+
const { AnalyzeCommand } = await import('../analyzeCommand.js')
|
|
96
|
+
|
|
97
|
+
describe('AnalyzeCommand', () => {
|
|
98
|
+
let command: InstanceType<typeof AnalyzeCommand>
|
|
99
|
+
let mockCatalogUpdateService: CatalogUpdateService
|
|
100
|
+
let mockWorkspaceService: WorkspaceService
|
|
101
|
+
let consoleSpy: ReturnType<typeof vi.spyOn>
|
|
102
|
+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>
|
|
103
|
+
let consoleWarnSpy: ReturnType<typeof vi.spyOn>
|
|
104
|
+
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
vi.clearAllMocks()
|
|
107
|
+
|
|
108
|
+
// Set up default mock return values for hoisted mocks
|
|
109
|
+
mocks.analyzeUpdates.mockResolvedValue({
|
|
110
|
+
provider: 'rule-engine',
|
|
111
|
+
confidence: 0.85,
|
|
112
|
+
summary: 'Test analysis summary',
|
|
113
|
+
recommendations: ['Recommendation 1'],
|
|
114
|
+
risks: [],
|
|
115
|
+
})
|
|
116
|
+
mocks.getLatestVersion.mockResolvedValue({ toString: () => '4.17.21' })
|
|
117
|
+
mocks.loadConfig.mockResolvedValue({
|
|
118
|
+
defaults: { format: 'table' },
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
122
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
123
|
+
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
124
|
+
|
|
125
|
+
// Create mock catalog update service
|
|
126
|
+
mockCatalogUpdateService = {
|
|
127
|
+
findCatalogForPackage: vi.fn().mockResolvedValue('default'),
|
|
128
|
+
analyzeImpact: vi.fn().mockResolvedValue({
|
|
129
|
+
catalogName: 'default',
|
|
130
|
+
packageName: 'lodash',
|
|
131
|
+
currentVersion: '4.17.20',
|
|
132
|
+
proposedVersion: '4.17.21',
|
|
133
|
+
updateType: 'patch',
|
|
134
|
+
affectedPackages: ['package-a', 'package-b'],
|
|
135
|
+
breakingChanges: [],
|
|
136
|
+
recommendations: ['Safe to update'],
|
|
137
|
+
riskLevel: 'low',
|
|
138
|
+
securityImpact: {
|
|
139
|
+
resolvedVulnerabilities: [],
|
|
140
|
+
newVulnerabilities: [],
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
checkOutdatedDependencies: vi.fn(),
|
|
144
|
+
planUpdates: vi.fn(),
|
|
145
|
+
executeUpdates: vi.fn(),
|
|
146
|
+
} as unknown as CatalogUpdateService
|
|
147
|
+
|
|
148
|
+
// Create mock workspace service
|
|
149
|
+
mockWorkspaceService = {
|
|
150
|
+
getWorkspaceInfo: vi.fn().mockResolvedValue({
|
|
151
|
+
path: '/test/workspace',
|
|
152
|
+
name: 'test-workspace',
|
|
153
|
+
isValid: true,
|
|
154
|
+
hasPackages: true,
|
|
155
|
+
hasCatalogs: true,
|
|
156
|
+
packageCount: 5,
|
|
157
|
+
catalogCount: 2,
|
|
158
|
+
catalogNames: ['default', 'react17'],
|
|
159
|
+
}),
|
|
160
|
+
discoverWorkspace: vi.fn(),
|
|
161
|
+
validateWorkspace: vi.fn(),
|
|
162
|
+
getWorkspaceStats: vi.fn(),
|
|
163
|
+
getCatalogs: vi.fn(),
|
|
164
|
+
getPackages: vi.fn(),
|
|
165
|
+
usesCatalogs: vi.fn(),
|
|
166
|
+
getPackagesUsingCatalog: vi.fn(),
|
|
167
|
+
findWorkspaces: vi.fn(),
|
|
168
|
+
checkHealth: vi.fn(),
|
|
169
|
+
} as unknown as WorkspaceService
|
|
170
|
+
|
|
171
|
+
command = new AnalyzeCommand(mockCatalogUpdateService, mockWorkspaceService)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
afterEach(() => {
|
|
175
|
+
consoleSpy.mockRestore()
|
|
176
|
+
consoleErrorSpy.mockRestore()
|
|
177
|
+
consoleWarnSpy.mockRestore()
|
|
178
|
+
vi.resetAllMocks()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe('execute', () => {
|
|
182
|
+
it('should auto-detect catalog when not specified', async () => {
|
|
183
|
+
await command.execute('lodash', undefined, {})
|
|
184
|
+
|
|
185
|
+
expect(mockCatalogUpdateService.findCatalogForPackage).toHaveBeenCalledWith(
|
|
186
|
+
'lodash',
|
|
187
|
+
undefined
|
|
188
|
+
)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('should use specified catalog', async () => {
|
|
192
|
+
await command.execute('lodash', undefined, { catalog: 'default' })
|
|
193
|
+
|
|
194
|
+
expect(mockCatalogUpdateService.findCatalogForPackage).not.toHaveBeenCalled()
|
|
195
|
+
expect(mockCatalogUpdateService.analyzeImpact).toHaveBeenCalledWith(
|
|
196
|
+
'default',
|
|
197
|
+
'lodash',
|
|
198
|
+
expect.any(String),
|
|
199
|
+
undefined
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('should throw error when package not found in any catalog', async () => {
|
|
204
|
+
mockCatalogUpdateService.findCatalogForPackage = vi.fn().mockResolvedValue(null)
|
|
205
|
+
|
|
206
|
+
await expect(command.execute('unknown-package', undefined, {})).rejects.toThrow(
|
|
207
|
+
'command.analyze.notFoundInCatalog'
|
|
208
|
+
)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should use specified version when provided', async () => {
|
|
212
|
+
await command.execute('lodash', '4.18.0', { catalog: 'default' })
|
|
213
|
+
|
|
214
|
+
expect(mockCatalogUpdateService.analyzeImpact).toHaveBeenCalledWith(
|
|
215
|
+
'default',
|
|
216
|
+
'lodash',
|
|
217
|
+
'4.18.0',
|
|
218
|
+
undefined
|
|
219
|
+
)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should fetch latest version when not specified', async () => {
|
|
223
|
+
await command.execute('lodash', undefined, { catalog: 'default' })
|
|
224
|
+
|
|
225
|
+
// The version should be fetched from NpmRegistryService
|
|
226
|
+
expect(mockCatalogUpdateService.analyzeImpact).toHaveBeenCalled()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('should run AI analysis by default', async () => {
|
|
230
|
+
await command.execute('lodash', '4.17.21', { catalog: 'default' })
|
|
231
|
+
|
|
232
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should skip AI analysis when --no-ai is set', async () => {
|
|
236
|
+
await command.execute('lodash', '4.17.21', { catalog: 'default', ai: false })
|
|
237
|
+
|
|
238
|
+
expect(mockCatalogUpdateService.analyzeImpact).toHaveBeenCalled()
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('should use specified workspace path', async () => {
|
|
242
|
+
await command.execute('lodash', '4.17.21', {
|
|
243
|
+
catalog: 'default',
|
|
244
|
+
workspace: '/custom/workspace',
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
expect(mockWorkspaceService.getWorkspaceInfo).toHaveBeenCalledWith('/custom/workspace')
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
describe('validateArgs', () => {
|
|
252
|
+
it('should return error for empty package name', () => {
|
|
253
|
+
const errors = AnalyzeCommand.validateArgs('')
|
|
254
|
+
|
|
255
|
+
expect(errors).toContain('validation.packageNameRequired')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('should return error for whitespace-only package name', () => {
|
|
259
|
+
const errors = AnalyzeCommand.validateArgs(' ')
|
|
260
|
+
|
|
261
|
+
expect(errors).toContain('validation.packageNameRequired')
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('should return no errors for valid package name', () => {
|
|
265
|
+
const errors = AnalyzeCommand.validateArgs('lodash')
|
|
266
|
+
|
|
267
|
+
expect(errors).toHaveLength(0)
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('getHelpText', () => {
|
|
272
|
+
it('should return help text with all options', () => {
|
|
273
|
+
const helpText = AnalyzeCommand.getHelpText()
|
|
274
|
+
|
|
275
|
+
expect(helpText).toContain('Analyze the impact')
|
|
276
|
+
expect(helpText).toContain('--catalog')
|
|
277
|
+
expect(helpText).toContain('--format')
|
|
278
|
+
expect(helpText).toContain('--no-ai')
|
|
279
|
+
expect(helpText).toContain('--provider')
|
|
280
|
+
expect(helpText).toContain('--analysis-type')
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
})
|