bbk-cli 1.0.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/.claude/bitbucket-config.local.md.example +58 -0
- package/.eslintcache +1 -0
- package/.github/dependabot.yml +15 -0
- package/.github/workflows/convetional-commit.yml +24 -0
- package/.github/workflows/publish-on-tag.yml +47 -0
- package/.github/workflows/release-please.yml +21 -0
- package/.github/workflows/run-tests.yml +75 -0
- package/.nvmrc +1 -0
- package/.prettierignore +2 -0
- package/.prettierrc.cjs +17 -0
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +21 -0
- package/LICENSE +202 -0
- package/README.md +381 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/wrapper.d.ts +38 -0
- package/dist/cli/wrapper.d.ts.map +1 -0
- package/dist/cli/wrapper.js +326 -0
- package/dist/cli/wrapper.js.map +1 -0
- package/dist/commands/helpers.d.ts +11 -0
- package/dist/commands/helpers.d.ts.map +1 -0
- package/dist/commands/helpers.js +40 -0
- package/dist/commands/helpers.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +3 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/runner.d.ts +7 -0
- package/dist/commands/runner.d.ts.map +1 -0
- package/dist/commands/runner.js +126 -0
- package/dist/commands/runner.js.map +1 -0
- package/dist/config/constants.d.ts +16 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +171 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/arg-parser.d.ts +7 -0
- package/dist/utils/arg-parser.d.ts.map +1 -0
- package/dist/utils/arg-parser.js +67 -0
- package/dist/utils/arg-parser.js.map +1 -0
- package/dist/utils/bitbucket-client.d.ts +122 -0
- package/dist/utils/bitbucket-client.d.ts.map +1 -0
- package/dist/utils/bitbucket-client.js +182 -0
- package/dist/utils/bitbucket-client.js.map +1 -0
- package/dist/utils/bitbucket-utils.d.ts +110 -0
- package/dist/utils/bitbucket-utils.d.ts.map +1 -0
- package/dist/utils/bitbucket-utils.js +491 -0
- package/dist/utils/bitbucket-utils.js.map +1 -0
- package/dist/utils/config-loader.d.ts +41 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +76 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/eslint.config.ts +15 -0
- package/package.json +62 -0
- package/release-please-config.json +33 -0
- package/tests/integration/cli-integration.test.ts +528 -0
- package/tests/unit/cli/wrapper.test.ts +727 -0
- package/tests/unit/commands/helpers.test.ts +268 -0
- package/tests/unit/commands/runner.test.ts +758 -0
- package/tests/unit/utils/arg-parser.test.ts +350 -0
- package/tests/unit/utils/config-loader.test.ts +158 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
// Mock the Bitbucket API functions
|
|
7
|
+
vi.mock('../../src/utils/bitbucket-client.js', () => ({
|
|
8
|
+
listRepositories: vi.fn(),
|
|
9
|
+
getRepository: vi.fn(),
|
|
10
|
+
listPullRequests: vi.fn(),
|
|
11
|
+
getPullRequest: vi.fn(),
|
|
12
|
+
createPullRequest: vi.fn(),
|
|
13
|
+
listBranches: vi.fn(),
|
|
14
|
+
listCommits: vi.fn(),
|
|
15
|
+
listIssues: vi.fn(),
|
|
16
|
+
getIssue: vi.fn(),
|
|
17
|
+
createIssue: vi.fn(),
|
|
18
|
+
listPipelines: vi.fn(),
|
|
19
|
+
getUser: vi.fn(),
|
|
20
|
+
testConnection: vi.fn(),
|
|
21
|
+
clearClients: vi.fn(),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// Mock helper functions - only where we need to spy on them
|
|
25
|
+
vi.mock('../../src/commands/helpers.js', async () => {
|
|
26
|
+
const actual = await vi.importActual('../../src/commands/helpers.js');
|
|
27
|
+
return {
|
|
28
|
+
...actual,
|
|
29
|
+
printAvailableCommands: vi.fn(actual.printAvailableCommands),
|
|
30
|
+
printCommandDetail: vi.fn(actual.printCommandDetail),
|
|
31
|
+
getCurrentVersion: vi.fn(() => '0.0.0'),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Mock config-loader to spy on loadConfig
|
|
36
|
+
vi.mock('../../src/utils/config-loader.js', async () => {
|
|
37
|
+
const actual = await vi.importActual('../../src/utils/config-loader.js');
|
|
38
|
+
return {
|
|
39
|
+
...actual,
|
|
40
|
+
loadConfig: vi.fn(actual.loadConfig),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Integration tests that test the entire flow through multiple modules
|
|
45
|
+
|
|
46
|
+
describe('CLI Integration', () => {
|
|
47
|
+
let testDir: string;
|
|
48
|
+
let configPath: string;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bbk-cli-integration-'));
|
|
52
|
+
fs.mkdirSync(path.join(testDir, '.claude'));
|
|
53
|
+
configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
54
|
+
|
|
55
|
+
// Write valid config
|
|
56
|
+
const configContent = `---
|
|
57
|
+
profiles:
|
|
58
|
+
cloud:
|
|
59
|
+
email: test@test.com
|
|
60
|
+
apiToken: test_token_123
|
|
61
|
+
staging:
|
|
62
|
+
email: staging@test.com
|
|
63
|
+
apiToken: staging_token_456
|
|
64
|
+
|
|
65
|
+
defaultProfile: cloud
|
|
66
|
+
defaultFormat: json
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
# Test Config`;
|
|
70
|
+
fs.writeFileSync(configPath, configContent);
|
|
71
|
+
|
|
72
|
+
// Clear all mocks before each test
|
|
73
|
+
vi.clearAllMocks();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('Config Loading Integration', () => {
|
|
81
|
+
it('should load and parse configuration file', async () => {
|
|
82
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
83
|
+
|
|
84
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
85
|
+
const config = await loadConfig(testDir);
|
|
86
|
+
|
|
87
|
+
expect(config).toBeDefined();
|
|
88
|
+
expect(config.profiles).toBeDefined();
|
|
89
|
+
expect(config.profiles.cloud).toBeDefined();
|
|
90
|
+
expect(config.profiles.cloud.email).toBe('test@test.com');
|
|
91
|
+
expect(config.profiles.cloud.apiToken).toBe('test_token_123');
|
|
92
|
+
expect(config.defaultProfile).toBe('cloud');
|
|
93
|
+
expect(config.defaultFormat).toBe('json');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should support multiple profiles', async () => {
|
|
97
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
98
|
+
const config = await loadConfig(testDir);
|
|
99
|
+
|
|
100
|
+
expect(Object.keys(config.profiles)).toHaveLength(2);
|
|
101
|
+
expect(config.profiles.cloud).toBeDefined();
|
|
102
|
+
expect(config.profiles.staging).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should validate required profile fields', async () => {
|
|
106
|
+
const invalidConfig = `---
|
|
107
|
+
profiles:
|
|
108
|
+
incomplete:
|
|
109
|
+
email: test@test.com
|
|
110
|
+
# Missing apiToken
|
|
111
|
+
---
|
|
112
|
+
`;
|
|
113
|
+
fs.writeFileSync(configPath, invalidConfig);
|
|
114
|
+
|
|
115
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
116
|
+
|
|
117
|
+
expect(() => loadConfig(testDir)).toThrow('must have both "email" and "apiToken"');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Command Runner Integration', () => {
|
|
122
|
+
it('should parse command line arguments and execute', async () => {
|
|
123
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
124
|
+
|
|
125
|
+
const { parseArguments } = await import('../../src/utils/arg-parser.js');
|
|
126
|
+
const { listRepositories } = await import('../../src/utils/bitbucket-client.js');
|
|
127
|
+
|
|
128
|
+
// Mock the Bitbucket API call
|
|
129
|
+
listRepositories.mockResolvedValue({
|
|
130
|
+
success: true,
|
|
131
|
+
result: JSON.stringify({ repositories: [{ slug: 'my-repo', name: 'My Repository' }] }),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
135
|
+
throw new Error('process.exit called');
|
|
136
|
+
});
|
|
137
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await parseArguments(['list-repositories', '{"workspace":"test-workspace"}']);
|
|
141
|
+
} catch {
|
|
142
|
+
// Expected
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
expect(listRepositories).toHaveBeenCalled();
|
|
146
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('repositories'));
|
|
147
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
148
|
+
|
|
149
|
+
exitSpy.mockRestore();
|
|
150
|
+
consoleLogSpy.mockRestore();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle --version flag', async () => {
|
|
154
|
+
const { parseArguments } = await import('../../src/utils/arg-parser.js');
|
|
155
|
+
|
|
156
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
157
|
+
throw new Error('process.exit called');
|
|
158
|
+
});
|
|
159
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await parseArguments(['--version']);
|
|
163
|
+
} catch {
|
|
164
|
+
// Expected
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('0.0.0');
|
|
168
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
169
|
+
|
|
170
|
+
exitSpy.mockRestore();
|
|
171
|
+
consoleLogSpy.mockRestore();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should handle --commands flag', async () => {
|
|
175
|
+
const { parseArguments } = await import('../../src/utils/arg-parser.js');
|
|
176
|
+
const { printAvailableCommands } = await import('../../src/commands/helpers.js');
|
|
177
|
+
|
|
178
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
179
|
+
throw new Error('process.exit called');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await parseArguments(['--commands']);
|
|
184
|
+
} catch {
|
|
185
|
+
// Expected
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
expect(printAvailableCommands).toHaveBeenCalled();
|
|
189
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
190
|
+
|
|
191
|
+
exitSpy.mockRestore();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle command-specific help', async () => {
|
|
195
|
+
const { parseArguments } = await import('../../src/utils/arg-parser.js');
|
|
196
|
+
const { printCommandDetail } = await import('../../src/commands/helpers.js');
|
|
197
|
+
|
|
198
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
199
|
+
throw new Error('process.exit called');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await parseArguments(['list-repositories', '-h']);
|
|
204
|
+
} catch {
|
|
205
|
+
// Expected
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
expect(printCommandDetail).toHaveBeenCalledWith('list-repositories');
|
|
209
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
210
|
+
|
|
211
|
+
exitSpy.mockRestore();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('Bitbucket API Integration', () => {
|
|
216
|
+
it('should initialize Bitbucket client with profile', async () => {
|
|
217
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
218
|
+
|
|
219
|
+
const { getBitbucketClientOptions } = await import('../../src/utils/config-loader.js');
|
|
220
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
221
|
+
|
|
222
|
+
const config = await loadConfig(testDir);
|
|
223
|
+
const options = getBitbucketClientOptions(config, 'cloud');
|
|
224
|
+
|
|
225
|
+
expect(options.auth).toBeDefined();
|
|
226
|
+
expect(options.auth?.email).toBe('test@test.com');
|
|
227
|
+
expect(options.auth?.apiToken).toBe('test_token_123');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should handle different profiles', async () => {
|
|
231
|
+
const { getBitbucketClientOptions } = await import('../../src/utils/config-loader.js');
|
|
232
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
233
|
+
|
|
234
|
+
const config = await loadConfig(testDir);
|
|
235
|
+
const cloudOptions = getBitbucketClientOptions(config, 'cloud');
|
|
236
|
+
const stagingOptions = getBitbucketClientOptions(config, 'staging');
|
|
237
|
+
|
|
238
|
+
expect(cloudOptions.auth?.email).toBe('test@test.com');
|
|
239
|
+
expect(stagingOptions.auth?.email).toBe('staging@test.com');
|
|
240
|
+
expect(cloudOptions).not.toEqual(stagingOptions);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('Command Help Integration', () => {
|
|
245
|
+
it('should display all available commands', async () => {
|
|
246
|
+
const { printAvailableCommands } = await import('../../src/commands/helpers.js');
|
|
247
|
+
const { COMMANDS } = await import('../../src/config/constants.js');
|
|
248
|
+
|
|
249
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
250
|
+
|
|
251
|
+
printAvailableCommands();
|
|
252
|
+
|
|
253
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('\nAvailable commands:');
|
|
254
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(`1. ${COMMANDS[0]}`));
|
|
255
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(`10. ${COMMANDS[9]}`));
|
|
256
|
+
|
|
257
|
+
consoleLogSpy.mockRestore();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should display detailed help for each command', async () => {
|
|
261
|
+
const { printCommandDetail } = await import('../../src/commands/helpers.js');
|
|
262
|
+
const { COMMANDS, COMMANDS_INFO } = await import('../../src/config/constants.js');
|
|
263
|
+
|
|
264
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
265
|
+
|
|
266
|
+
COMMANDS.forEach((command, index) => {
|
|
267
|
+
consoleLogSpy.mockClear();
|
|
268
|
+
printCommandDetail(command);
|
|
269
|
+
|
|
270
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(command));
|
|
271
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(COMMANDS_INFO[index]));
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
consoleLogSpy.mockRestore();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('CLI Wrapper Integration', () => {
|
|
279
|
+
it('should initialize CLI with config', async () => {
|
|
280
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
281
|
+
|
|
282
|
+
const { wrapper } = await import('../../src/cli/wrapper.js');
|
|
283
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
284
|
+
|
|
285
|
+
const cli = new wrapper();
|
|
286
|
+
|
|
287
|
+
await cli.connect();
|
|
288
|
+
|
|
289
|
+
expect(loadConfig).toHaveBeenCalledWith(testDir);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should handle profile switching', async () => {
|
|
293
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
294
|
+
|
|
295
|
+
const { wrapper } = await import('../../src/cli/wrapper.js');
|
|
296
|
+
const cli = new wrapper();
|
|
297
|
+
|
|
298
|
+
await cli.connect();
|
|
299
|
+
|
|
300
|
+
// Simulate profile switch
|
|
301
|
+
const handleCommand = cli['handleCommand'].bind(cli);
|
|
302
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
303
|
+
|
|
304
|
+
await handleCommand('profile staging');
|
|
305
|
+
|
|
306
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Switched to profile: staging');
|
|
307
|
+
|
|
308
|
+
consoleLogSpy.mockRestore();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should handle format switching', async () => {
|
|
312
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
313
|
+
|
|
314
|
+
const { wrapper } = await import('../../src/cli/wrapper.js');
|
|
315
|
+
const cli = new wrapper();
|
|
316
|
+
|
|
317
|
+
await cli.connect();
|
|
318
|
+
|
|
319
|
+
const handleCommand = cli['handleCommand'].bind(cli);
|
|
320
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
321
|
+
|
|
322
|
+
await handleCommand('format toon');
|
|
323
|
+
|
|
324
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Output format set to: toon');
|
|
325
|
+
|
|
326
|
+
consoleLogSpy.mockRestore();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('Error Handling Integration', () => {
|
|
331
|
+
it('should handle missing config file', async () => {
|
|
332
|
+
fs.rmSync(configPath);
|
|
333
|
+
|
|
334
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
335
|
+
|
|
336
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
337
|
+
|
|
338
|
+
expect(() => loadConfig(testDir)).toThrow('Configuration file not found');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should handle invalid config format', async () => {
|
|
342
|
+
const invalidConfig = `# Invalid Config
|
|
343
|
+
|
|
344
|
+
This is just markdown without frontmatter
|
|
345
|
+
`;
|
|
346
|
+
fs.writeFileSync(configPath, invalidConfig);
|
|
347
|
+
|
|
348
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
349
|
+
|
|
350
|
+
expect(() => loadConfig(testDir)).toThrow('Invalid configuration file format');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should handle missing profile', async () => {
|
|
354
|
+
const { getBitbucketClientOptions } = await import('../../src/utils/config-loader.js');
|
|
355
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
356
|
+
|
|
357
|
+
const config = await loadConfig(testDir);
|
|
358
|
+
|
|
359
|
+
expect(() => getBitbucketClientOptions(config, 'nonexistent')).toThrow('Profile "nonexistent" not found');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should handle invalid email format', async () => {
|
|
363
|
+
const invalidConfig = `---
|
|
364
|
+
profiles:
|
|
365
|
+
invalid:
|
|
366
|
+
email: invalid-email
|
|
367
|
+
apiToken: token
|
|
368
|
+
---
|
|
369
|
+
`;
|
|
370
|
+
fs.writeFileSync(configPath, invalidConfig);
|
|
371
|
+
|
|
372
|
+
const { loadConfig } = await import('../../src/utils/config-loader.js');
|
|
373
|
+
|
|
374
|
+
expect(() => loadConfig(testDir)).toThrow('Profile "invalid" has invalid email format: "invalid-email"');
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe('End-to-End Workflows', () => {
|
|
379
|
+
it('should execute list-repositories workflow', async () => {
|
|
380
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
381
|
+
|
|
382
|
+
const { runCommand } = await import('../../src/commands/runner.js');
|
|
383
|
+
const { listRepositories } = await import('../../src/utils/bitbucket-client.js');
|
|
384
|
+
|
|
385
|
+
listRepositories.mockResolvedValue({
|
|
386
|
+
success: true,
|
|
387
|
+
result: JSON.stringify({
|
|
388
|
+
repositories: [
|
|
389
|
+
{ slug: 'docs-repo', name: 'Documentation' },
|
|
390
|
+
{ slug: 'eng-repo', name: 'Engineering' },
|
|
391
|
+
],
|
|
392
|
+
}),
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
396
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
397
|
+
throw new Error('process.exit called');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
await runCommand('list-repositories', '{"workspace":"test-workspace"}', null);
|
|
402
|
+
} catch {
|
|
403
|
+
// Expected
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
expect(listRepositories).toHaveBeenCalledWith('cloud', 'test-workspace', 'json');
|
|
407
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('docs-repo'));
|
|
408
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Engineering'));
|
|
409
|
+
|
|
410
|
+
exitSpy.mockRestore();
|
|
411
|
+
consoleLogSpy.mockRestore();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should execute create-issue workflow', async () => {
|
|
415
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
416
|
+
|
|
417
|
+
const { runCommand } = await import('../../src/commands/runner.js');
|
|
418
|
+
const { createIssue } = await import('../../src/utils/bitbucket-client.js');
|
|
419
|
+
|
|
420
|
+
createIssue.mockResolvedValue({
|
|
421
|
+
success: true,
|
|
422
|
+
result: JSON.stringify({
|
|
423
|
+
id: '12345',
|
|
424
|
+
title: 'New Issue',
|
|
425
|
+
kind: 'bug',
|
|
426
|
+
}),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
430
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
431
|
+
throw new Error('process.exit called');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
await runCommand(
|
|
436
|
+
'create-issue',
|
|
437
|
+
JSON.stringify({
|
|
438
|
+
workspace: 'test-workspace',
|
|
439
|
+
repoSlug: 'my-repo',
|
|
440
|
+
title: 'New Issue',
|
|
441
|
+
content: 'Issue description',
|
|
442
|
+
}),
|
|
443
|
+
null
|
|
444
|
+
);
|
|
445
|
+
} catch {
|
|
446
|
+
// Expected
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
expect(createIssue).toHaveBeenCalledWith(
|
|
450
|
+
'cloud',
|
|
451
|
+
'test-workspace',
|
|
452
|
+
'my-repo',
|
|
453
|
+
'New Issue',
|
|
454
|
+
'Issue description',
|
|
455
|
+
undefined,
|
|
456
|
+
undefined,
|
|
457
|
+
'json'
|
|
458
|
+
);
|
|
459
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('12345'));
|
|
460
|
+
|
|
461
|
+
exitSpy.mockRestore();
|
|
462
|
+
consoleLogSpy.mockRestore();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should execute get-user workflow', async () => {
|
|
466
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
467
|
+
|
|
468
|
+
const { runCommand } = await import('../../src/commands/runner.js');
|
|
469
|
+
const { getUser } = await import('../../src/utils/bitbucket-client.js');
|
|
470
|
+
|
|
471
|
+
getUser.mockResolvedValue({
|
|
472
|
+
success: true,
|
|
473
|
+
result: JSON.stringify({
|
|
474
|
+
uuid: '{5b10a2844c20165700ede21g}',
|
|
475
|
+
display_name: 'John Doe',
|
|
476
|
+
username: 'johndoe',
|
|
477
|
+
}),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
481
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
482
|
+
throw new Error('process.exit called');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
await runCommand('get-user', '{"username":"johndoe"}', null);
|
|
487
|
+
} catch {
|
|
488
|
+
// Expected
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
expect(getUser).toHaveBeenCalledWith('cloud', 'johndoe', 'json');
|
|
492
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('John Doe'));
|
|
493
|
+
|
|
494
|
+
exitSpy.mockRestore();
|
|
495
|
+
consoleLogSpy.mockRestore();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should execute test-connection workflow', async () => {
|
|
499
|
+
process.env.CLAUDE_PROJECT_ROOT = testDir;
|
|
500
|
+
|
|
501
|
+
const { runCommand } = await import('../../src/commands/runner.js');
|
|
502
|
+
const { testConnection } = await import('../../src/utils/bitbucket-client.js');
|
|
503
|
+
|
|
504
|
+
testConnection.mockResolvedValue({
|
|
505
|
+
success: true,
|
|
506
|
+
result: 'Connection successful!\n\nProfile: cloud\nLogged in as: John Doe (johndoe)',
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
510
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
511
|
+
throw new Error('process.exit called');
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
await runCommand('test-connection', null, null);
|
|
516
|
+
} catch {
|
|
517
|
+
// Expected
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
expect(testConnection).toHaveBeenCalledWith('cloud');
|
|
521
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Connection successful'));
|
|
522
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('John Doe'));
|
|
523
|
+
|
|
524
|
+
exitSpy.mockRestore();
|
|
525
|
+
consoleLogSpy.mockRestore();
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
});
|