@aaronshaf/ger 0.1.11 → 0.2.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/.github/workflows/claude-code-review.yml +61 -56
- package/.github/workflows/claude.yml +10 -24
- package/README.md +51 -0
- package/bun.lock +8 -8
- package/package.json +3 -3
- package/src/api/gerrit.ts +54 -16
- package/src/cli/commands/extract-url.ts +266 -0
- package/src/cli/commands/review.ts +13 -2
- package/src/cli/commands/setup.ts +1 -1
- package/src/cli/commands/show.ts +112 -18
- package/src/cli/index.ts +140 -23
- package/src/schemas/config.ts +13 -4
- package/src/services/config.test.ts +150 -0
- package/src/services/config.ts +60 -16
- package/src/services/git-worktree.ts +73 -16
- package/src/services/review-strategy.ts +40 -22
- package/src/utils/change-id.test.ts +98 -0
- package/src/utils/change-id.ts +63 -0
- package/src/utils/git-commit.test.ts +277 -0
- package/src/utils/git-commit.ts +122 -0
- package/tests/change-id-formats.test.ts +268 -0
- package/tests/extract-url.test.ts +518 -0
- package/tests/mocks/fetch-mock.ts +5 -2
- package/tests/mocks/msw-handlers.ts +3 -3
- package/tests/show-auto-detect.test.ts +306 -0
- package/tests/show.test.ts +157 -1
- package/tests/unit/git-worktree.test.ts +2 -1
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll, afterEach, mock, spyOn } from 'bun:test'
|
|
2
|
+
import { setupServer } from 'msw/node'
|
|
3
|
+
import { http, HttpResponse } from 'msw'
|
|
4
|
+
import { Effect, Layer } from 'effect'
|
|
5
|
+
import { showCommand } from '@/cli/commands/show'
|
|
6
|
+
import { GerritApiServiceLive } from '@/api/gerrit'
|
|
7
|
+
import { ConfigService } from '@/services/config'
|
|
8
|
+
import { generateMockChange } from '@/test-utils/mock-generator'
|
|
9
|
+
import { createMockConfigService } from './helpers/config-mock'
|
|
10
|
+
import * as childProcess from 'node:child_process'
|
|
11
|
+
import { EventEmitter } from 'node:events'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Integration tests for auto-detecting Change-ID from HEAD commit
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const mockChange = generateMockChange({
|
|
18
|
+
_number: 392385,
|
|
19
|
+
change_id: 'If5a3ae8cb5a107e187447802358417f311d0c4b1',
|
|
20
|
+
subject: 'WIP: test',
|
|
21
|
+
status: 'NEW',
|
|
22
|
+
project: 'canvas-lms',
|
|
23
|
+
branch: 'master',
|
|
24
|
+
created: '2024-01-15 10:00:00.000000000',
|
|
25
|
+
updated: '2024-01-15 12:00:00.000000000',
|
|
26
|
+
owner: {
|
|
27
|
+
_account_id: 1001,
|
|
28
|
+
name: 'Test User',
|
|
29
|
+
email: 'test@example.com',
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const mockDiff = `--- a/test.txt
|
|
34
|
+
+++ b/test.txt
|
|
35
|
+
@@ -1,1 +1,2 @@
|
|
36
|
+
original line
|
|
37
|
+
+new line`
|
|
38
|
+
|
|
39
|
+
const server = setupServer(
|
|
40
|
+
http.get('*/a/accounts/self', () => {
|
|
41
|
+
return HttpResponse.json({
|
|
42
|
+
_account_id: 1000,
|
|
43
|
+
name: 'Test User',
|
|
44
|
+
email: 'test@example.com',
|
|
45
|
+
})
|
|
46
|
+
}),
|
|
47
|
+
|
|
48
|
+
// Handler that matches the auto-detected Change-ID
|
|
49
|
+
http.get('*/a/changes/:changeId', ({ params }) => {
|
|
50
|
+
const { changeId } = params
|
|
51
|
+
if (changeId === 'If5a3ae8cb5a107e187447802358417f311d0c4b1') {
|
|
52
|
+
return HttpResponse.text(`)]}'
|
|
53
|
+
${JSON.stringify(mockChange)}`)
|
|
54
|
+
}
|
|
55
|
+
return HttpResponse.text('Not Found', { status: 404 })
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
http.get('*/a/changes/:changeId/revisions/current/patch', ({ params }) => {
|
|
59
|
+
const { changeId } = params
|
|
60
|
+
if (changeId === 'If5a3ae8cb5a107e187447802358417f311d0c4b1') {
|
|
61
|
+
return HttpResponse.text(btoa(mockDiff))
|
|
62
|
+
}
|
|
63
|
+
return HttpResponse.text('Not Found', { status: 404 })
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
http.get('*/a/changes/:changeId/revisions/current/comments', ({ params }) => {
|
|
67
|
+
const { changeId } = params
|
|
68
|
+
if (changeId === 'If5a3ae8cb5a107e187447802358417f311d0c4b1') {
|
|
69
|
+
return HttpResponse.text(`)]}'
|
|
70
|
+
{}`)
|
|
71
|
+
}
|
|
72
|
+
return HttpResponse.text('Not Found', { status: 404 })
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
let capturedLogs: string[] = []
|
|
77
|
+
let capturedErrors: string[] = []
|
|
78
|
+
|
|
79
|
+
const mockConsoleLog = mock((...args: any[]) => {
|
|
80
|
+
capturedLogs.push(args.join(' '))
|
|
81
|
+
})
|
|
82
|
+
const mockConsoleError = mock((...args: any[]) => {
|
|
83
|
+
capturedErrors.push(args.join(' '))
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const originalConsoleLog = console.log
|
|
87
|
+
const originalConsoleError = console.error
|
|
88
|
+
|
|
89
|
+
let spawnSpy: ReturnType<typeof spyOn>
|
|
90
|
+
|
|
91
|
+
beforeAll(() => {
|
|
92
|
+
server.listen({ onUnhandledRequest: 'bypass' })
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
console.log = mockConsoleLog
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
console.error = mockConsoleError
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
afterAll(() => {
|
|
100
|
+
server.close()
|
|
101
|
+
console.log = originalConsoleLog
|
|
102
|
+
console.error = originalConsoleError
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
server.resetHandlers()
|
|
107
|
+
mockConsoleLog.mockClear()
|
|
108
|
+
mockConsoleError.mockClear()
|
|
109
|
+
capturedLogs = []
|
|
110
|
+
capturedErrors = []
|
|
111
|
+
|
|
112
|
+
if (spawnSpy) {
|
|
113
|
+
spawnSpy.mockRestore()
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const createMockConfigLayer = (): Layer.Layer<ConfigService, never, never> =>
|
|
118
|
+
Layer.succeed(ConfigService, createMockConfigService())
|
|
119
|
+
|
|
120
|
+
describe('show command with auto-detection', () => {
|
|
121
|
+
test('auto-detects Change-ID from HEAD commit when no argument provided', async () => {
|
|
122
|
+
const commitMessage = `feat: add feature
|
|
123
|
+
|
|
124
|
+
Change-Id: If5a3ae8cb5a107e187447802358417f311d0c4b1`
|
|
125
|
+
|
|
126
|
+
// Mock git log command
|
|
127
|
+
const mockChildProcess = new EventEmitter()
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
mockChildProcess.stdout = new EventEmitter()
|
|
130
|
+
// @ts-ignore
|
|
131
|
+
mockChildProcess.stderr = new EventEmitter()
|
|
132
|
+
|
|
133
|
+
spawnSpy = spyOn(childProcess, 'spawn')
|
|
134
|
+
spawnSpy.mockReturnValue(mockChildProcess as any)
|
|
135
|
+
|
|
136
|
+
const effect = showCommand(undefined, {}).pipe(
|
|
137
|
+
Effect.provide(GerritApiServiceLive),
|
|
138
|
+
Effect.provide(createMockConfigLayer()),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const resultPromise = Effect.runPromise(effect)
|
|
142
|
+
|
|
143
|
+
// Simulate git log success
|
|
144
|
+
setImmediate(() => {
|
|
145
|
+
// @ts-ignore
|
|
146
|
+
mockChildProcess.stdout.emit('data', Buffer.from(commitMessage))
|
|
147
|
+
mockChildProcess.emit('close', 0)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await resultPromise
|
|
151
|
+
|
|
152
|
+
const output = capturedLogs.join('\n')
|
|
153
|
+
expect(output).toContain('Change 392385')
|
|
154
|
+
expect(output).toContain('WIP: test')
|
|
155
|
+
expect(capturedErrors.length).toBe(0)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('auto-detects Change-ID with --xml flag', async () => {
|
|
159
|
+
const commitMessage = `feat: add feature
|
|
160
|
+
|
|
161
|
+
Change-Id: If5a3ae8cb5a107e187447802358417f311d0c4b1`
|
|
162
|
+
|
|
163
|
+
const mockChildProcess = new EventEmitter()
|
|
164
|
+
// @ts-ignore
|
|
165
|
+
mockChildProcess.stdout = new EventEmitter()
|
|
166
|
+
// @ts-ignore
|
|
167
|
+
mockChildProcess.stderr = new EventEmitter()
|
|
168
|
+
|
|
169
|
+
spawnSpy = spyOn(childProcess, 'spawn')
|
|
170
|
+
spawnSpy.mockReturnValue(mockChildProcess as any)
|
|
171
|
+
|
|
172
|
+
const effect = showCommand(undefined, { xml: true }).pipe(
|
|
173
|
+
Effect.provide(GerritApiServiceLive),
|
|
174
|
+
Effect.provide(createMockConfigLayer()),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const resultPromise = Effect.runPromise(effect)
|
|
178
|
+
|
|
179
|
+
setImmediate(() => {
|
|
180
|
+
// @ts-ignore
|
|
181
|
+
mockChildProcess.stdout.emit('data', Buffer.from(commitMessage))
|
|
182
|
+
mockChildProcess.emit('close', 0)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
await resultPromise
|
|
186
|
+
|
|
187
|
+
const output = capturedLogs.join('\n')
|
|
188
|
+
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
189
|
+
expect(output).toContain('<show_result>')
|
|
190
|
+
expect(output).toContain('<status>success</status>')
|
|
191
|
+
expect(output).toContain('392385')
|
|
192
|
+
expect(capturedErrors.length).toBe(0)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('shows error when no Change-ID in HEAD commit', async () => {
|
|
196
|
+
const commitMessage = `feat: add feature without Change-ID
|
|
197
|
+
|
|
198
|
+
This commit has no Change-ID footer.`
|
|
199
|
+
|
|
200
|
+
const mockChildProcess = new EventEmitter()
|
|
201
|
+
// @ts-ignore
|
|
202
|
+
mockChildProcess.stdout = new EventEmitter()
|
|
203
|
+
// @ts-ignore
|
|
204
|
+
mockChildProcess.stderr = new EventEmitter()
|
|
205
|
+
|
|
206
|
+
spawnSpy = spyOn(childProcess, 'spawn')
|
|
207
|
+
spawnSpy.mockReturnValue(mockChildProcess as any)
|
|
208
|
+
|
|
209
|
+
const effect = showCommand(undefined, {}).pipe(
|
|
210
|
+
Effect.provide(GerritApiServiceLive),
|
|
211
|
+
Effect.provide(createMockConfigLayer()),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
const resultPromise = Effect.runPromise(effect)
|
|
215
|
+
|
|
216
|
+
setImmediate(() => {
|
|
217
|
+
// @ts-ignore
|
|
218
|
+
mockChildProcess.stdout.emit('data', Buffer.from(commitMessage))
|
|
219
|
+
mockChildProcess.emit('close', 0)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
await resultPromise
|
|
223
|
+
|
|
224
|
+
const output = capturedErrors.join('\n')
|
|
225
|
+
expect(output).toContain('No Change-ID found in HEAD commit')
|
|
226
|
+
expect(capturedLogs.length).toBe(0)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('shows error when not in git repository', async () => {
|
|
230
|
+
const mockChildProcess = new EventEmitter()
|
|
231
|
+
// @ts-ignore
|
|
232
|
+
mockChildProcess.stdout = new EventEmitter()
|
|
233
|
+
// @ts-ignore
|
|
234
|
+
mockChildProcess.stderr = new EventEmitter()
|
|
235
|
+
|
|
236
|
+
spawnSpy = spyOn(childProcess, 'spawn')
|
|
237
|
+
spawnSpy.mockReturnValue(mockChildProcess as any)
|
|
238
|
+
|
|
239
|
+
const effect = showCommand(undefined, {}).pipe(
|
|
240
|
+
Effect.provide(GerritApiServiceLive),
|
|
241
|
+
Effect.provide(createMockConfigLayer()),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
const resultPromise = Effect.runPromise(effect)
|
|
245
|
+
|
|
246
|
+
setImmediate(() => {
|
|
247
|
+
// @ts-ignore
|
|
248
|
+
mockChildProcess.stderr.emit('data', Buffer.from('fatal: not a git repository'))
|
|
249
|
+
mockChildProcess.emit('close', 128)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
await resultPromise
|
|
253
|
+
|
|
254
|
+
const output = capturedErrors.join('\n')
|
|
255
|
+
expect(output).toContain('fatal: not a git repository')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('still works with explicit change-id argument', async () => {
|
|
259
|
+
// Don't mock git - should not be called when changeId is provided
|
|
260
|
+
const effect = showCommand('If5a3ae8cb5a107e187447802358417f311d0c4b1', {}).pipe(
|
|
261
|
+
Effect.provide(GerritApiServiceLive),
|
|
262
|
+
Effect.provide(createMockConfigLayer()),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
await Effect.runPromise(effect)
|
|
266
|
+
|
|
267
|
+
const output = capturedLogs.join('\n')
|
|
268
|
+
expect(output).toContain('Change 392385')
|
|
269
|
+
expect(output).toContain('WIP: test')
|
|
270
|
+
expect(capturedErrors.length).toBe(0)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('shows XML error when no Change-ID in commit with --xml flag', async () => {
|
|
274
|
+
const commitMessage = `feat: no change id`
|
|
275
|
+
|
|
276
|
+
const mockChildProcess = new EventEmitter()
|
|
277
|
+
// @ts-ignore
|
|
278
|
+
mockChildProcess.stdout = new EventEmitter()
|
|
279
|
+
// @ts-ignore
|
|
280
|
+
mockChildProcess.stderr = new EventEmitter()
|
|
281
|
+
|
|
282
|
+
spawnSpy = spyOn(childProcess, 'spawn')
|
|
283
|
+
spawnSpy.mockReturnValue(mockChildProcess as any)
|
|
284
|
+
|
|
285
|
+
const effect = showCommand(undefined, { xml: true }).pipe(
|
|
286
|
+
Effect.provide(GerritApiServiceLive),
|
|
287
|
+
Effect.provide(createMockConfigLayer()),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const resultPromise = Effect.runPromise(effect)
|
|
291
|
+
|
|
292
|
+
setImmediate(() => {
|
|
293
|
+
// @ts-ignore
|
|
294
|
+
mockChildProcess.stdout.emit('data', Buffer.from(commitMessage))
|
|
295
|
+
mockChildProcess.emit('close', 0)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
await resultPromise
|
|
299
|
+
|
|
300
|
+
const output = capturedLogs.join('\n')
|
|
301
|
+
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
302
|
+
expect(output).toContain('<show_result>')
|
|
303
|
+
expect(output).toContain('<status>error</status>')
|
|
304
|
+
expect(output).toContain('No Change-ID found in HEAD commit')
|
|
305
|
+
})
|
|
306
|
+
})
|
package/tests/show.test.ts
CHANGED
|
@@ -238,7 +238,9 @@ describe('show command', () => {
|
|
|
238
238
|
await Effect.runPromise(program)
|
|
239
239
|
|
|
240
240
|
const output = capturedErrors.join('\n')
|
|
241
|
-
expect(output).toContain('✗
|
|
241
|
+
expect(output).toContain('✗ Error:')
|
|
242
|
+
// The error message will be from the network layer
|
|
243
|
+
expect(output.length).toBeGreaterThan(0)
|
|
242
244
|
})
|
|
243
245
|
|
|
244
246
|
test('should handle API errors gracefully in XML format', async () => {
|
|
@@ -436,4 +438,158 @@ describe('show command', () => {
|
|
|
436
438
|
// Should filter out autogenerated messages
|
|
437
439
|
expect(output).not.toContain('Uploaded patch set')
|
|
438
440
|
})
|
|
441
|
+
|
|
442
|
+
test('should output JSON format when --json flag is used', async () => {
|
|
443
|
+
setupMockHandlers()
|
|
444
|
+
|
|
445
|
+
const mockConfigLayer = createMockConfigLayer()
|
|
446
|
+
const program = showCommand('12345', { json: true }).pipe(
|
|
447
|
+
Effect.provide(GerritApiServiceLive),
|
|
448
|
+
Effect.provide(mockConfigLayer),
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
await Effect.runPromise(program)
|
|
452
|
+
|
|
453
|
+
const output = capturedLogs.join('\n')
|
|
454
|
+
|
|
455
|
+
// Parse JSON to verify it's valid
|
|
456
|
+
const parsed = JSON.parse(output)
|
|
457
|
+
|
|
458
|
+
expect(parsed.status).toBe('success')
|
|
459
|
+
expect(parsed.change.id).toBe('I123abc456def')
|
|
460
|
+
expect(parsed.change.number).toBe(12345)
|
|
461
|
+
expect(parsed.change.subject).toBe('Fix authentication bug')
|
|
462
|
+
expect(parsed.change.status).toBe('NEW')
|
|
463
|
+
expect(parsed.change.project).toBe('test-project')
|
|
464
|
+
expect(parsed.change.branch).toBe('main')
|
|
465
|
+
expect(parsed.change.owner.name).toBe('John Doe')
|
|
466
|
+
expect(parsed.change.owner.email).toBe('john@example.com')
|
|
467
|
+
|
|
468
|
+
// Check diff is present
|
|
469
|
+
expect(parsed.diff).toContain('src/auth.js')
|
|
470
|
+
expect(parsed.diff).toContain('authenticate(user)')
|
|
471
|
+
|
|
472
|
+
// Check comments array
|
|
473
|
+
expect(Array.isArray(parsed.comments)).toBe(true)
|
|
474
|
+
expect(parsed.comments.length).toBe(3)
|
|
475
|
+
expect(parsed.comments[0].message).toContain('Clear commit message')
|
|
476
|
+
expect(parsed.comments[1].message).toBe('Good improvement!')
|
|
477
|
+
expect(parsed.comments[2].message).toBe('Consider adding JSDoc')
|
|
478
|
+
|
|
479
|
+
// Check messages array (should be empty for this test)
|
|
480
|
+
expect(Array.isArray(parsed.messages)).toBe(true)
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
test('should handle API errors gracefully in JSON format', async () => {
|
|
484
|
+
server.use(
|
|
485
|
+
http.get('*/a/changes/:changeId', () => {
|
|
486
|
+
return HttpResponse.json({ error: 'Change not found' }, { status: 404 })
|
|
487
|
+
}),
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
const mockConfigLayer = createMockConfigLayer()
|
|
491
|
+
const program = showCommand('12345', { json: true }).pipe(
|
|
492
|
+
Effect.provide(GerritApiServiceLive),
|
|
493
|
+
Effect.provide(mockConfigLayer),
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
await Effect.runPromise(program)
|
|
497
|
+
|
|
498
|
+
const output = capturedLogs.join('\n')
|
|
499
|
+
|
|
500
|
+
// Parse JSON to verify it's valid
|
|
501
|
+
const parsed = JSON.parse(output)
|
|
502
|
+
|
|
503
|
+
expect(parsed.status).toBe('error')
|
|
504
|
+
expect(parsed.error).toBeDefined()
|
|
505
|
+
expect(typeof parsed.error).toBe('string')
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
test('should sort comments by date in ascending order in XML output', async () => {
|
|
509
|
+
setupMockHandlers()
|
|
510
|
+
|
|
511
|
+
const mockConfigLayer = createMockConfigLayer()
|
|
512
|
+
const program = showCommand('12345', { xml: true }).pipe(
|
|
513
|
+
Effect.provide(GerritApiServiceLive),
|
|
514
|
+
Effect.provide(mockConfigLayer),
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
await Effect.runPromise(program)
|
|
518
|
+
|
|
519
|
+
const output = capturedLogs.join('\n')
|
|
520
|
+
|
|
521
|
+
// Extract comment sections to verify order
|
|
522
|
+
const commentMatches = output.matchAll(
|
|
523
|
+
/<comment>[\s\S]*?<updated>(.*?)<\/updated>[\s\S]*?<message><!\[CDATA\[(.*?)\]\]><\/message>[\s\S]*?<\/comment>/g,
|
|
524
|
+
)
|
|
525
|
+
const comments = Array.from(commentMatches).map((match) => ({
|
|
526
|
+
updated: match[1],
|
|
527
|
+
message: match[2],
|
|
528
|
+
}))
|
|
529
|
+
|
|
530
|
+
// Should have 3 comments
|
|
531
|
+
expect(comments.length).toBe(3)
|
|
532
|
+
|
|
533
|
+
// Comments should be in ascending date order (oldest first)
|
|
534
|
+
expect(comments[0].updated).toBe('2024-01-15 11:00:00.000000000')
|
|
535
|
+
expect(comments[0].message).toBe('Clear commit message')
|
|
536
|
+
|
|
537
|
+
expect(comments[1].updated).toBe('2024-01-15 11:30:00.000000000')
|
|
538
|
+
expect(comments[1].message).toBe('Good improvement!')
|
|
539
|
+
|
|
540
|
+
expect(comments[2].updated).toBe('2024-01-15 11:45:00.000000000')
|
|
541
|
+
expect(comments[2].message).toBe('Consider adding JSDoc')
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
test('should include messages in JSON output', async () => {
|
|
545
|
+
const mockChange = generateMockChange({
|
|
546
|
+
_number: 12345,
|
|
547
|
+
subject: 'Fix authentication bug',
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
const mockMessages: MessageInfo[] = [
|
|
551
|
+
{
|
|
552
|
+
id: 'msg1',
|
|
553
|
+
message: 'Patch Set 2: Verified-1\\n\\nBuild Failed https://jenkins.example.com/job/123',
|
|
554
|
+
author: { _account_id: 1001, name: 'Jenkins Bot' },
|
|
555
|
+
date: '2024-01-15 11:30:00.000000000',
|
|
556
|
+
_revision_number: 2,
|
|
557
|
+
},
|
|
558
|
+
]
|
|
559
|
+
|
|
560
|
+
server.use(
|
|
561
|
+
http.get('*/a/changes/:changeId', ({ request }) => {
|
|
562
|
+
const url = new URL(request.url)
|
|
563
|
+
if (url.searchParams.get('o') === 'MESSAGES') {
|
|
564
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify({ messages: mockMessages })}`)
|
|
565
|
+
}
|
|
566
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
567
|
+
}),
|
|
568
|
+
http.get('*/a/changes/:changeId/revisions/current/patch', () => {
|
|
569
|
+
return HttpResponse.text('diff content')
|
|
570
|
+
}),
|
|
571
|
+
http.get('*/a/changes/:changeId/revisions/current/comments', () => {
|
|
572
|
+
return HttpResponse.text(`)]}'\n{}`)
|
|
573
|
+
}),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
const mockConfigLayer = createMockConfigLayer()
|
|
577
|
+
const program = showCommand('12345', { json: true }).pipe(
|
|
578
|
+
Effect.provide(GerritApiServiceLive),
|
|
579
|
+
Effect.provide(mockConfigLayer),
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
await Effect.runPromise(program)
|
|
583
|
+
|
|
584
|
+
const output = capturedLogs.join('\n')
|
|
585
|
+
const parsed = JSON.parse(output)
|
|
586
|
+
|
|
587
|
+
expect(parsed.messages).toBeDefined()
|
|
588
|
+
expect(Array.isArray(parsed.messages)).toBe(true)
|
|
589
|
+
expect(parsed.messages.length).toBe(1)
|
|
590
|
+
expect(parsed.messages[0].message).toContain('Build Failed')
|
|
591
|
+
expect(parsed.messages[0].message).toContain('https://jenkins.example.com')
|
|
592
|
+
expect(parsed.messages[0].author.name).toBe('Jenkins Bot')
|
|
593
|
+
expect(parsed.messages[0].revision).toBe(2)
|
|
594
|
+
})
|
|
439
595
|
})
|
|
@@ -21,7 +21,8 @@ describe('GitWorktreeService Types and Structure', () => {
|
|
|
21
21
|
|
|
22
22
|
test('should create service tag correctly', () => {
|
|
23
23
|
expect(GitWorktreeService).toBeDefined()
|
|
24
|
-
expect(typeof GitWorktreeService).toBe('
|
|
24
|
+
expect(typeof GitWorktreeService).toBe('object')
|
|
25
|
+
expect(GitWorktreeService.key).toBe('GitWorktreeService')
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
test('should be able to create mock service implementation', async () => {
|