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,727 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { wrapper } from '../../../src/cli/wrapper.js';
|
|
4
|
+
|
|
5
|
+
// Create shared mock interface for readline
|
|
6
|
+
const mockRlInterface = {
|
|
7
|
+
prompt: vi.fn(),
|
|
8
|
+
on: vi.fn(),
|
|
9
|
+
close: vi.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Mock readline
|
|
13
|
+
vi.mock('readline', () => ({
|
|
14
|
+
default: {
|
|
15
|
+
createInterface: vi.fn(() => mockRlInterface),
|
|
16
|
+
},
|
|
17
|
+
createInterface: vi.fn(() => mockRlInterface),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Mock the commands module
|
|
21
|
+
vi.mock('../../../src/commands/index.js', () => ({
|
|
22
|
+
getCurrentVersion: vi.fn().mockReturnValue('0.0.0'),
|
|
23
|
+
printAvailableCommands: vi.fn(),
|
|
24
|
+
printCommandDetail: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// Mock the config module
|
|
28
|
+
vi.mock('../../../src/config/index.js', () => ({
|
|
29
|
+
COMMANDS: [
|
|
30
|
+
'list-repositories',
|
|
31
|
+
'get-repository',
|
|
32
|
+
'list-pullrequests',
|
|
33
|
+
'get-pullrequest',
|
|
34
|
+
'create-pullrequest',
|
|
35
|
+
'list-branches',
|
|
36
|
+
'list-commits',
|
|
37
|
+
'list-issues',
|
|
38
|
+
'get-issue',
|
|
39
|
+
'create-issue',
|
|
40
|
+
'list-pipelines',
|
|
41
|
+
'get-user',
|
|
42
|
+
'test-connection',
|
|
43
|
+
],
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// Mock the utils module
|
|
47
|
+
vi.mock('../../../src/utils/index.js', () => ({
|
|
48
|
+
clearClients: vi.fn(),
|
|
49
|
+
createIssue: vi.fn(),
|
|
50
|
+
createPullRequest: vi.fn(),
|
|
51
|
+
getIssue: vi.fn(),
|
|
52
|
+
getPullRequest: vi.fn(),
|
|
53
|
+
getRepository: vi.fn(),
|
|
54
|
+
getUser: vi.fn(),
|
|
55
|
+
listBranches: vi.fn(),
|
|
56
|
+
listCommits: vi.fn(),
|
|
57
|
+
listIssues: vi.fn(),
|
|
58
|
+
listPipelines: vi.fn(),
|
|
59
|
+
listPullRequests: vi.fn(),
|
|
60
|
+
listRepositories: vi.fn(),
|
|
61
|
+
loadConfig: vi.fn(),
|
|
62
|
+
testConnection: vi.fn(),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
const originalEnv = process.env;
|
|
66
|
+
|
|
67
|
+
describe('cli/wrapper', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.clearAllMocks();
|
|
70
|
+
process.env = { ...originalEnv };
|
|
71
|
+
// Reset the mock readline interface
|
|
72
|
+
mockRlInterface.prompt.mockClear();
|
|
73
|
+
mockRlInterface.on.mockClear();
|
|
74
|
+
mockRlInterface.close.mockClear();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
process.env = originalEnv;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('wrapper', () => {
|
|
82
|
+
let cli: wrapper;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
cli = new wrapper();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('constructor', () => {
|
|
89
|
+
it('should create readline interface with correct prompt', async () => {
|
|
90
|
+
const readline = await import('readline');
|
|
91
|
+
|
|
92
|
+
const newCli = new wrapper();
|
|
93
|
+
|
|
94
|
+
expect(readline.default.createInterface).toHaveBeenCalledWith({
|
|
95
|
+
input: process.stdin,
|
|
96
|
+
output: process.stdout,
|
|
97
|
+
prompt: 'bbk> ',
|
|
98
|
+
});
|
|
99
|
+
expect(newCli).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('connect', () => {
|
|
104
|
+
it('should load config successfully', async () => {
|
|
105
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
106
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
107
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
108
|
+
defaultProfile: 'cloud',
|
|
109
|
+
defaultFormat: 'json',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
113
|
+
|
|
114
|
+
await cli.connect();
|
|
115
|
+
|
|
116
|
+
expect(loadConfig).toHaveBeenCalled();
|
|
117
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Bitbucket CLI'));
|
|
118
|
+
|
|
119
|
+
consoleLogSpy.mockRestore();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should set default profile and format', async () => {
|
|
123
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
124
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
125
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
126
|
+
defaultProfile: 'cloud',
|
|
127
|
+
defaultFormat: 'toon',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
131
|
+
|
|
132
|
+
await cli.connect();
|
|
133
|
+
|
|
134
|
+
// @ts-expect-error - accessing private property for testing
|
|
135
|
+
expect(cli.currentProfile).toBe('cloud');
|
|
136
|
+
// @ts-expect-error - accessing private property for testing
|
|
137
|
+
expect(cli.currentFormat).toBe('toon');
|
|
138
|
+
|
|
139
|
+
consoleLogSpy.mockRestore();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should exit with error if config load fails', async () => {
|
|
143
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
144
|
+
vi.mocked(loadConfig).mockImplementation(() => {
|
|
145
|
+
throw new Error('Config file not found');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
149
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
150
|
+
throw new Error('process.exit called');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await cli.connect();
|
|
155
|
+
} catch {
|
|
156
|
+
// Expected
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to load configuration:', 'Config file not found');
|
|
160
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('\nMake sure:');
|
|
161
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('1. .claude/bitbucket-config.local.md exists');
|
|
162
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
163
|
+
|
|
164
|
+
consoleErrorSpy.mockRestore();
|
|
165
|
+
exitSpy.mockRestore();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should use CLAUDE_PROJECT_ROOT if set', async () => {
|
|
169
|
+
process.env.CLAUDE_PROJECT_ROOT = '/custom/root';
|
|
170
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
171
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
172
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
173
|
+
defaultProfile: 'cloud',
|
|
174
|
+
defaultFormat: 'json',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
178
|
+
|
|
179
|
+
await cli.connect();
|
|
180
|
+
|
|
181
|
+
expect(loadConfig).toHaveBeenCalledWith('/custom/root');
|
|
182
|
+
|
|
183
|
+
consoleLogSpy.mockRestore();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should use process.cwd() if CLAUDE_PROJECT_ROOT not set', async () => {
|
|
187
|
+
delete process.env.CLAUDE_PROJECT_ROOT;
|
|
188
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
189
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
190
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
191
|
+
defaultProfile: 'cloud',
|
|
192
|
+
defaultFormat: 'json',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
196
|
+
|
|
197
|
+
await cli.connect();
|
|
198
|
+
|
|
199
|
+
expect(loadConfig).toHaveBeenCalledWith(process.cwd());
|
|
200
|
+
|
|
201
|
+
consoleLogSpy.mockRestore();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('handleCommand', () => {
|
|
206
|
+
beforeEach(async () => {
|
|
207
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
208
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
209
|
+
profiles: { cloud: { host: 'https://test.atlassian.net', email: 'test@test.com', apiToken: 'token' } },
|
|
210
|
+
defaultProfile: 'cloud',
|
|
211
|
+
defaultFormat: 'json',
|
|
212
|
+
});
|
|
213
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
214
|
+
await cli.connect();
|
|
215
|
+
consoleLogSpy.mockRestore();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should handle empty input', async () => {
|
|
219
|
+
await cli['handleCommand'](' ');
|
|
220
|
+
|
|
221
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should handle exit command', async () => {
|
|
225
|
+
const { clearClients } = await import('../../../src/utils/index.js');
|
|
226
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
227
|
+
|
|
228
|
+
await cli['handleCommand']('exit');
|
|
229
|
+
|
|
230
|
+
expect(mockRlInterface.close).toHaveBeenCalled();
|
|
231
|
+
expect(clearClients).toHaveBeenCalled();
|
|
232
|
+
|
|
233
|
+
consoleLogSpy.mockRestore();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should handle quit command', async () => {
|
|
237
|
+
const { clearClients } = await import('../../../src/utils/index.js');
|
|
238
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
239
|
+
|
|
240
|
+
await cli['handleCommand']('quit');
|
|
241
|
+
|
|
242
|
+
expect(mockRlInterface.close).toHaveBeenCalled();
|
|
243
|
+
expect(clearClients).toHaveBeenCalled();
|
|
244
|
+
|
|
245
|
+
consoleLogSpy.mockRestore();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should handle q command', async () => {
|
|
249
|
+
const { clearClients } = await import('../../../src/utils/index.js');
|
|
250
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
251
|
+
|
|
252
|
+
await cli['handleCommand']('q');
|
|
253
|
+
|
|
254
|
+
expect(mockRlInterface.close).toHaveBeenCalled();
|
|
255
|
+
expect(clearClients).toHaveBeenCalled();
|
|
256
|
+
|
|
257
|
+
consoleLogSpy.mockRestore();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should handle help command', async () => {
|
|
261
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
262
|
+
|
|
263
|
+
await cli['handleCommand']('help');
|
|
264
|
+
|
|
265
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Bitbucket CLI'));
|
|
266
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
267
|
+
|
|
268
|
+
consoleLogSpy.mockRestore();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should handle ? command as help', async () => {
|
|
272
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
273
|
+
|
|
274
|
+
await cli['handleCommand']('?');
|
|
275
|
+
|
|
276
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Bitbucket CLI'));
|
|
277
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
278
|
+
|
|
279
|
+
consoleLogSpy.mockRestore();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should handle commands command', async () => {
|
|
283
|
+
const { printAvailableCommands } = await import('../../../src/commands/index.js');
|
|
284
|
+
|
|
285
|
+
await cli['handleCommand']('commands');
|
|
286
|
+
|
|
287
|
+
expect(printAvailableCommands).toHaveBeenCalled();
|
|
288
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should handle clear command', async () => {
|
|
292
|
+
const consoleSpy = vi.spyOn(console, 'clear').mockImplementation(() => {});
|
|
293
|
+
|
|
294
|
+
await cli['handleCommand']('clear');
|
|
295
|
+
|
|
296
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
297
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
298
|
+
|
|
299
|
+
consoleSpy.mockRestore();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should switch profile to valid profile', async () => {
|
|
303
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
304
|
+
|
|
305
|
+
await cli['handleCommand']('profile cloud');
|
|
306
|
+
|
|
307
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Switched to profile: cloud');
|
|
308
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
309
|
+
|
|
310
|
+
consoleLogSpy.mockRestore();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should show error for invalid profile', async () => {
|
|
314
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
315
|
+
|
|
316
|
+
await cli['handleCommand']('profile nonexistent');
|
|
317
|
+
|
|
318
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('ERROR:'));
|
|
319
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
320
|
+
|
|
321
|
+
consoleErrorSpy.mockRestore();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('should switch format to valid format', async () => {
|
|
325
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
326
|
+
|
|
327
|
+
await cli['handleCommand']('format toon');
|
|
328
|
+
|
|
329
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Output format set to: toon');
|
|
330
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
331
|
+
|
|
332
|
+
consoleLogSpy.mockRestore();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should show error for invalid format', async () => {
|
|
336
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
337
|
+
|
|
338
|
+
await cli['handleCommand']('format xml');
|
|
339
|
+
|
|
340
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('ERROR: Invalid format. Choose: json or toon');
|
|
341
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
342
|
+
|
|
343
|
+
consoleErrorSpy.mockRestore();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should list available profiles', async () => {
|
|
347
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
348
|
+
|
|
349
|
+
await cli['handleCommand']('profiles');
|
|
350
|
+
|
|
351
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('\nAvailable profiles:');
|
|
352
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('1. cloud (current)');
|
|
353
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
354
|
+
|
|
355
|
+
consoleLogSpy.mockRestore();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should show command detail with -h flag', async () => {
|
|
359
|
+
const { printCommandDetail } = await import('../../../src/commands/index.js');
|
|
360
|
+
|
|
361
|
+
await cli['handleCommand']('list-repositories -h');
|
|
362
|
+
|
|
363
|
+
expect(printCommandDetail).toHaveBeenCalledWith('list-repositories');
|
|
364
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should show error for unknown command', async () => {
|
|
368
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
369
|
+
|
|
370
|
+
await cli['handleCommand']('unknown-command');
|
|
371
|
+
|
|
372
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Unknown command:'));
|
|
373
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
374
|
+
|
|
375
|
+
consoleErrorSpy.mockRestore();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should trim whitespace from command', async () => {
|
|
379
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
380
|
+
vi.mocked(listRepositories).mockResolvedValue({ success: true, result: '{}' });
|
|
381
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
382
|
+
|
|
383
|
+
await cli['handleCommand'](' list-repositories {"workspace":"myworkspace"} ');
|
|
384
|
+
|
|
385
|
+
expect(listRepositories).toHaveBeenCalled();
|
|
386
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
387
|
+
|
|
388
|
+
consoleLogSpy.mockRestore();
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('runCommand', () => {
|
|
393
|
+
beforeEach(async () => {
|
|
394
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
395
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
396
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
397
|
+
defaultProfile: 'cloud',
|
|
398
|
+
defaultFormat: 'json',
|
|
399
|
+
});
|
|
400
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
401
|
+
await cli.connect();
|
|
402
|
+
consoleLogSpy.mockRestore();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should execute list-repositories command', async () => {
|
|
406
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
407
|
+
vi.mocked(listRepositories).mockResolvedValue({ success: true, result: '{"repositories": []}' });
|
|
408
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
409
|
+
|
|
410
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace"}');
|
|
411
|
+
|
|
412
|
+
expect(listRepositories).toHaveBeenCalledWith('cloud', 'myworkspace', 'json');
|
|
413
|
+
|
|
414
|
+
consoleLogSpy.mockRestore();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('should execute get-repository with workspace and repoSlug', async () => {
|
|
418
|
+
const { getRepository } = await import('../../../src/utils/index.js');
|
|
419
|
+
vi.mocked(getRepository).mockResolvedValue({ success: true, result: '{"name":"my-repo"}' });
|
|
420
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
421
|
+
|
|
422
|
+
await cli['runCommand']('get-repository', '{"workspace":"myworkspace","repoSlug":"my-repo"}');
|
|
423
|
+
|
|
424
|
+
expect(getRepository).toHaveBeenCalledWith('cloud', 'myworkspace', 'my-repo', 'json');
|
|
425
|
+
|
|
426
|
+
consoleLogSpy.mockRestore();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should show error if get-repository missing parameters', async () => {
|
|
430
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
431
|
+
|
|
432
|
+
await cli['runCommand']('get-repository', '{}');
|
|
433
|
+
|
|
434
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('ERROR: "workspace" and "repoSlug" parameters are required');
|
|
435
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
436
|
+
|
|
437
|
+
consoleErrorSpy.mockRestore();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should execute list-pullrequests with all parameters', async () => {
|
|
441
|
+
const { listPullRequests } = await import('../../../src/utils/index.js');
|
|
442
|
+
vi.mocked(listPullRequests).mockResolvedValue({ success: true, result: '{"pullrequests": []}' });
|
|
443
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
444
|
+
|
|
445
|
+
await cli['runCommand']('list-pullrequests', '{"workspace":"myworkspace","repoSlug":"my-repo","state":"OPEN"}');
|
|
446
|
+
|
|
447
|
+
expect(listPullRequests).toHaveBeenCalledWith('cloud', 'myworkspace', 'my-repo', 'OPEN', 'json');
|
|
448
|
+
|
|
449
|
+
consoleLogSpy.mockRestore();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should execute get-issue with parameters', async () => {
|
|
453
|
+
const { getIssue } = await import('../../../src/utils/index.js');
|
|
454
|
+
vi.mocked(getIssue).mockResolvedValue({ success: true, result: '{"id":"123"}' });
|
|
455
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
456
|
+
|
|
457
|
+
await cli['runCommand']('get-issue', '{"workspace":"myworkspace","repoSlug":"my-repo","issueId":"123"}');
|
|
458
|
+
|
|
459
|
+
expect(getIssue).toHaveBeenCalledWith('cloud', 'myworkspace', 'my-repo', '123', 'json');
|
|
460
|
+
|
|
461
|
+
consoleLogSpy.mockRestore();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should show error if get-issue missing parameters', async () => {
|
|
465
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
466
|
+
|
|
467
|
+
await cli['runCommand']('get-issue', '{}');
|
|
468
|
+
|
|
469
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
470
|
+
'ERROR: "workspace", "repoSlug", and "issueId" parameters are required'
|
|
471
|
+
);
|
|
472
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
473
|
+
|
|
474
|
+
consoleErrorSpy.mockRestore();
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('should execute create-issue with required parameters', async () => {
|
|
478
|
+
const { createIssue } = await import('../../../src/utils/index.js');
|
|
479
|
+
vi.mocked(createIssue).mockResolvedValue({ success: true, result: '{"id":"456"}' });
|
|
480
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
481
|
+
|
|
482
|
+
await cli['runCommand'](
|
|
483
|
+
'create-issue',
|
|
484
|
+
'{"workspace":"myworkspace","repoSlug":"my-repo","title":"Bug report"}'
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
expect(createIssue).toHaveBeenCalledWith(
|
|
488
|
+
'cloud',
|
|
489
|
+
'myworkspace',
|
|
490
|
+
'my-repo',
|
|
491
|
+
'Bug report',
|
|
492
|
+
undefined,
|
|
493
|
+
undefined,
|
|
494
|
+
undefined,
|
|
495
|
+
'json'
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
consoleLogSpy.mockRestore();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('should show error if create-issue missing parameters', async () => {
|
|
502
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
503
|
+
|
|
504
|
+
await cli['runCommand']('create-issue', '{"workspace":"myworkspace"}');
|
|
505
|
+
|
|
506
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
507
|
+
'ERROR: "workspace", "repoSlug", and "title" parameters are required'
|
|
508
|
+
);
|
|
509
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
510
|
+
|
|
511
|
+
consoleErrorSpy.mockRestore();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('should execute list-branches with parameters', async () => {
|
|
515
|
+
const { listBranches } = await import('../../../src/utils/index.js');
|
|
516
|
+
vi.mocked(listBranches).mockResolvedValue({ success: true, result: '{"branches": []}' });
|
|
517
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
518
|
+
|
|
519
|
+
await cli['runCommand']('list-branches', '{"workspace":"myworkspace","repoSlug":"my-repo"}');
|
|
520
|
+
|
|
521
|
+
expect(listBranches).toHaveBeenCalledWith('cloud', 'myworkspace', 'my-repo', undefined, undefined, 'json');
|
|
522
|
+
|
|
523
|
+
consoleLogSpy.mockRestore();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should show error if list-branches missing parameters', async () => {
|
|
527
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
528
|
+
|
|
529
|
+
await cli['runCommand']('list-branches', '{}');
|
|
530
|
+
|
|
531
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('ERROR: "workspace" and "repoSlug" parameters are required');
|
|
532
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
533
|
+
|
|
534
|
+
consoleErrorSpy.mockRestore();
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should execute test-connection', async () => {
|
|
538
|
+
const { testConnection } = await import('../../../src/utils/index.js');
|
|
539
|
+
vi.mocked(testConnection).mockResolvedValue({ success: true, result: 'Connected' });
|
|
540
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
541
|
+
|
|
542
|
+
await cli['runCommand']('test-connection', '{}');
|
|
543
|
+
|
|
544
|
+
expect(testConnection).toHaveBeenCalledWith('cloud');
|
|
545
|
+
|
|
546
|
+
consoleLogSpy.mockRestore();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('should execute get-user', async () => {
|
|
550
|
+
const { getUser } = await import('../../../src/utils/index.js');
|
|
551
|
+
vi.mocked(getUser).mockResolvedValue({ success: true, result: '{"username":"testuser"}' });
|
|
552
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
553
|
+
|
|
554
|
+
await cli['runCommand']('get-user', '{"username":"testuser"}');
|
|
555
|
+
|
|
556
|
+
expect(getUser).toHaveBeenCalledWith('cloud', 'testuser', 'json');
|
|
557
|
+
|
|
558
|
+
consoleLogSpy.mockRestore();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('should use profile from args if provided', async () => {
|
|
562
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
563
|
+
vi.mocked(listRepositories).mockResolvedValue({ success: true, result: '{}' });
|
|
564
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
565
|
+
|
|
566
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace","profile":"staging"}');
|
|
567
|
+
|
|
568
|
+
expect(listRepositories).toHaveBeenCalledWith('staging', 'myworkspace', 'json');
|
|
569
|
+
|
|
570
|
+
consoleLogSpy.mockRestore();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('should use format from args if provided', async () => {
|
|
574
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
575
|
+
vi.mocked(listRepositories).mockResolvedValue({ success: true, result: '{}' });
|
|
576
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
577
|
+
|
|
578
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace","format":"toon"}');
|
|
579
|
+
|
|
580
|
+
expect(listRepositories).toHaveBeenCalledWith('cloud', 'myworkspace', 'toon');
|
|
581
|
+
|
|
582
|
+
consoleLogSpy.mockRestore();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('should use current profile and format by default', async () => {
|
|
586
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
587
|
+
vi.mocked(listRepositories).mockResolvedValue({ success: true, result: '{}' });
|
|
588
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
589
|
+
|
|
590
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace"}');
|
|
591
|
+
|
|
592
|
+
expect(listRepositories).toHaveBeenCalledWith('cloud', 'myworkspace', 'json');
|
|
593
|
+
|
|
594
|
+
consoleLogSpy.mockRestore();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should display success result', async () => {
|
|
598
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
599
|
+
vi.mocked(listRepositories).mockResolvedValue({ success: true, result: '{"repositories": []}' });
|
|
600
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
601
|
+
|
|
602
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace"}');
|
|
603
|
+
|
|
604
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('\n{"repositories": []}');
|
|
605
|
+
|
|
606
|
+
consoleLogSpy.mockRestore();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('should display error result', async () => {
|
|
610
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
611
|
+
vi.mocked(listRepositories).mockResolvedValue({ success: false, error: 'API Error' });
|
|
612
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
613
|
+
|
|
614
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace"}');
|
|
615
|
+
|
|
616
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('\nAPI Error');
|
|
617
|
+
|
|
618
|
+
consoleErrorSpy.mockRestore();
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('should handle command errors', async () => {
|
|
622
|
+
const { listRepositories } = await import('../../../src/utils/index.js');
|
|
623
|
+
vi.mocked(listRepositories).mockRejectedValue(new Error('Network error'));
|
|
624
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
625
|
+
|
|
626
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace"}');
|
|
627
|
+
|
|
628
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error running command:', 'Network error');
|
|
629
|
+
|
|
630
|
+
consoleErrorSpy.mockRestore();
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should show error if configuration not loaded', async () => {
|
|
634
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
635
|
+
// @ts-expect-error - accessing private property for testing
|
|
636
|
+
cli.config = null;
|
|
637
|
+
|
|
638
|
+
await cli['runCommand']('list-repositories', '{"workspace":"myworkspace"}');
|
|
639
|
+
|
|
640
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Configuration not loaded!');
|
|
641
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
642
|
+
|
|
643
|
+
consoleLogSpy.mockRestore();
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
describe('printHelp', () => {
|
|
648
|
+
beforeEach(async () => {
|
|
649
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
650
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
651
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
652
|
+
defaultProfile: 'cloud',
|
|
653
|
+
defaultFormat: 'json',
|
|
654
|
+
});
|
|
655
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
656
|
+
await cli.connect();
|
|
657
|
+
consoleLogSpy.mockRestore();
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('should print help message with current settings', () => {
|
|
661
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
662
|
+
|
|
663
|
+
cli['printHelp']();
|
|
664
|
+
|
|
665
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Bitbucket CLI v0.0.0'));
|
|
666
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Profile: cloud'));
|
|
667
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Format: json'));
|
|
668
|
+
|
|
669
|
+
consoleLogSpy.mockRestore();
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
describe('start', () => {
|
|
674
|
+
beforeEach(async () => {
|
|
675
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
676
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
677
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
678
|
+
defaultProfile: 'cloud',
|
|
679
|
+
defaultFormat: 'json',
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('should setup readline event handlers', async () => {
|
|
684
|
+
await cli.start();
|
|
685
|
+
|
|
686
|
+
expect(mockRlInterface.prompt).toHaveBeenCalled();
|
|
687
|
+
expect(mockRlInterface.on).toHaveBeenCalledWith('line', expect.any(Function));
|
|
688
|
+
expect(mockRlInterface.on).toHaveBeenCalledWith('close', expect.any(Function));
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it('should setup signal handlers for SIGINT and SIGTERM', async () => {
|
|
692
|
+
const onSpy = vi.spyOn(process, 'on').mockImplementation(() => process);
|
|
693
|
+
|
|
694
|
+
await cli.start();
|
|
695
|
+
|
|
696
|
+
expect(onSpy).toHaveBeenCalledWith('SIGINT', expect.any(Function));
|
|
697
|
+
expect(onSpy).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
|
|
698
|
+
|
|
699
|
+
onSpy.mockRestore();
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
describe('disconnect', () => {
|
|
704
|
+
beforeEach(async () => {
|
|
705
|
+
const { loadConfig } = await import('../../../src/utils/index.js');
|
|
706
|
+
vi.mocked(loadConfig).mockReturnValue({
|
|
707
|
+
profiles: { cloud: { email: 'test@test.com', apiToken: 'token123' } },
|
|
708
|
+
defaultProfile: 'cloud',
|
|
709
|
+
defaultFormat: 'json',
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it('should clear clients and close readline', async () => {
|
|
714
|
+
const { clearClients } = await import('../../../src/utils/index.js');
|
|
715
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
716
|
+
|
|
717
|
+
await cli['disconnect']();
|
|
718
|
+
|
|
719
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('\nClosing Bitbucket connections...');
|
|
720
|
+
expect(clearClients).toHaveBeenCalled();
|
|
721
|
+
expect(mockRlInterface.close).toHaveBeenCalled();
|
|
722
|
+
|
|
723
|
+
consoleLogSpy.mockRestore();
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
});
|