@aaronshaf/ger 0.2.4 → 0.3.1
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.md +3 -3
- package/README.md +87 -0
- package/package.json +1 -1
- package/src/cli/commands/build-status.ts +238 -0
- package/src/cli/index.ts +80 -0
- package/tests/abandon.test.ts +178 -111
- package/tests/build-status-watch.test.ts +347 -0
- package/tests/build-status.test.ts +640 -0
- package/tests/helpers/build-status-test-setup.ts +83 -0
- package/tests/mine.test.ts +130 -163
- package/tests/mocks/fetch-mock.ts +0 -142
- package/tests/setup.ts +0 -13
package/tests/mine.test.ts
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, mock, test } from 'bun:test'
|
|
2
2
|
import { Effect, Layer } from 'effect'
|
|
3
|
+
import { HttpResponse, http } from 'msw'
|
|
4
|
+
import { setupServer } from 'msw/node'
|
|
5
|
+
import { GerritApiServiceLive } from '@/api/gerrit'
|
|
3
6
|
import { mineCommand } from '@/cli/commands/mine'
|
|
4
|
-
import {
|
|
7
|
+
import { ConfigService } from '@/services/config'
|
|
5
8
|
import { generateMockChange } from '@/test-utils/mock-generator'
|
|
6
9
|
import type { ChangeInfo } from '@/schemas/gerrit'
|
|
10
|
+
import { createMockConfigService } from './helpers/config-mock'
|
|
7
11
|
|
|
8
|
-
//
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
// Create MSW server
|
|
13
|
+
const server = setupServer(
|
|
14
|
+
// Default handler for auth check
|
|
15
|
+
http.get('*/a/accounts/self', ({ request }) => {
|
|
16
|
+
const auth = request.headers.get('Authorization')
|
|
17
|
+
if (!auth || !auth.startsWith('Basic ')) {
|
|
18
|
+
return HttpResponse.text('Unauthorized', { status: 401 })
|
|
19
|
+
}
|
|
20
|
+
return HttpResponse.json({
|
|
21
|
+
_account_id: 1000,
|
|
22
|
+
name: 'Test User',
|
|
23
|
+
email: 'test@example.com',
|
|
24
|
+
})
|
|
25
|
+
}),
|
|
26
|
+
)
|
|
18
27
|
|
|
19
28
|
describe('mine command', () => {
|
|
29
|
+
let mockConsoleLog: ReturnType<typeof mock>
|
|
30
|
+
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
server.listen({ onUnhandledRequest: 'bypass' })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterAll(() => {
|
|
36
|
+
server.close()
|
|
37
|
+
})
|
|
38
|
+
|
|
20
39
|
beforeEach(() => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
mockConsoleLog = mock(() => {})
|
|
41
|
+
console.log = mockConsoleLog
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
server.resetHandlers()
|
|
24
46
|
})
|
|
25
47
|
|
|
26
48
|
test('should fetch and display my changes in pretty format', async () => {
|
|
@@ -41,32 +63,26 @@ describe('mine command', () => {
|
|
|
41
63
|
}),
|
|
42
64
|
]
|
|
43
65
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
abandonChange: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
52
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
53
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
54
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
55
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
56
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
57
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
58
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
59
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
60
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
61
|
-
})
|
|
66
|
+
server.use(
|
|
67
|
+
http.get('*/a/changes/', ({ request }) => {
|
|
68
|
+
const url = new URL(request.url)
|
|
69
|
+
expect(url.searchParams.get('q')).toBe('owner:self status:open')
|
|
70
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChanges)}`)
|
|
71
|
+
}),
|
|
72
|
+
)
|
|
62
73
|
|
|
74
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
63
75
|
await Effect.runPromise(
|
|
64
|
-
mineCommand({ xml: false }).pipe(
|
|
76
|
+
mineCommand({ xml: false }).pipe(
|
|
77
|
+
Effect.provide(GerritApiServiceLive),
|
|
78
|
+
Effect.provide(mockConfigLayer),
|
|
79
|
+
),
|
|
65
80
|
)
|
|
66
81
|
|
|
67
|
-
|
|
68
|
-
expect(
|
|
69
|
-
expect(
|
|
82
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
83
|
+
expect(output.length).toBeGreaterThan(0)
|
|
84
|
+
expect(output).toContain('My test change')
|
|
85
|
+
expect(output).toContain('Another change')
|
|
70
86
|
})
|
|
71
87
|
|
|
72
88
|
test('should output XML format when --xml flag is used', async () => {
|
|
@@ -80,30 +96,23 @@ describe('mine command', () => {
|
|
|
80
96
|
}),
|
|
81
97
|
]
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
abandonChange: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
91
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
92
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
93
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
94
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
95
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
96
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
97
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
98
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
99
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
100
|
-
})
|
|
99
|
+
server.use(
|
|
100
|
+
http.get('*/a/changes/', ({ request }) => {
|
|
101
|
+
const url = new URL(request.url)
|
|
102
|
+
expect(url.searchParams.get('q')).toBe('owner:self status:open')
|
|
103
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChanges)}`)
|
|
104
|
+
}),
|
|
105
|
+
)
|
|
101
106
|
|
|
107
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
102
108
|
await Effect.runPromise(
|
|
103
|
-
mineCommand({ xml: true }).pipe(
|
|
109
|
+
mineCommand({ xml: true }).pipe(
|
|
110
|
+
Effect.provide(GerritApiServiceLive),
|
|
111
|
+
Effect.provide(mockConfigLayer),
|
|
112
|
+
),
|
|
104
113
|
)
|
|
105
114
|
|
|
106
|
-
const output =
|
|
115
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
107
116
|
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
108
117
|
expect(output).toContain('<changes count="1">')
|
|
109
118
|
expect(output).toContain('<change>')
|
|
@@ -117,113 +126,83 @@ describe('mine command', () => {
|
|
|
117
126
|
})
|
|
118
127
|
|
|
119
128
|
test('should handle no changes gracefully', async () => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
126
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
127
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
128
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
129
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
130
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
131
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
132
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
133
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
134
|
-
})
|
|
129
|
+
server.use(
|
|
130
|
+
http.get('*/a/changes/', () => {
|
|
131
|
+
return HttpResponse.text(")]}'\n[]")
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
135
134
|
|
|
135
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
136
136
|
await Effect.runPromise(
|
|
137
|
-
mineCommand({ xml: false }).pipe(
|
|
137
|
+
mineCommand({ xml: false }).pipe(
|
|
138
|
+
Effect.provide(GerritApiServiceLive),
|
|
139
|
+
Effect.provide(mockConfigLayer),
|
|
140
|
+
),
|
|
138
141
|
)
|
|
139
142
|
|
|
140
143
|
// Mine command returns early for empty results, so no output is expected
|
|
141
|
-
expect(
|
|
144
|
+
expect(mockConsoleLog.mock.calls).toEqual([])
|
|
142
145
|
})
|
|
143
146
|
|
|
144
147
|
test('should handle no changes gracefully in XML format', async () => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
151
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
152
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
153
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
154
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
155
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
156
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
157
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
158
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
159
|
-
})
|
|
148
|
+
server.use(
|
|
149
|
+
http.get('*/a/changes/', () => {
|
|
150
|
+
return HttpResponse.text(")]}'\n[]")
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
160
153
|
|
|
154
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
161
155
|
await Effect.runPromise(
|
|
162
|
-
mineCommand({ xml: true }).pipe(
|
|
156
|
+
mineCommand({ xml: true }).pipe(
|
|
157
|
+
Effect.provide(GerritApiServiceLive),
|
|
158
|
+
Effect.provide(mockConfigLayer),
|
|
159
|
+
),
|
|
163
160
|
)
|
|
164
161
|
|
|
165
|
-
const output =
|
|
162
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
166
163
|
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
167
164
|
expect(output).toContain('<changes count="0">')
|
|
168
165
|
expect(output).toContain('</changes>')
|
|
169
166
|
})
|
|
170
167
|
|
|
171
168
|
test('should handle network failures gracefully', async () => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
178
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
179
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
180
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
181
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
182
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
183
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
184
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
185
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
186
|
-
})
|
|
169
|
+
server.use(
|
|
170
|
+
http.get('*/a/changes/', () => {
|
|
171
|
+
return HttpResponse.text('Network error', { status: 500 })
|
|
172
|
+
}),
|
|
173
|
+
)
|
|
187
174
|
|
|
175
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
188
176
|
const result = await Effect.runPromise(
|
|
189
177
|
Effect.either(
|
|
190
|
-
mineCommand({ xml: false }).pipe(
|
|
178
|
+
mineCommand({ xml: false }).pipe(
|
|
179
|
+
Effect.provide(GerritApiServiceLive),
|
|
180
|
+
Effect.provide(mockConfigLayer),
|
|
181
|
+
),
|
|
191
182
|
),
|
|
192
183
|
)
|
|
193
184
|
|
|
194
185
|
expect(result._tag).toBe('Left')
|
|
195
|
-
if (result._tag === 'Left') {
|
|
196
|
-
expect(result.left.message).toBe('Network error')
|
|
197
|
-
}
|
|
198
186
|
})
|
|
199
187
|
|
|
200
188
|
test('should handle network failures gracefully in XML format', async () => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
207
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
208
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
209
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
210
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
211
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
212
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
213
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
214
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
215
|
-
})
|
|
189
|
+
server.use(
|
|
190
|
+
http.get('*/a/changes/', () => {
|
|
191
|
+
return HttpResponse.text('API error', { status: 500 })
|
|
192
|
+
}),
|
|
193
|
+
)
|
|
216
194
|
|
|
195
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
217
196
|
const result = await Effect.runPromise(
|
|
218
197
|
Effect.either(
|
|
219
|
-
mineCommand({ xml: true }).pipe(
|
|
198
|
+
mineCommand({ xml: true }).pipe(
|
|
199
|
+
Effect.provide(GerritApiServiceLive),
|
|
200
|
+
Effect.provide(mockConfigLayer),
|
|
201
|
+
),
|
|
220
202
|
),
|
|
221
203
|
)
|
|
222
204
|
|
|
223
205
|
expect(result._tag).toBe('Left')
|
|
224
|
-
if (result._tag === 'Left') {
|
|
225
|
-
expect(result.left.message).toBe('API error')
|
|
226
|
-
}
|
|
227
206
|
})
|
|
228
207
|
|
|
229
208
|
test('should properly escape XML special characters', async () => {
|
|
@@ -237,27 +216,21 @@ describe('mine command', () => {
|
|
|
237
216
|
}),
|
|
238
217
|
]
|
|
239
218
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
246
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
247
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
248
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
249
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
250
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
251
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
252
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
253
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
254
|
-
})
|
|
219
|
+
server.use(
|
|
220
|
+
http.get('*/a/changes/', () => {
|
|
221
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChanges)}`)
|
|
222
|
+
}),
|
|
223
|
+
)
|
|
255
224
|
|
|
225
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
256
226
|
await Effect.runPromise(
|
|
257
|
-
mineCommand({ xml: true }).pipe(
|
|
227
|
+
mineCommand({ xml: true }).pipe(
|
|
228
|
+
Effect.provide(GerritApiServiceLive),
|
|
229
|
+
Effect.provide(mockConfigLayer),
|
|
230
|
+
),
|
|
258
231
|
)
|
|
259
232
|
|
|
260
|
-
const output =
|
|
233
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
261
234
|
// CDATA sections should preserve special characters
|
|
262
235
|
expect(output).toContain('<![CDATA[Test with <special> & "characters"]]>')
|
|
263
236
|
expect(output).toContain('<branch>feature/test&update</branch>')
|
|
@@ -288,27 +261,21 @@ describe('mine command', () => {
|
|
|
288
261
|
}),
|
|
289
262
|
]
|
|
290
263
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
testConnection: Effect.fail(new Error('Not implemented') as ApiError),
|
|
297
|
-
getRevision: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
298
|
-
getFiles: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
299
|
-
getFileDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
300
|
-
getFileContent: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
301
|
-
getPatch: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
302
|
-
getDiff: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
303
|
-
getComments: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
304
|
-
getMessages: () => Effect.fail(new Error('Not implemented') as ApiError),
|
|
305
|
-
})
|
|
264
|
+
server.use(
|
|
265
|
+
http.get('*/a/changes/', () => {
|
|
266
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChanges)}`)
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
306
269
|
|
|
270
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
307
271
|
await Effect.runPromise(
|
|
308
|
-
mineCommand({ xml: false }).pipe(
|
|
272
|
+
mineCommand({ xml: false }).pipe(
|
|
273
|
+
Effect.provide(GerritApiServiceLive),
|
|
274
|
+
Effect.provide(mockConfigLayer),
|
|
275
|
+
),
|
|
309
276
|
)
|
|
310
277
|
|
|
311
|
-
const output =
|
|
278
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
312
279
|
expect(output).toContain('Change in project A')
|
|
313
280
|
expect(output).toContain('Change in project B')
|
|
314
281
|
expect(output).toContain('Another change in project A')
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { mock } from 'bun:test'
|
|
2
|
-
import { Schema } from '@effect/schema'
|
|
3
|
-
import { ChangeInfo } from '@/schemas/gerrit'
|
|
4
|
-
import {
|
|
5
|
-
generateMockAccount,
|
|
6
|
-
generateMockChange,
|
|
7
|
-
generateMockFileDiff,
|
|
8
|
-
generateMockFiles,
|
|
9
|
-
} from '@/test-utils/mock-generator'
|
|
10
|
-
|
|
11
|
-
// Generate consistent mock data using Effect Schema
|
|
12
|
-
const mockChange = generateMockChange()
|
|
13
|
-
const mockFiles = generateMockFiles()
|
|
14
|
-
const mockDiff = generateMockFileDiff()
|
|
15
|
-
const mockAccount = generateMockAccount()
|
|
16
|
-
|
|
17
|
-
// Keep the old mockChange definition for now as backup (disabled to fix unused variable)
|
|
18
|
-
// const _mockChange: Schema.Schema.Type<typeof ChangeInfo> = {
|
|
19
|
-
// id: 'myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940',
|
|
20
|
-
// project: 'myProject',
|
|
21
|
-
// branch: 'master',
|
|
22
|
-
// change_id: 'I8473b95934b5732ac55d26311a706c9c2bde9940',
|
|
23
|
-
// subject: 'Implementing new feature',
|
|
24
|
-
// status: 'NEW',
|
|
25
|
-
// created: '2023-12-01 10:00:00.000000000',
|
|
26
|
-
// updated: '2023-12-01 12:00:00.000000000',
|
|
27
|
-
// _number: 123456,
|
|
28
|
-
// owner: {
|
|
29
|
-
// _account_id: 1000000,
|
|
30
|
-
// name: 'John Doe',
|
|
31
|
-
// email: 'john.doe@example.com',
|
|
32
|
-
// },
|
|
33
|
-
// labels: {},
|
|
34
|
-
// permitted_labels: {},
|
|
35
|
-
// removable_reviewers: [],
|
|
36
|
-
// reviewers: {},
|
|
37
|
-
// requirements: [],
|
|
38
|
-
// }
|
|
39
|
-
|
|
40
|
-
export const setupFetchMock = (): ((
|
|
41
|
-
url: string | URL,
|
|
42
|
-
options?: RequestInit,
|
|
43
|
-
) => Promise<Response>) => {
|
|
44
|
-
return mock(async (url: string | URL, options?: RequestInit): Promise<Response> => {
|
|
45
|
-
const urlStr = url.toString()
|
|
46
|
-
const method = options?.method || 'GET'
|
|
47
|
-
|
|
48
|
-
// Check authentication
|
|
49
|
-
const authHeader =
|
|
50
|
-
options?.headers && 'Authorization' in options.headers
|
|
51
|
-
? (options.headers as Record<string, string>).Authorization
|
|
52
|
-
: undefined
|
|
53
|
-
|
|
54
|
-
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
55
|
-
return new Response(`)]}'\n${JSON.stringify({ message: 'Unauthorized' })}`, { status: 401 })
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Authentication endpoint
|
|
59
|
-
if (urlStr.includes('/a/accounts/self')) {
|
|
60
|
-
return new Response(`)]}'\n${JSON.stringify(mockAccount)}`, { status: 200 })
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// List changes endpoint (must come before get change endpoint)
|
|
64
|
-
if (urlStr.includes('/a/changes/?q=')) {
|
|
65
|
-
return new Response(`)]}'\n${JSON.stringify([mockChange])}`, { status: 200 })
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Get change endpoint
|
|
69
|
-
if (
|
|
70
|
-
urlStr.includes('/a/changes/') &&
|
|
71
|
-
method === 'GET' &&
|
|
72
|
-
!urlStr.includes('/files') &&
|
|
73
|
-
!urlStr.includes('/diff') &&
|
|
74
|
-
!urlStr.includes('/patch') &&
|
|
75
|
-
!urlStr.includes('/review')
|
|
76
|
-
) {
|
|
77
|
-
if (urlStr.includes('notfound')) {
|
|
78
|
-
return new Response(`)]}'\n${JSON.stringify({ message: 'Not found' })}`, { status: 404 })
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Validate response against schema
|
|
82
|
-
const validated = Schema.decodeUnknownSync(ChangeInfo)(mockChange)
|
|
83
|
-
return new Response(`)]}'\n${JSON.stringify(validated)}`, { status: 200 })
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Get file diff endpoint - must be checked BEFORE other file endpoints
|
|
87
|
-
if (urlStr.includes('/files/') && urlStr.includes('/diff') && method === 'GET') {
|
|
88
|
-
return new Response(`)]}'\n${JSON.stringify(mockDiff)}`, { status: 200 })
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Get files endpoint (list of files)
|
|
92
|
-
if (
|
|
93
|
-
urlStr.includes('/files') &&
|
|
94
|
-
method === 'GET' &&
|
|
95
|
-
!urlStr.includes('/diff') &&
|
|
96
|
-
!urlStr.includes('/content')
|
|
97
|
-
) {
|
|
98
|
-
return new Response(`)]}'\n${JSON.stringify(mockFiles)}`, { status: 200 })
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Get file content endpoint
|
|
102
|
-
if (urlStr.includes('/content') && method === 'GET' && !urlStr.includes('/diff')) {
|
|
103
|
-
const content =
|
|
104
|
-
'function main() {\n console.log("Hello, world!")\n return process.exit(0)\n}'
|
|
105
|
-
const base64Content = btoa(content)
|
|
106
|
-
return new Response(base64Content, { status: 200 })
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Get patch endpoint
|
|
110
|
-
if (urlStr.includes('/patch') && method === 'GET') {
|
|
111
|
-
const patch = `--- a/src/main.ts
|
|
112
|
-
+++ b/src/main.ts
|
|
113
|
-
@@ -1,3 +1,3 @@
|
|
114
|
-
function main() {
|
|
115
|
-
console.log("Hello, world!")
|
|
116
|
-
- return 0
|
|
117
|
-
+ return process.exit(0)
|
|
118
|
-
}`
|
|
119
|
-
const base64Patch = btoa(patch)
|
|
120
|
-
return new Response(base64Patch, { status: 200 })
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Post review endpoint
|
|
124
|
-
if (urlStr.includes('/review') && method === 'POST') {
|
|
125
|
-
return new Response(
|
|
126
|
-
")]}'\n" +
|
|
127
|
-
JSON.stringify({
|
|
128
|
-
labels: {},
|
|
129
|
-
ready: true,
|
|
130
|
-
}),
|
|
131
|
-
{ status: 200 },
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Default 404 for unhandled requests
|
|
136
|
-
return new Response(`)]}'\n${JSON.stringify({ message: 'Not found' })}`, { status: 404 })
|
|
137
|
-
})
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export const restoreFetch: () => void = () => {
|
|
141
|
-
// Restore original fetch (Bun handles this automatically after tests)
|
|
142
|
-
}
|
package/tests/setup.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeAll } from 'bun:test'
|
|
2
|
-
import { setupFetchMock } from './mocks/fetch-mock'
|
|
3
|
-
|
|
4
|
-
// Setup Bun's native fetch mocking before all tests
|
|
5
|
-
beforeAll(() => {
|
|
6
|
-
setupFetchMock()
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
// Clean up after each test (Bun automatically restores mocks)
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
// Bun automatically handles mock cleanup
|
|
12
|
-
// But we can reset if needed
|
|
13
|
-
})
|