@aaronshaf/ger 1.2.11 → 2.0.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/.ast-grep/rules/no-as-casting.yml +13 -0
- package/.claude-plugin/plugin.json +22 -0
- package/.github/workflows/ci-simple.yml +53 -0
- package/.github/workflows/ci.yml +171 -0
- package/.github/workflows/claude-code-review.yml +83 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/dependency-update.yml +84 -0
- package/.github/workflows/release.yml +166 -0
- package/.github/workflows/security-scan.yml +113 -0
- package/.github/workflows/security.yml +96 -0
- package/.husky/pre-commit +16 -0
- package/.husky/pre-push +25 -0
- package/.lintstagedrc.json +6 -0
- package/.tool-versions +1 -0
- package/CLAUDE.md +105 -0
- package/DEVELOPMENT.md +361 -0
- package/EXAMPLES.md +457 -0
- package/README.md +831 -16
- package/bin/ger +3 -18
- package/biome.json +36 -0
- package/bun.lock +678 -0
- package/bunfig.toml +8 -0
- package/docs/adr/0001-use-effect-for-side-effects.md +65 -0
- package/docs/adr/0002-use-bun-runtime.md +64 -0
- package/docs/adr/0003-store-credentials-in-home-directory.md +75 -0
- package/docs/adr/0004-use-commander-for-cli.md +76 -0
- package/docs/adr/0005-use-effect-schema-for-validation.md +93 -0
- package/docs/adr/0006-use-msw-for-api-mocking.md +89 -0
- package/docs/adr/0007-git-hooks-for-quality.md +94 -0
- package/docs/adr/0008-no-as-typecasting.md +83 -0
- package/docs/adr/0009-file-size-limits.md +82 -0
- package/docs/adr/0010-llm-friendly-xml-output.md +93 -0
- package/docs/adr/0011-ai-tool-strategy-pattern.md +102 -0
- package/docs/adr/0012-build-status-message-parsing.md +94 -0
- package/docs/adr/0013-git-subprocess-integration.md +98 -0
- package/docs/adr/0014-group-management-support.md +95 -0
- package/docs/adr/0015-batch-comment-processing.md +111 -0
- package/docs/adr/0016-flexible-change-identifiers.md +94 -0
- package/docs/adr/0017-git-worktree-support.md +102 -0
- package/docs/adr/0018-auto-install-commit-hook.md +103 -0
- package/docs/adr/0019-sdk-package-exports.md +95 -0
- package/docs/adr/0020-code-coverage-enforcement.md +105 -0
- package/docs/adr/0021-typescript-isolated-declarations.md +83 -0
- package/docs/adr/0022-biome-oxlint-tooling.md +124 -0
- package/docs/adr/README.md +30 -0
- package/docs/prd/README.md +12 -0
- package/docs/prd/architecture.md +325 -0
- package/docs/prd/commands.md +433 -0
- package/docs/prd/data-model.md +349 -0
- package/docs/prd/overview.md +124 -0
- package/index.ts +219 -0
- package/oxlint.json +24 -0
- package/package.json +82 -15
- package/scripts/check-coverage.ts +69 -0
- package/scripts/check-file-size.ts +38 -0
- package/scripts/fix-test-mocks.ts +55 -0
- package/skills/gerrit-workflow/SKILL.md +247 -0
- package/skills/gerrit-workflow/examples.md +572 -0
- package/skills/gerrit-workflow/reference.md +728 -0
- package/src/api/gerrit.ts +696 -0
- package/src/cli/commands/abandon.ts +65 -0
- package/src/cli/commands/add-reviewer.ts +156 -0
- package/src/cli/commands/build-status.ts +282 -0
- package/src/cli/commands/checkout.ts +422 -0
- package/src/cli/commands/comment.ts +460 -0
- package/src/cli/commands/comments.ts +85 -0
- package/src/cli/commands/diff.ts +71 -0
- package/src/cli/commands/extract-url.ts +266 -0
- package/src/cli/commands/groups-members.ts +104 -0
- package/src/cli/commands/groups-show.ts +169 -0
- package/src/cli/commands/groups.ts +137 -0
- package/src/cli/commands/incoming.ts +226 -0
- package/src/cli/commands/init.ts +164 -0
- package/src/cli/commands/mine.ts +115 -0
- package/src/cli/commands/open.ts +57 -0
- package/src/cli/commands/projects.ts +68 -0
- package/src/cli/commands/push.ts +430 -0
- package/src/cli/commands/rebase.ts +71 -0
- package/src/cli/commands/remove-reviewer.ts +123 -0
- package/src/cli/commands/restore.ts +50 -0
- package/src/cli/commands/review.ts +486 -0
- package/src/cli/commands/search.ts +162 -0
- package/src/cli/commands/setup.ts +286 -0
- package/src/cli/commands/show.ts +491 -0
- package/src/cli/commands/status.ts +35 -0
- package/src/cli/commands/submit.ts +108 -0
- package/src/cli/commands/vote.ts +119 -0
- package/src/cli/commands/workspace.ts +200 -0
- package/src/cli/index.ts +53 -0
- package/src/cli/register-commands.ts +659 -0
- package/src/cli/register-group-commands.ts +88 -0
- package/src/cli/register-reviewer-commands.ts +97 -0
- package/src/prompts/default-review.md +86 -0
- package/src/prompts/system-inline-review.md +135 -0
- package/src/prompts/system-overall-review.md +206 -0
- package/src/schemas/config.test.ts +245 -0
- package/src/schemas/config.ts +84 -0
- package/src/schemas/gerrit.ts +681 -0
- package/src/services/commit-hook.ts +314 -0
- package/src/services/config.test.ts +150 -0
- package/src/services/config.ts +250 -0
- package/src/services/git-worktree.ts +342 -0
- package/src/services/review-strategy.ts +292 -0
- package/src/test-utils/mock-generator.ts +138 -0
- package/src/utils/change-id.test.ts +98 -0
- package/src/utils/change-id.ts +63 -0
- package/src/utils/comment-formatters.ts +153 -0
- package/src/utils/diff-context.ts +103 -0
- package/src/utils/diff-formatters.ts +141 -0
- package/src/utils/formatters.ts +85 -0
- package/src/utils/git-commit.test.ts +277 -0
- package/src/utils/git-commit.ts +122 -0
- package/src/utils/index.ts +55 -0
- package/src/utils/message-filters.ts +26 -0
- package/src/utils/review-formatters.ts +89 -0
- package/src/utils/review-prompt-builder.ts +110 -0
- package/src/utils/shell-safety.ts +117 -0
- package/src/utils/status-indicators.ts +100 -0
- package/src/utils/url-parser.test.ts +271 -0
- package/src/utils/url-parser.ts +118 -0
- package/tests/abandon.test.ts +230 -0
- package/tests/add-reviewer.test.ts +579 -0
- package/tests/build-status-watch.test.ts +344 -0
- package/tests/build-status.test.ts +789 -0
- package/tests/change-id-formats.test.ts +268 -0
- package/tests/checkout/integration.test.ts +653 -0
- package/tests/checkout/parse-input.test.ts +55 -0
- package/tests/checkout/validation.test.ts +178 -0
- package/tests/comment-batch-advanced.test.ts +431 -0
- package/tests/comment-gerrit-api-compliance.test.ts +414 -0
- package/tests/comment.test.ts +708 -0
- package/tests/comments.test.ts +323 -0
- package/tests/config-service-simple.test.ts +100 -0
- package/tests/diff.test.ts +419 -0
- package/tests/extract-url.test.ts +517 -0
- package/tests/groups-members.test.ts +256 -0
- package/tests/groups-show.test.ts +323 -0
- package/tests/groups.test.ts +334 -0
- package/tests/helpers/build-status-test-setup.ts +83 -0
- package/tests/helpers/config-mock.ts +27 -0
- package/tests/incoming.test.ts +357 -0
- package/tests/init.test.ts +70 -0
- package/tests/integration/commit-hook.test.ts +246 -0
- package/tests/interactive-incoming.test.ts +173 -0
- package/tests/mine.test.ts +285 -0
- package/tests/mocks/msw-handlers.ts +80 -0
- package/tests/open.test.ts +233 -0
- package/tests/projects.test.ts +259 -0
- package/tests/rebase.test.ts +368 -0
- package/tests/remove-reviewer.test.ts +357 -0
- package/tests/restore.test.ts +237 -0
- package/tests/review.test.ts +135 -0
- package/tests/search.test.ts +712 -0
- package/tests/setup.test.ts +63 -0
- package/tests/show-auto-detect.test.ts +324 -0
- package/tests/show.test.ts +813 -0
- package/tests/status.test.ts +145 -0
- package/tests/submit.test.ts +316 -0
- package/tests/unit/commands/push.test.ts +194 -0
- package/tests/unit/git-branch-detection.test.ts +82 -0
- package/tests/unit/git-worktree.test.ts +55 -0
- package/tests/unit/patterns/push-patterns.test.ts +148 -0
- package/tests/unit/schemas/gerrit.test.ts +85 -0
- package/tests/unit/services/commit-hook.test.ts +132 -0
- package/tests/unit/services/review-strategy.test.ts +349 -0
- package/tests/unit/test-utils/mock-generator.test.ts +154 -0
- package/tests/unit/utils/comment-formatters.test.ts +415 -0
- package/tests/unit/utils/diff-context.test.ts +171 -0
- package/tests/unit/utils/diff-formatters.test.ts +165 -0
- package/tests/unit/utils/formatters.test.ts +411 -0
- package/tests/unit/utils/message-filters.test.ts +227 -0
- package/tests/unit/utils/shell-safety.test.ts +230 -0
- package/tests/unit/utils/status-indicators.test.ts +137 -0
- package/tests/vote.test.ts +317 -0
- package/tests/workspace.test.ts +295 -0
- package/tsconfig.json +36 -5
- package/src/commands/branch.ts +0 -196
- package/src/ger.ts +0 -22
- package/src/types.d.ts +0 -35
- package/src/utils.ts +0 -130
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock } from 'bun:test'
|
|
2
|
+
import { Effect, Layer } from 'effect'
|
|
3
|
+
import { HttpResponse, http } from 'msw'
|
|
4
|
+
import { setupServer } from 'msw/node'
|
|
5
|
+
import { GerritApiServiceLive } from '@/api/gerrit'
|
|
6
|
+
import { rebaseCommand } from '@/cli/commands/rebase'
|
|
7
|
+
import { ConfigService } from '@/services/config'
|
|
8
|
+
import { createMockConfigService } from './helpers/config-mock'
|
|
9
|
+
import type { ChangeInfo } from '@/schemas/gerrit'
|
|
10
|
+
|
|
11
|
+
const mockChange: ChangeInfo = {
|
|
12
|
+
id: 'test-project~master~I123',
|
|
13
|
+
_number: 12345,
|
|
14
|
+
change_id: 'I123',
|
|
15
|
+
project: 'test-project',
|
|
16
|
+
branch: 'master',
|
|
17
|
+
subject: 'Test change to rebase',
|
|
18
|
+
status: 'NEW',
|
|
19
|
+
created: '2024-01-01 10:00:00.000000000',
|
|
20
|
+
updated: '2024-01-01 12:00:00.000000000',
|
|
21
|
+
owner: {
|
|
22
|
+
_account_id: 1000,
|
|
23
|
+
name: 'Test User',
|
|
24
|
+
email: 'test@example.com',
|
|
25
|
+
},
|
|
26
|
+
labels: {
|
|
27
|
+
'Code-Review': {
|
|
28
|
+
value: 0,
|
|
29
|
+
},
|
|
30
|
+
Verified: {
|
|
31
|
+
value: 0,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
work_in_progress: false,
|
|
35
|
+
submittable: false,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create MSW server
|
|
39
|
+
const server = setupServer(
|
|
40
|
+
// Default handler for auth check
|
|
41
|
+
http.get('*/a/accounts/self', ({ request }) => {
|
|
42
|
+
const auth = request.headers.get('Authorization')
|
|
43
|
+
if (!auth || !auth.startsWith('Basic ')) {
|
|
44
|
+
return HttpResponse.text('Unauthorized', { status: 401 })
|
|
45
|
+
}
|
|
46
|
+
return HttpResponse.json({
|
|
47
|
+
_account_id: 1000,
|
|
48
|
+
name: 'Test User',
|
|
49
|
+
email: 'test@example.com',
|
|
50
|
+
})
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
describe('rebase command', () => {
|
|
55
|
+
let mockConsoleLog: ReturnType<typeof mock>
|
|
56
|
+
let mockConsoleError: ReturnType<typeof mock>
|
|
57
|
+
|
|
58
|
+
beforeAll(() => {
|
|
59
|
+
server.listen({ onUnhandledRequest: 'bypass' })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
server.close()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
mockConsoleLog = mock(() => {})
|
|
68
|
+
mockConsoleError = mock(() => {})
|
|
69
|
+
console.log = mockConsoleLog
|
|
70
|
+
console.error = mockConsoleError
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
server.resetHandlers()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should rebase a change without a base', async () => {
|
|
78
|
+
server.use(
|
|
79
|
+
http.post('*/a/changes/12345/revisions/current/rebase', async ({ request }) => {
|
|
80
|
+
const body = (await request.json()) as { base?: string }
|
|
81
|
+
expect(body.base).toBeUndefined()
|
|
82
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
87
|
+
const program = rebaseCommand('12345', {}).pipe(
|
|
88
|
+
Effect.provide(GerritApiServiceLive),
|
|
89
|
+
Effect.provide(mockConfigLayer),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
await Effect.runPromise(program)
|
|
93
|
+
|
|
94
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
95
|
+
expect(output).toContain('Rebased change 12345')
|
|
96
|
+
expect(output).toContain('Test change to rebase')
|
|
97
|
+
expect(output).toContain('Branch: master')
|
|
98
|
+
expect(output).not.toContain('Base:')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should rebase a change with a specified base', async () => {
|
|
102
|
+
server.use(
|
|
103
|
+
http.post('*/a/changes/12345/revisions/current/rebase', async ({ request }) => {
|
|
104
|
+
const body = (await request.json()) as { base?: string }
|
|
105
|
+
expect(body.base).toBe('refs/heads/main')
|
|
106
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
111
|
+
const program = rebaseCommand('12345', {
|
|
112
|
+
base: 'refs/heads/main',
|
|
113
|
+
}).pipe(Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer))
|
|
114
|
+
|
|
115
|
+
await Effect.runPromise(program)
|
|
116
|
+
|
|
117
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
118
|
+
expect(output).toContain('Rebased change 12345')
|
|
119
|
+
expect(output).toContain('Test change to rebase')
|
|
120
|
+
expect(output).toContain('Branch: master')
|
|
121
|
+
expect(output).toContain('Base: refs/heads/main')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('should output XML format when --xml flag is used', async () => {
|
|
125
|
+
server.use(
|
|
126
|
+
http.post('*/a/changes/12345/revisions/current/rebase', async () => {
|
|
127
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
132
|
+
const program = rebaseCommand('12345', { xml: true }).pipe(
|
|
133
|
+
Effect.provide(GerritApiServiceLive),
|
|
134
|
+
Effect.provide(mockConfigLayer),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
await Effect.runPromise(program)
|
|
138
|
+
|
|
139
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
140
|
+
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
141
|
+
expect(output).toContain('<rebase_result>')
|
|
142
|
+
expect(output).toContain('<status>success</status>')
|
|
143
|
+
expect(output).toContain('<change_number>12345</change_number>')
|
|
144
|
+
expect(output).toContain('<subject><![CDATA[Test change to rebase]]></subject>')
|
|
145
|
+
expect(output).toContain('<branch>master</branch>')
|
|
146
|
+
expect(output).toContain('</rebase_result>')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should output XML format with base when --base is provided', async () => {
|
|
150
|
+
server.use(
|
|
151
|
+
http.post('*/a/changes/12345/revisions/current/rebase', async ({ request }) => {
|
|
152
|
+
const body = (await request.json()) as { base?: string }
|
|
153
|
+
expect(body.base).toBe('refs/heads/develop')
|
|
154
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
155
|
+
}),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
159
|
+
const program = rebaseCommand('12345', {
|
|
160
|
+
xml: true,
|
|
161
|
+
base: 'refs/heads/develop',
|
|
162
|
+
}).pipe(Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer))
|
|
163
|
+
|
|
164
|
+
await Effect.runPromise(program)
|
|
165
|
+
|
|
166
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
167
|
+
expect(output).toContain('<rebase_result>')
|
|
168
|
+
expect(output).toContain('<status>success</status>')
|
|
169
|
+
expect(output).toContain('<base><![CDATA[refs/heads/develop]]></base>')
|
|
170
|
+
expect(output).toContain('</rebase_result>')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should handle not found errors gracefully with pretty output', async () => {
|
|
174
|
+
server.use(
|
|
175
|
+
http.post('*/a/changes/99999/revisions/current/rebase', () => {
|
|
176
|
+
return HttpResponse.text('Change not found', { status: 404 })
|
|
177
|
+
}),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
181
|
+
const program = rebaseCommand('99999', {}).pipe(
|
|
182
|
+
Effect.provide(GerritApiServiceLive),
|
|
183
|
+
Effect.provide(mockConfigLayer),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// Error boundary catches and outputs to console.error
|
|
187
|
+
await Effect.runPromise(program)
|
|
188
|
+
|
|
189
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
190
|
+
expect(errorOutput).toContain('Error:')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should handle not found errors with XML output when --xml flag is used', async () => {
|
|
194
|
+
server.use(
|
|
195
|
+
http.post('*/a/changes/99999/revisions/current/rebase', () => {
|
|
196
|
+
return HttpResponse.text('Change not found', { status: 404 })
|
|
197
|
+
}),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
201
|
+
const program = rebaseCommand('99999', { xml: true }).pipe(
|
|
202
|
+
Effect.provide(GerritApiServiceLive),
|
|
203
|
+
Effect.provide(mockConfigLayer),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
// Error boundary catches and outputs XML error
|
|
207
|
+
await Effect.runPromise(program)
|
|
208
|
+
|
|
209
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
210
|
+
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
211
|
+
expect(output).toContain('<rebase_result>')
|
|
212
|
+
expect(output).toContain('<status>error</status>')
|
|
213
|
+
expect(output).toContain('<error><![CDATA[')
|
|
214
|
+
expect(output).toContain('</rebase_result>')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should output error to console.error when no change ID and HEAD has no Change-Id', async () => {
|
|
218
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
219
|
+
const program = rebaseCommand(undefined, {}).pipe(
|
|
220
|
+
Effect.provide(GerritApiServiceLive),
|
|
221
|
+
Effect.provide(mockConfigLayer),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
// Error boundary catches NoChangeIdError and outputs to console.error
|
|
225
|
+
await Effect.runPromise(program)
|
|
226
|
+
|
|
227
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
228
|
+
expect(errorOutput).toContain('Error:')
|
|
229
|
+
expect(errorOutput).toContain('No Change-ID found in HEAD commit')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should output XML error when no change ID and HEAD has no Change-Id with --xml flag', async () => {
|
|
233
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
234
|
+
const program = rebaseCommand(undefined, { xml: true }).pipe(
|
|
235
|
+
Effect.provide(GerritApiServiceLive),
|
|
236
|
+
Effect.provide(mockConfigLayer),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
// Error boundary catches NoChangeIdError and outputs XML error
|
|
240
|
+
await Effect.runPromise(program)
|
|
241
|
+
|
|
242
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
243
|
+
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
244
|
+
expect(output).toContain('<rebase_result>')
|
|
245
|
+
expect(output).toContain('<status>error</status>')
|
|
246
|
+
expect(output).toContain('No Change-ID found in HEAD commit')
|
|
247
|
+
expect(output).toContain('</rebase_result>')
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should treat empty string as missing change ID and auto-detect', async () => {
|
|
251
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
252
|
+
const program = rebaseCommand('', {}).pipe(
|
|
253
|
+
Effect.provide(GerritApiServiceLive),
|
|
254
|
+
Effect.provide(mockConfigLayer),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
// Empty string triggers auto-detection, which fails with NoChangeIdError
|
|
258
|
+
await Effect.runPromise(program)
|
|
259
|
+
|
|
260
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
261
|
+
expect(errorOutput).toContain('Error:')
|
|
262
|
+
expect(errorOutput).toContain('No Change-ID found in HEAD commit')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should handle rebase conflicts gracefully', async () => {
|
|
266
|
+
server.use(
|
|
267
|
+
http.post('*/a/changes/12345/revisions/current/rebase', () => {
|
|
268
|
+
return HttpResponse.text('Rebase conflict detected', { status: 409 })
|
|
269
|
+
}),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
273
|
+
const program = rebaseCommand('12345', {}).pipe(
|
|
274
|
+
Effect.provide(GerritApiServiceLive),
|
|
275
|
+
Effect.provide(mockConfigLayer),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
// Error boundary catches and outputs to console.error
|
|
279
|
+
await Effect.runPromise(program)
|
|
280
|
+
|
|
281
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
282
|
+
expect(errorOutput).toContain('Error:')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should handle API errors gracefully', async () => {
|
|
286
|
+
server.use(
|
|
287
|
+
http.post('*/a/changes/12345/revisions/current/rebase', () => {
|
|
288
|
+
return HttpResponse.text('Forbidden', { status: 403 })
|
|
289
|
+
}),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
293
|
+
const program = rebaseCommand('12345', {}).pipe(
|
|
294
|
+
Effect.provide(GerritApiServiceLive),
|
|
295
|
+
Effect.provide(mockConfigLayer),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
// Error boundary catches and outputs to console.error
|
|
299
|
+
await Effect.runPromise(program)
|
|
300
|
+
|
|
301
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
302
|
+
expect(errorOutput).toContain('Error:')
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('should handle changes that are already up to date', async () => {
|
|
306
|
+
server.use(
|
|
307
|
+
http.post('*/a/changes/12345/revisions/current/rebase', () => {
|
|
308
|
+
return HttpResponse.text('Change is already up to date', { status: 409 })
|
|
309
|
+
}),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
313
|
+
const program = rebaseCommand('12345', {}).pipe(
|
|
314
|
+
Effect.provide(GerritApiServiceLive),
|
|
315
|
+
Effect.provide(mockConfigLayer),
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
// Error boundary catches and outputs to console.error
|
|
319
|
+
await Effect.runPromise(program)
|
|
320
|
+
|
|
321
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
322
|
+
expect(errorOutput).toContain('Error:')
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('should handle network errors gracefully', async () => {
|
|
326
|
+
server.use(
|
|
327
|
+
http.post('*/a/changes/12345/revisions/current/rebase', () => {
|
|
328
|
+
return HttpResponse.error()
|
|
329
|
+
}),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
333
|
+
const program = rebaseCommand('12345', {}).pipe(
|
|
334
|
+
Effect.provide(GerritApiServiceLive),
|
|
335
|
+
Effect.provide(mockConfigLayer),
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
// Error boundary catches and outputs to console.error
|
|
339
|
+
await Effect.runPromise(program)
|
|
340
|
+
|
|
341
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
342
|
+
expect(errorOutput).toContain('Error:')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('should handle network errors with XML output', async () => {
|
|
346
|
+
server.use(
|
|
347
|
+
http.post('*/a/changes/12345/revisions/current/rebase', () => {
|
|
348
|
+
return HttpResponse.error()
|
|
349
|
+
}),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
353
|
+
const program = rebaseCommand('12345', { xml: true }).pipe(
|
|
354
|
+
Effect.provide(GerritApiServiceLive),
|
|
355
|
+
Effect.provide(mockConfigLayer),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
// Error boundary catches and outputs XML error
|
|
359
|
+
await Effect.runPromise(program)
|
|
360
|
+
|
|
361
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
362
|
+
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
363
|
+
expect(output).toContain('<rebase_result>')
|
|
364
|
+
expect(output).toContain('<status>error</status>')
|
|
365
|
+
expect(output).toContain('<error><![CDATA[')
|
|
366
|
+
expect(output).toContain('</rebase_result>')
|
|
367
|
+
})
|
|
368
|
+
})
|