Files
orca/src/main/github/project-view/mutations.test.ts
T
2026-06-23 15:18:46 -07:00

122 lines
3.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const { acquireMock, ghExecFileAsyncMock, releaseMock, runRestMock } = vi.hoisted(() => ({
acquireMock: vi.fn(),
ghExecFileAsyncMock: vi.fn(),
releaseMock: vi.fn(),
runRestMock: vi.fn()
}))
vi.mock('./internals', () => ({
acquire: acquireMock,
release: releaseMock,
extractExecError: (err: unknown) => ({
stderr: err instanceof Error ? err.message : String(err),
stdout: ''
}),
ghExecFileAsync: ghExecFileAsyncMock,
rateLimitGuard: () => ({ blocked: false }),
noteRateLimitSpend: vi.fn(),
classifyProjectError: (stderr: string) => ({ type: 'unknown', message: stderr }),
rateLimitedError: () => ({ type: 'rate_limited', message: 'rate limited' }),
runGraphql: vi.fn(),
runRest: runRestMock,
validateSlugArgs: (owner: string, repo: string) =>
owner && repo ? { ok: true } : { ok: false, error: { type: 'validation_error' } },
assertPositiveInt: (value: number, name: string) =>
Number.isInteger(value) && value > 0
? { ok: true, value }
: { ok: false, error: { type: 'validation_error', message: `${name} invalid` } }
}))
import { updateIssueBySlug } from './mutations'
describe('updateIssueBySlug', () => {
beforeEach(() => {
acquireMock.mockReset().mockResolvedValue(undefined)
ghExecFileAsyncMock.mockReset().mockResolvedValue({ stdout: '', stderr: '' })
releaseMock.mockReset()
runRestMock.mockReset().mockResolvedValue({ ok: true, data: {} })
})
it('closes slug-addressed issues with completed and not planned reasons via gh issue close', async () => {
await expect(
updateIssueBySlug({
owner: 'acme',
repo: 'widgets',
number: 12,
updates: { state: 'closed', stateReason: 'completed' }
})
).resolves.toEqual({ ok: true })
await expect(
updateIssueBySlug({
owner: 'acme',
repo: 'widgets',
number: 13,
updates: { state: 'closed', stateReason: 'not_planned' }
})
).resolves.toEqual({ ok: true })
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
['issue', 'close', '12', '--repo', 'acme/widgets', '--reason', 'completed'],
{ encoding: 'utf-8' }
)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
2,
['issue', 'close', '13', '--repo', 'acme/widgets', '--reason', 'not planned'],
{ encoding: 'utf-8' }
)
expect(runRestMock).not.toHaveBeenCalled()
})
it('closes slug-addressed duplicate issues with --duplicate-of', async () => {
await expect(
updateIssueBySlug({
owner: 'acme',
repo: 'widgets',
number: 12,
updates: { state: 'closed', stateReason: 'duplicate', duplicateOf: 99 }
})
).resolves.toEqual({ ok: true })
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
['issue', 'close', '12', '--repo', 'acme/widgets', '--duplicate-of', '99'],
{ encoding: 'utf-8' }
)
expect(runRestMock).not.toHaveBeenCalled()
})
it('rejects duplicate closes without a target before spawning gh', async () => {
const result = await updateIssueBySlug({
owner: 'acme',
repo: 'widgets',
number: 12,
updates: { state: 'closed', stateReason: 'duplicate' }
})
expect(result).toMatchObject({
ok: false,
error: { type: 'validation_error' }
})
expect(ghExecFileAsyncMock).not.toHaveBeenCalled()
expect(runRestMock).not.toHaveBeenCalled()
})
it('reopens slug-addressed issues via gh issue reopen', async () => {
await expect(
updateIssueBySlug({
owner: 'acme',
repo: 'widgets',
number: 12,
updates: { state: 'open' }
})
).resolves.toEqual({ ok: true })
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
['issue', 'reopen', '12', '--repo', 'acme/widgets'],
{ encoding: 'utf-8' }
)
})
})