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,350 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { parseArguments } from '../../../src/utils/arg-parser.js';
|
|
4
|
+
|
|
5
|
+
// Mock the imported modules
|
|
6
|
+
vi.mock('../../../src/commands/index.js', () => ({
|
|
7
|
+
getCurrentVersion: vi.fn().mockReturnValue('0.0.0'),
|
|
8
|
+
printAvailableCommands: vi.fn(),
|
|
9
|
+
printCommandDetail: vi.fn(),
|
|
10
|
+
runCommand: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('../../../src/config/index.js', () => ({
|
|
14
|
+
COMMANDS: [
|
|
15
|
+
'list-repos',
|
|
16
|
+
'get-repo',
|
|
17
|
+
'list-prs',
|
|
18
|
+
'get-pr',
|
|
19
|
+
'create-pr',
|
|
20
|
+
'update-pr',
|
|
21
|
+
'add-comment',
|
|
22
|
+
'delete-pr',
|
|
23
|
+
'get-user',
|
|
24
|
+
'test-connection',
|
|
25
|
+
],
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
describe('arg-parser', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('parseArguments', () => {
|
|
38
|
+
it('should handle --version flag', async () => {
|
|
39
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
40
|
+
throw new Error('process.exit called');
|
|
41
|
+
});
|
|
42
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await parseArguments(['--version']);
|
|
46
|
+
} catch {
|
|
47
|
+
// Expected
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('0.0.0');
|
|
51
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
52
|
+
|
|
53
|
+
exitSpy.mockRestore();
|
|
54
|
+
consoleLogSpy.mockRestore();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle -v flag (short version)', async () => {
|
|
58
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
59
|
+
throw new Error('process.exit called');
|
|
60
|
+
});
|
|
61
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await parseArguments(['-v']);
|
|
65
|
+
} catch {
|
|
66
|
+
// Expected
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('0.0.0');
|
|
70
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
71
|
+
|
|
72
|
+
exitSpy.mockRestore();
|
|
73
|
+
consoleLogSpy.mockRestore();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle --commands flag', async () => {
|
|
77
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
78
|
+
throw new Error('process.exit called');
|
|
79
|
+
});
|
|
80
|
+
const { printAvailableCommands } = await import('../../../src/commands/index.js');
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await parseArguments(['--commands']);
|
|
84
|
+
} catch {
|
|
85
|
+
// Expected
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
expect(printAvailableCommands).toHaveBeenCalled();
|
|
89
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
90
|
+
|
|
91
|
+
exitSpy.mockRestore();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle command -h for command-specific help', async () => {
|
|
95
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
96
|
+
throw new Error('process.exit called');
|
|
97
|
+
});
|
|
98
|
+
const { printCommandDetail } = await import('../../../src/commands/index.js');
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await parseArguments(['list-repos', '-h']);
|
|
102
|
+
} catch {
|
|
103
|
+
// Expected
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
expect(printCommandDetail).toHaveBeenCalledWith('list-repos');
|
|
107
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
108
|
+
|
|
109
|
+
exitSpy.mockRestore();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should handle --help flag', async () => {
|
|
113
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
114
|
+
throw new Error('process.exit called');
|
|
115
|
+
});
|
|
116
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await parseArguments(['--help']);
|
|
120
|
+
} catch {
|
|
121
|
+
// Expected
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Bitbucket CLI'));
|
|
125
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Usage:'));
|
|
126
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
127
|
+
|
|
128
|
+
exitSpy.mockRestore();
|
|
129
|
+
consoleLogSpy.mockRestore();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should handle -h flag (short help)', async () => {
|
|
133
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
134
|
+
throw new Error('process.exit called');
|
|
135
|
+
});
|
|
136
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await parseArguments(['-h']);
|
|
140
|
+
} catch {
|
|
141
|
+
// Expected
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Bitbucket CLI'));
|
|
145
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
146
|
+
|
|
147
|
+
exitSpy.mockRestore();
|
|
148
|
+
consoleLogSpy.mockRestore();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should execute valid command in headless mode', async () => {
|
|
152
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
153
|
+
throw new Error('process.exit called');
|
|
154
|
+
});
|
|
155
|
+
const { runCommand } = await import('../../../src/commands/index.js');
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await parseArguments(['list-repos', '{"workspace":"myworkspace"}']);
|
|
159
|
+
} catch {
|
|
160
|
+
// Expected
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
expect(runCommand).toHaveBeenCalledWith('list-repos', '{"workspace":"myworkspace"}', null);
|
|
164
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
165
|
+
|
|
166
|
+
exitSpy.mockRestore();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should execute command without arguments', async () => {
|
|
170
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
171
|
+
throw new Error('process.exit called');
|
|
172
|
+
});
|
|
173
|
+
const { runCommand } = await import('../../../src/commands/index.js');
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await parseArguments(['test-connection']);
|
|
177
|
+
} catch {
|
|
178
|
+
// Expected
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
expect(runCommand).toHaveBeenCalledWith('test-connection', null, null);
|
|
182
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
183
|
+
|
|
184
|
+
exitSpy.mockRestore();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should parse command with flag parameter', async () => {
|
|
188
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
189
|
+
throw new Error('process.exit called');
|
|
190
|
+
});
|
|
191
|
+
const { runCommand } = await import('../../../src/commands/index.js');
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
await parseArguments(['get-repo', '{"workspace":"myworkspace","repo":"myrepo"}', '--format', 'json']);
|
|
195
|
+
} catch {
|
|
196
|
+
// Expected
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
expect(runCommand).toHaveBeenCalledWith('get-repo', '{"workspace":"myworkspace","repo":"myrepo"}', '--format');
|
|
200
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
201
|
+
|
|
202
|
+
exitSpy.mockRestore();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should return false for interactive mode (no arguments)', async () => {
|
|
206
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {});
|
|
207
|
+
const result = await parseArguments([]);
|
|
208
|
+
|
|
209
|
+
expect(result).toBe(false);
|
|
210
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
211
|
+
|
|
212
|
+
exitSpy.mockRestore();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should return false for empty arguments', async () => {
|
|
216
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {});
|
|
217
|
+
const result = await parseArguments([]);
|
|
218
|
+
|
|
219
|
+
expect(result).toBe(false);
|
|
220
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
221
|
+
|
|
222
|
+
exitSpy.mockRestore();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should return false for unrecognized flags when not first argument', async () => {
|
|
226
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {});
|
|
227
|
+
const result = await parseArguments(['--unknown']);
|
|
228
|
+
|
|
229
|
+
expect(result).toBe(false);
|
|
230
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
231
|
+
|
|
232
|
+
exitSpy.mockRestore();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should prioritize --version flag even if other arguments exist', async () => {
|
|
236
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
237
|
+
throw new Error('process.exit called');
|
|
238
|
+
});
|
|
239
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
240
|
+
const { runCommand } = await import('../../../src/commands/index.js');
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
await parseArguments(['--version', 'list-repos', '{"workspace":"myworkspace"}']);
|
|
244
|
+
} catch {
|
|
245
|
+
// Expected
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('0.0.0');
|
|
249
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
250
|
+
expect(runCommand).not.toHaveBeenCalled();
|
|
251
|
+
|
|
252
|
+
exitSpy.mockRestore();
|
|
253
|
+
consoleLogSpy.mockRestore();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should prioritize --commands flag', async () => {
|
|
257
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
258
|
+
throw new Error('process.exit called');
|
|
259
|
+
});
|
|
260
|
+
const { printAvailableCommands } = await import('../../../src/commands/index.js');
|
|
261
|
+
const { runCommand } = await import('../../../src/commands/index.js');
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
await parseArguments(['--commands', 'list-repos']);
|
|
265
|
+
} catch {
|
|
266
|
+
// Expected
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
expect(printAvailableCommands).toHaveBeenCalled();
|
|
270
|
+
expect(runCommand).not.toHaveBeenCalled();
|
|
271
|
+
|
|
272
|
+
exitSpy.mockRestore();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should handle all 10 commands as valid', async () => {
|
|
276
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
277
|
+
throw new Error('process.exit called');
|
|
278
|
+
});
|
|
279
|
+
const { runCommand } = await import('../../../src/commands/index.js');
|
|
280
|
+
|
|
281
|
+
const commands = [
|
|
282
|
+
'list-repos',
|
|
283
|
+
'get-repo',
|
|
284
|
+
'list-prs',
|
|
285
|
+
'get-pr',
|
|
286
|
+
'create-pr',
|
|
287
|
+
'update-pr',
|
|
288
|
+
'add-comment',
|
|
289
|
+
'delete-pr',
|
|
290
|
+
'get-user',
|
|
291
|
+
'test-connection',
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
for (const cmd of commands) {
|
|
295
|
+
try {
|
|
296
|
+
await parseArguments([cmd]);
|
|
297
|
+
} catch {
|
|
298
|
+
// Expected for each command
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
expect(runCommand).toHaveBeenCalledTimes(commands.length);
|
|
303
|
+
|
|
304
|
+
exitSpy.mockRestore();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('printGeneralHelp', () => {
|
|
309
|
+
it('should display help message with all commands', async () => {
|
|
310
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
311
|
+
throw new Error('process.exit called');
|
|
312
|
+
});
|
|
313
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
await parseArguments(['--help']);
|
|
317
|
+
} catch {
|
|
318
|
+
// Expected
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Bitbucket CLI'));
|
|
322
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('npx bbk-cli'));
|
|
323
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('npx bbk-cli --commands'));
|
|
324
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('list-repos'));
|
|
325
|
+
|
|
326
|
+
exitSpy.mockRestore();
|
|
327
|
+
consoleLogSpy.mockRestore();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should display examples section', async () => {
|
|
331
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((): never => {
|
|
332
|
+
throw new Error('process.exit called');
|
|
333
|
+
});
|
|
334
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
await parseArguments(['--help']);
|
|
338
|
+
} catch {
|
|
339
|
+
// Expected
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Examples:'));
|
|
343
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('npx bbk-cli list-repos'));
|
|
344
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('npx bbk-cli get-repo'));
|
|
345
|
+
|
|
346
|
+
exitSpy.mockRestore();
|
|
347
|
+
consoleLogSpy.mockRestore();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { loadConfig } from '../../../src/utils/config-loader.js';
|
|
7
|
+
|
|
8
|
+
describe('config-loader', () => {
|
|
9
|
+
let testDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Create a temporary directory for test configs
|
|
13
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bbk-cli-test-'));
|
|
14
|
+
fs.mkdirSync(path.join(testDir, '.claude'));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
// Clean up test directory
|
|
19
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('loadConfig', () => {
|
|
23
|
+
it('should load valid Bitbucket configuration file', () => {
|
|
24
|
+
const configContent = `---
|
|
25
|
+
profiles:
|
|
26
|
+
cloud:
|
|
27
|
+
email: user@example.com
|
|
28
|
+
apiToken: app_token_here
|
|
29
|
+
staging:
|
|
30
|
+
email: staging@example.com
|
|
31
|
+
apiToken: staging_token_here
|
|
32
|
+
|
|
33
|
+
defaultProfile: cloud
|
|
34
|
+
defaultFormat: json
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Bitbucket Connection Profiles
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
41
|
+
fs.writeFileSync(configPath, configContent);
|
|
42
|
+
|
|
43
|
+
const config = loadConfig(testDir);
|
|
44
|
+
|
|
45
|
+
expect(config.profiles).toBeDefined();
|
|
46
|
+
expect(config.profiles.cloud).toBeDefined();
|
|
47
|
+
expect(config.profiles.cloud.email).toBe('user@example.com');
|
|
48
|
+
expect(config.profiles.cloud.apiToken).toBe('app_token_here');
|
|
49
|
+
|
|
50
|
+
expect(config.profiles.staging).toBeDefined();
|
|
51
|
+
expect(config.profiles.staging.apiToken).toBe('staging_token_here');
|
|
52
|
+
|
|
53
|
+
expect(config.defaultProfile).toBe('cloud');
|
|
54
|
+
expect(config.defaultFormat).toBe('json');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should throw error if config file does not exist', () => {
|
|
58
|
+
expect(() => loadConfig(testDir)).toThrow('Configuration file not found');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should throw error if frontmatter is missing', () => {
|
|
62
|
+
const configContent = `# Bitbucket Connection Profiles
|
|
63
|
+
|
|
64
|
+
This is just markdown content without frontmatter.
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
68
|
+
fs.writeFileSync(configPath, configContent);
|
|
69
|
+
|
|
70
|
+
expect(() => loadConfig(testDir)).toThrow('Invalid configuration file format');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should throw error if profiles are missing', () => {
|
|
74
|
+
const configContent = `---
|
|
75
|
+
defaultProfile: cloud
|
|
76
|
+
---
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
80
|
+
fs.writeFileSync(configPath, configContent);
|
|
81
|
+
|
|
82
|
+
expect(() => loadConfig(testDir)).toThrow('Configuration must include "profiles" object');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should throw error if profile is missing required auth fields', () => {
|
|
86
|
+
const configContent = `---
|
|
87
|
+
profiles:
|
|
88
|
+
incomplete:
|
|
89
|
+
email: test@example.com
|
|
90
|
+
# Missing apiToken
|
|
91
|
+
---
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
95
|
+
fs.writeFileSync(configPath, configContent);
|
|
96
|
+
|
|
97
|
+
expect(() => loadConfig(testDir)).toThrow('must have both "email" and "apiToken"');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should use first profile as default if defaultProfile not specified', () => {
|
|
101
|
+
const configContent = `---
|
|
102
|
+
profiles:
|
|
103
|
+
first:
|
|
104
|
+
email: first@example.com
|
|
105
|
+
apiToken: first_token
|
|
106
|
+
second:
|
|
107
|
+
email: second@example.com
|
|
108
|
+
apiToken: second_token
|
|
109
|
+
---
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
113
|
+
fs.writeFileSync(configPath, configContent);
|
|
114
|
+
|
|
115
|
+
const config = loadConfig(testDir);
|
|
116
|
+
|
|
117
|
+
expect(config.defaultProfile).toBe('first');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should use json as default format if not specified', () => {
|
|
121
|
+
const configContent = `---
|
|
122
|
+
profiles:
|
|
123
|
+
cloud:
|
|
124
|
+
email: user@example.com
|
|
125
|
+
apiToken: token_here
|
|
126
|
+
---
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
const configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
130
|
+
fs.writeFileSync(configPath, configContent);
|
|
131
|
+
|
|
132
|
+
const config = loadConfig(testDir);
|
|
133
|
+
|
|
134
|
+
expect(config.defaultFormat).toBe('json');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should support all output formats: json, toon', () => {
|
|
138
|
+
const formats: Array<'json' | 'toon'> = ['json', 'toon'];
|
|
139
|
+
|
|
140
|
+
formats.forEach(format => {
|
|
141
|
+
const configContent = `---
|
|
142
|
+
profiles:
|
|
143
|
+
cloud:
|
|
144
|
+
email: user@example.com
|
|
145
|
+
apiToken: token_here
|
|
146
|
+
defaultFormat: ${format}
|
|
147
|
+
---
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
const configPath = path.join(testDir, '.claude', 'bitbucket-config.local.md');
|
|
151
|
+
fs.writeFileSync(configPath, configContent);
|
|
152
|
+
|
|
153
|
+
const config = loadConfig(testDir);
|
|
154
|
+
expect(config.defaultFormat).toBe(format);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: 'v8',
|
|
9
|
+
reporter: ['text', 'json', 'html'],
|
|
10
|
+
exclude: [
|
|
11
|
+
'node_modules/',
|
|
12
|
+
'dist/',
|
|
13
|
+
'**/*.d.ts',
|
|
14
|
+
'**/*.config.*',
|
|
15
|
+
'**/index.ts', // Barrel exports
|
|
16
|
+
'tests/',
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
include: ['tests/**/*.test.ts'],
|
|
20
|
+
exclude: ['node_modules', 'dist'],
|
|
21
|
+
},
|
|
22
|
+
});
|