@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.
@@ -0,0 +1,268 @@
1
+ import { describe, test, expect, beforeAll, afterAll, afterEach, mock } 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 { commentCommand } from '@/cli/commands/comment'
7
+ import { diffCommand } from '@/cli/commands/diff'
8
+ import { GerritApiServiceLive } from '@/api/gerrit'
9
+ import { ConfigService } from '@/services/config'
10
+ import { generateMockChange } from '@/test-utils/mock-generator'
11
+ import { createMockConfigService } from './helpers/config-mock'
12
+
13
+ /**
14
+ * Integration tests to verify that commands accept both change number and Change-ID formats
15
+ */
16
+
17
+ const CHANGE_NUMBER = '392385'
18
+ const CHANGE_ID = 'If5a3ae8cb5a107e187447802358417f311d0c4b1'
19
+
20
+ const mockChange = generateMockChange({
21
+ _number: 392385,
22
+ change_id: CHANGE_ID,
23
+ subject: 'WIP: test',
24
+ status: 'NEW',
25
+ project: 'canvas-lms',
26
+ branch: 'master',
27
+ created: '2024-01-15 10:00:00.000000000',
28
+ updated: '2024-01-15 12:00:00.000000000',
29
+ owner: {
30
+ _account_id: 1001,
31
+ name: 'Test User',
32
+ email: 'test@example.com',
33
+ },
34
+ })
35
+
36
+ const mockDiff = `--- a/test.txt
37
+ +++ b/test.txt
38
+ @@ -1,1 +1,2 @@
39
+ original line
40
+ +new line`
41
+
42
+ const server = setupServer(
43
+ http.get('*/a/accounts/self', () => {
44
+ return HttpResponse.json({
45
+ _account_id: 1000,
46
+ name: 'Test User',
47
+ email: 'test@example.com',
48
+ })
49
+ }),
50
+
51
+ // Handler that matches both change number and Change-ID
52
+ http.get('*/a/changes/:changeId', ({ params }) => {
53
+ const { changeId } = params
54
+ // Accept both formats
55
+ if (changeId === CHANGE_NUMBER || changeId === CHANGE_ID) {
56
+ return HttpResponse.text(`)]}'
57
+ ${JSON.stringify(mockChange)}`)
58
+ }
59
+ return HttpResponse.text('Not Found', { status: 404 })
60
+ }),
61
+
62
+ http.get('*/a/changes/:changeId/revisions/current/patch', ({ params }) => {
63
+ const { changeId } = params
64
+ if (changeId === CHANGE_NUMBER || changeId === CHANGE_ID) {
65
+ return HttpResponse.text(btoa(mockDiff))
66
+ }
67
+ return HttpResponse.text('Not Found', { status: 404 })
68
+ }),
69
+
70
+ http.get('*/a/changes/:changeId/revisions/current/comments', ({ params }) => {
71
+ const { changeId } = params
72
+ if (changeId === CHANGE_NUMBER || changeId === CHANGE_ID) {
73
+ return HttpResponse.text(`)]}'
74
+ {}`)
75
+ }
76
+ return HttpResponse.text('Not Found', { status: 404 })
77
+ }),
78
+
79
+ http.post('*/a/changes/:changeId/revisions/current/review', async ({ params, request }) => {
80
+ const { changeId } = params
81
+ if (changeId === CHANGE_NUMBER || changeId === CHANGE_ID) {
82
+ return HttpResponse.text(`)]}'
83
+ {}`)
84
+ }
85
+ return HttpResponse.text('Not Found', { status: 404 })
86
+ }),
87
+ )
88
+
89
+ let capturedLogs: string[] = []
90
+ let capturedErrors: string[] = []
91
+
92
+ const mockConsoleLog = mock((...args: any[]) => {
93
+ capturedLogs.push(args.join(' '))
94
+ })
95
+ const mockConsoleError = mock((...args: any[]) => {
96
+ capturedErrors.push(args.join(' '))
97
+ })
98
+
99
+ const originalConsoleLog = console.log
100
+ const originalConsoleError = console.error
101
+
102
+ beforeAll(() => {
103
+ server.listen({ onUnhandledRequest: 'bypass' })
104
+ // @ts-ignore
105
+ console.log = mockConsoleLog
106
+ // @ts-ignore
107
+ console.error = mockConsoleError
108
+ })
109
+
110
+ afterAll(() => {
111
+ server.close()
112
+ console.log = originalConsoleLog
113
+ console.error = originalConsoleError
114
+ })
115
+
116
+ afterEach(() => {
117
+ server.resetHandlers()
118
+ mockConsoleLog.mockClear()
119
+ mockConsoleError.mockClear()
120
+ capturedLogs = []
121
+ capturedErrors = []
122
+ })
123
+
124
+ const createMockConfigLayer = (): Layer.Layer<ConfigService, never, never> =>
125
+ Layer.succeed(ConfigService, createMockConfigService())
126
+
127
+ describe('Change ID format support', () => {
128
+ describe('show command', () => {
129
+ test('accepts numeric change number', async () => {
130
+ const effect = showCommand(CHANGE_NUMBER, {}).pipe(
131
+ Effect.provide(GerritApiServiceLive),
132
+ Effect.provide(createMockConfigLayer()),
133
+ )
134
+
135
+ await Effect.runPromise(effect)
136
+
137
+ const output = capturedLogs.join('\n')
138
+ expect(output).toContain('Change 392385')
139
+ expect(output).toContain('WIP: test')
140
+ expect(capturedErrors.length).toBe(0)
141
+ })
142
+
143
+ test('accepts Change-ID format', async () => {
144
+ const effect = showCommand(CHANGE_ID, {}).pipe(
145
+ Effect.provide(GerritApiServiceLive),
146
+ Effect.provide(createMockConfigLayer()),
147
+ )
148
+
149
+ await Effect.runPromise(effect)
150
+
151
+ const output = capturedLogs.join('\n')
152
+ expect(output).toContain('Change 392385')
153
+ expect(output).toContain('WIP: test')
154
+ expect(capturedErrors.length).toBe(0)
155
+ })
156
+
157
+ test('rejects invalid change identifier', async () => {
158
+ const effect = showCommand('invalid-id', {}).pipe(
159
+ Effect.provide(GerritApiServiceLive),
160
+ Effect.provide(createMockConfigLayer()),
161
+ )
162
+
163
+ await Effect.runPromise(effect)
164
+
165
+ const output = capturedErrors.join('\n')
166
+ expect(output).toContain('Invalid change identifier')
167
+ })
168
+ })
169
+
170
+ describe('diff command', () => {
171
+ test('accepts numeric change number', async () => {
172
+ const effect = diffCommand(CHANGE_NUMBER, {}).pipe(
173
+ Effect.provide(GerritApiServiceLive),
174
+ Effect.provide(createMockConfigLayer()),
175
+ )
176
+
177
+ await Effect.runPromise(effect)
178
+
179
+ const output = capturedLogs.join('\n')
180
+ expect(output).toContain('--- a/test.txt')
181
+ expect(output).toContain('+++ b/test.txt')
182
+ expect(capturedErrors.length).toBe(0)
183
+ })
184
+
185
+ test('accepts Change-ID format', async () => {
186
+ const effect = diffCommand(CHANGE_ID, {}).pipe(
187
+ Effect.provide(GerritApiServiceLive),
188
+ Effect.provide(createMockConfigLayer()),
189
+ )
190
+
191
+ await Effect.runPromise(effect)
192
+
193
+ const output = capturedLogs.join('\n')
194
+ expect(output).toContain('--- a/test.txt')
195
+ expect(output).toContain('+++ b/test.txt')
196
+ expect(capturedErrors.length).toBe(0)
197
+ })
198
+ })
199
+
200
+ describe('comment command', () => {
201
+ test('accepts numeric change number', async () => {
202
+ const effect = commentCommand(CHANGE_NUMBER, { message: 'LGTM' }).pipe(
203
+ Effect.provide(GerritApiServiceLive),
204
+ Effect.provide(createMockConfigLayer()),
205
+ )
206
+
207
+ await Effect.runPromise(effect)
208
+
209
+ const output = capturedLogs.join('\n')
210
+ expect(output).toContain('Comment posted successfully')
211
+ expect(capturedErrors.length).toBe(0)
212
+ })
213
+
214
+ test('accepts Change-ID format', async () => {
215
+ const effect = commentCommand(CHANGE_ID, { message: 'LGTM' }).pipe(
216
+ Effect.provide(GerritApiServiceLive),
217
+ Effect.provide(createMockConfigLayer()),
218
+ )
219
+
220
+ await Effect.runPromise(effect)
221
+
222
+ const output = capturedLogs.join('\n')
223
+ expect(output).toContain('Comment posted successfully')
224
+ expect(capturedErrors.length).toBe(0)
225
+ })
226
+ })
227
+
228
+ describe('edge cases', () => {
229
+ test('trims whitespace from change identifiers', async () => {
230
+ const effect = showCommand(` ${CHANGE_NUMBER} `, {}).pipe(
231
+ Effect.provide(GerritApiServiceLive),
232
+ Effect.provide(createMockConfigLayer()),
233
+ )
234
+
235
+ await Effect.runPromise(effect)
236
+
237
+ const output = capturedLogs.join('\n')
238
+ expect(output).toContain('Change 392385')
239
+ expect(capturedErrors.length).toBe(0)
240
+ })
241
+
242
+ test('validates Change-ID format strictly (uppercase I)', async () => {
243
+ const lowercaseChangeId = 'if5a3ae8cb5a107e187447802358417f311d0c4b1'
244
+ const effect = showCommand(lowercaseChangeId, {}).pipe(
245
+ Effect.provide(GerritApiServiceLive),
246
+ Effect.provide(createMockConfigLayer()),
247
+ )
248
+
249
+ await Effect.runPromise(effect)
250
+
251
+ const output = capturedErrors.join('\n')
252
+ expect(output).toContain('Invalid change identifier')
253
+ })
254
+
255
+ test('rejects Change-ID with incorrect length', async () => {
256
+ const shortChangeId = 'If5a3ae8cb5a107e18744780235841'
257
+ const effect = showCommand(shortChangeId, {}).pipe(
258
+ Effect.provide(GerritApiServiceLive),
259
+ Effect.provide(createMockConfigLayer()),
260
+ )
261
+
262
+ await Effect.runPromise(effect)
263
+
264
+ const output = capturedErrors.join('\n')
265
+ expect(output).toContain('Invalid change identifier')
266
+ })
267
+ })
268
+ })