Files
orca/src/main/github/client-issue-source.test.ts
T
NeilandOrca e33b2006f4 Remove stale max-lines lint disables from files under the limit (#7548)
110 files carried an eslint/oxlint-disable max-lines directive but are
already under the default max-lines budget (300 .ts / 400 .tsx / 600 .mjs
/ 800 test), so the suppression is dead. Removing it restores real
max-lines coverage on these files with zero behavior change.

Each removed directive had max-lines as its only rule; verified via a
full oxlint run (0 max-lines violations, 0 new errors). Diff is pure
deletions (200 lines, 0 additions) — no code touched.

Co-authored-by: Orca <help@stably.ai>
2026-07-06 02:12:32 -07:00

686 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as GhUtils from './gh-utils'
const {
execFileAsyncMock,
ghExecFileAsyncMock,
getOwnerRepoMock,
getIssueOwnerRepoMock,
getOwnerRepoForRemoteMock,
resolveIssueSourceMock,
rateLimitGuardMock,
noteRateLimitSpendMock,
acquireMock,
releaseMock
} = vi.hoisted(() => ({
execFileAsyncMock: vi.fn(),
ghExecFileAsyncMock: vi.fn(),
getOwnerRepoMock: vi.fn(),
getIssueOwnerRepoMock: vi.fn(),
getOwnerRepoForRemoteMock: vi.fn(),
resolveIssueSourceMock: vi.fn(),
rateLimitGuardMock: vi.fn(() => ({ blocked: false })),
noteRateLimitSpendMock: vi.fn(),
acquireMock: vi.fn(),
releaseMock: vi.fn()
}))
vi.mock('./gh-utils', async () => {
const actual = await vi.importActual<typeof GhUtils>('./gh-utils')
return {
...actual,
execFileAsync: execFileAsyncMock,
ghExecFileAsync: ghExecFileAsyncMock,
getOwnerRepo: getOwnerRepoMock,
getIssueOwnerRepo: getIssueOwnerRepoMock,
getOwnerRepoForRemote: getOwnerRepoForRemoteMock,
resolveIssueSource: resolveIssueSourceMock,
acquire: acquireMock,
release: releaseMock,
_resetOwnerRepoCache: vi.fn()
}
})
vi.mock('./rate-limit', () => ({
rateLimitGuard: rateLimitGuardMock,
noteRateLimitSpend: noteRateLimitSpendMock
}))
import { countWorkItems, getWorkItem, listWorkItems, _resetOwnerRepoCache } from './client'
describe('GitHub issue source split', () => {
beforeEach(() => {
execFileAsyncMock.mockReset()
ghExecFileAsyncMock.mockReset()
getOwnerRepoMock.mockReset()
getIssueOwnerRepoMock.mockReset()
getOwnerRepoForRemoteMock.mockReset()
resolveIssueSourceMock.mockReset()
rateLimitGuardMock.mockReset()
rateLimitGuardMock.mockReturnValue({ blocked: false })
noteRateLimitSpendMock.mockReset()
acquireMock.mockReset()
releaseMock.mockReset()
acquireMock.mockResolvedValue(undefined)
// Why: default the preference-aware resolver to 'auto' semantics so the
// pre-existing test cases (which don't think about preference at all)
// still pass. `listWorkItems` now calls `resolveIssueSource` instead of
// `getIssueOwnerRepo` directly — we delegate back to the single-call
// mock to preserve the one-fetch-per-test invariant each test sets up.
resolveIssueSourceMock.mockImplementation(async () => ({
source: await getIssueOwnerRepoMock(),
fellBack: false
}))
// Default the upstream-candidate lookup to null so existing tests that
// only mock `getIssueOwnerRepo` + `getOwnerRepo` don't need to think
// about it. Tests that care set it explicitly.
getOwnerRepoForRemoteMock.mockResolvedValue(null)
_resetOwnerRepoCache()
})
it('uses upstream for issues and origin for PRs in mixed recent results', async () => {
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock
.mockResolvedValueOnce({
stdout: JSON.stringify([
{
number: 923,
title: 'Use upstream issues',
state: 'open',
html_url: 'https://github.com/stablyai/orca/issues/923',
labels: [],
updated_at: '2026-04-01T00:00:00Z',
user: { login: 'octocat' }
}
])
})
.mockResolvedValueOnce({
stdout: JSON.stringify([
{
number: 42,
title: 'Fork PR',
state: 'open',
html_url: 'https://github.com/fork/orca/pull/42',
labels: [],
updated_at: '2026-03-31T00:00:00Z',
user: { login: 'octocat' },
draft: false,
head: { ref: 'feature' },
base: { ref: 'main' }
}
])
})
await listWorkItems('/repo-root', 10)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
[
'api',
'--cache',
'120s',
'repos/stablyai/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
],
{ cwd: '/repo-root' }
)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
2,
[
'api',
'--cache',
'120s',
'repos/fork/orca/pulls?per_page=10&state=open&sort=updated&direction=desc'
],
{ cwd: '/repo-root' }
)
})
it('omits gh api cache args for no-cache recent work-item requests', async () => {
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
await listWorkItems('/repo-root', 10, undefined, undefined, undefined, undefined, true)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
['api', 'repos/stablyai/orca/issues?per_page=10&state=open&sort=updated&direction=desc'],
{ cwd: '/repo-root' }
)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
2,
['api', 'repos/fork/orca/pulls?per_page=10&state=open&sort=updated&direction=desc'],
{ cwd: '/repo-root' }
)
})
it('lists SSH repo work items with explicit owner/repo and no local cwd', async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'stablyai', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
await listWorkItems('/home/jinwoo/orca', 10, undefined, undefined, 'auto', 'openclaw-2')
expect(resolveIssueSourceMock).toHaveBeenCalledWith(
'/home/jinwoo/orca',
'auto',
'openclaw-2',
{}
)
expect(getOwnerRepoMock).toHaveBeenCalledWith('/home/jinwoo/orca', 'openclaw-2', {})
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
[
'api',
'--cache',
'120s',
'repos/stablyai/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
],
{}
)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
2,
[
'api',
'--cache',
'120s',
'repos/fork/orca/pulls?per_page=10&state=open&sort=updated&direction=desc'
],
{}
)
})
it('uses upstream for issue-only queries and origin for PR-only queries', async () => {
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' })
await listWorkItems('/repo-root', 10, 'is:issue')
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
expect.arrayContaining(['--repo', 'stablyai/orca']),
{ cwd: '/repo-root' }
)
ghExecFileAsyncMock.mockClear()
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' })
await listWorkItems('/repo-root', 10, 'is:pr')
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
expect.arrayContaining(['--repo', 'fork/orca']),
{ cwd: '/repo-root' }
)
})
it("uses upstream for recent PRs when preference='upstream'", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'stablyai', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
getOwnerRepoForRemoteMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
await listWorkItems('/repo-root', 10, undefined, undefined, 'upstream')
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
2,
[
'api',
'--cache',
'120s',
'repos/stablyai/orca/pulls?per_page=10&state=open&sort=updated&direction=desc'
],
{ cwd: '/repo-root' }
)
})
it("uses upstream for queried PRs when preference='upstream'", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'stablyai', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
getOwnerRepoForRemoteMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' })
await listWorkItems('/repo-root', 10, 'is:pr is:open', undefined, 'upstream')
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
expect.arrayContaining(['--repo', 'stablyai/orca']),
{ cwd: '/repo-root' }
)
})
it("uses upstream for PR counts when preference='upstream'", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'stablyai', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
getOwnerRepoForRemoteMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '9\n' })
const count = await countWorkItems('/repo-root', 'is:pr is:open', 'upstream')
expect(count).toBe(9)
expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(1)
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
[
'api',
'--cache',
'120s',
`search/issues?q=${encodeURIComponent('repo:stablyai/orca is:pull-request is:open')}&per_page=1`,
'--jq',
'.total_count'
],
{ cwd: '/repo-root' }
)
})
it("falls back to origin for PRs when preference='upstream' and upstream is missing", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'fork', repo: 'orca' },
fellBack: true
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
getOwnerRepoForRemoteMock.mockResolvedValueOnce(null)
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' })
const result = await listWorkItems('/repo-root', 10, 'is:pr', undefined, 'upstream')
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
expect.arrayContaining(['--repo', 'fork/orca']),
{ cwd: '/repo-root' }
)
expect(result.sources).toEqual({
issues: { owner: 'fork', repo: 'orca' },
prs: { owner: 'fork', repo: 'orca' },
originCandidate: { owner: 'fork', repo: 'orca' },
upstreamCandidate: null
})
expect(result.issueSourceFellBack).toBe(true)
})
it('counts default work items across upstream issues and origin PRs', async () => {
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock
.mockResolvedValueOnce({ stdout: '7\n' })
.mockResolvedValueOnce({ stdout: '5\n' })
const count = await countWorkItems('/repo-root')
expect(count).toBe(12)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
[
'api',
'--cache',
'120s',
`search/issues?q=${encodeURIComponent('repo:stablyai/orca is:issue is:open')}&per_page=1`,
'--jq',
'.total_count'
],
{ cwd: '/repo-root' }
)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
2,
[
'api',
'--cache',
'120s',
`search/issues?q=${encodeURIComponent('repo:fork/orca is:pull-request is:open')}&per_page=1`,
'--jq',
'.total_count'
],
{ cwd: '/repo-root' }
)
})
it('typed PR lookup does not fetch an upstream issue with the same number', async () => {
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({
stdout: JSON.stringify({
number: 42,
title: 'Origin PR',
state: 'open',
html_url: 'https://github.com/fork/orca/pull/42',
labels: [],
updated_at: '2026-04-02T00:00:00Z',
user: { login: 'octocat' },
draft: false,
head: { ref: 'feature' },
base: { ref: 'main' }
})
})
const item = await getWorkItem('/repo-root', 42, 'pr')
expect(getIssueOwnerRepoMock).not.toHaveBeenCalled()
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
[
'pr',
'view',
'42',
'--repo',
'fork/orca',
'--json',
expect.stringContaining('reviewDecision')
],
{ cwd: '/repo-root' }
)
expect(item?.type).toBe('pr')
})
it('raw number lookup tries upstream issue before origin PR', async () => {
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
// Why: simulate a real gh 404 (the only error type that should fall through).
// Non-404 errors re-throw so transient upstream failures don't misroute to an
// unrelated origin PR with the same number.
ghExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 404: Not Found'))
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({
stdout: JSON.stringify({
number: 42,
title: 'Origin PR',
state: 'open',
html_url: 'https://github.com/fork/orca/pull/42',
labels: [],
updated_at: '2026-04-02T00:00:00Z',
user: { login: 'octocat' },
draft: false
})
})
const item = await getWorkItem('/repo-root', 42)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
['api', 'repos/stablyai/orca/issues/42'],
{ cwd: '/repo-root' }
)
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
2,
[
'pr',
'view',
'42',
'--repo',
'fork/orca',
'--json',
expect.stringContaining('reviewDecision')
],
{ cwd: '/repo-root' }
)
expect(item?.type).toBe('pr')
})
it('surfaces a 403 from upstream issues through the listWorkItems envelope', async () => {
// Why: parent design doc §3 / acceptance criterion 2 — the IPC envelope
// must carry a classified error for the failing side so the renderer can
// swap the empty-state for a retryable banner. `sources` must stay
// populated so the banner copy can name the repo that failed.
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock
.mockRejectedValueOnce(new Error('HTTP 403: Resource not accessible by integration'))
.mockResolvedValueOnce({ stdout: '[]' })
const result = await listWorkItems('/repo-root', 10)
expect(result.items).toEqual([])
expect(result.sources).toMatchObject({
issues: { owner: 'stablyai', repo: 'orca' },
prs: { owner: 'fork', repo: 'orca' }
})
expect(result.errors?.issues?.type).toBe('permission_denied')
})
it('returns partial results when upstream issues fail but origin PRs succeed', async () => {
// Why: parent design doc §2 partial-failure rule — a failing source must
// not zero out the succeeding source. The UI renders origin PRs with a
// banner above the list, not an empty state. Ensures the IPC shape
// carries both the successful items and the error for the failing side.
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock
.mockRejectedValueOnce(new Error('HTTP 403: Resource not accessible by integration'))
.mockResolvedValueOnce({
stdout: JSON.stringify([
{
number: 42,
title: 'Fork PR',
state: 'open',
html_url: 'https://github.com/fork/orca/pull/42',
labels: [],
updated_at: '2026-03-31T00:00:00Z',
user: { login: 'octocat' },
draft: false,
head: { ref: 'feature' },
base: { ref: 'main' }
}
])
})
const result = await listWorkItems('/repo-root', 10)
expect(result.items.map((i) => i.id)).toEqual(['pr:42'])
expect(result.errors?.issues?.type).toBe('permission_denied')
})
it('raw number lookup does not fall through on transient upstream errors', async () => {
// Why: with issue source split, a non-404 upstream failure must not silently
// route to origin's PR #N — that would return an unrelated item.
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
ghExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 500: server error'))
const item = await getWorkItem('/repo-root', 42)
expect(item).toBeNull()
expect(getOwnerRepoMock).not.toHaveBeenCalled()
expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(1)
})
describe('per-repo issue-source preference', () => {
// Why: 3 preference states × 2 remote-topology states = 6 cases per the
// design doc §9. These tests isolate `listWorkItems` against a mocked
// `resolveIssueSource` to verify the preference is threaded all the way
// to the gh call and that `fellBack` propagates into the envelope.
it("preference='auto' + upstream exists → queries upstream", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'stablyai', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
const result = await listWorkItems('/repo-root', 10, undefined, undefined, 'auto')
expect(resolveIssueSourceMock).toHaveBeenCalledWith('/repo-root', 'auto', undefined, {})
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
[
'api',
'--cache',
'120s',
'repos/stablyai/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
],
{ cwd: '/repo-root' }
)
expect(result.issueSourceFellBack).toBeUndefined()
})
it("preference='auto' + no upstream → queries origin", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'solo', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'solo', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
await listWorkItems('/repo-root', 10, undefined, undefined, 'auto')
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
[
'api',
'--cache',
'120s',
'repos/solo/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
],
{ cwd: '/repo-root' }
)
})
it("preference='upstream' + upstream exists → queries upstream", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'stablyai', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
const result = await listWorkItems('/repo-root', 10, undefined, undefined, 'upstream')
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
expect.arrayContaining([
'repos/stablyai/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
]),
{ cwd: '/repo-root' }
)
expect(result.issueSourceFellBack).toBeUndefined()
})
it("preference='upstream' + no upstream → falls back to origin with fellBack=true", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'solo', repo: 'orca' },
fellBack: true
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'solo', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
const result = await listWorkItems('/repo-root', 10, undefined, undefined, 'upstream')
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
expect.arrayContaining([
'repos/solo/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
]),
{ cwd: '/repo-root' }
)
expect(result.issueSourceFellBack).toBe(true)
})
it("preference='origin' + upstream exists → queries origin (not upstream)", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'fork', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
await listWorkItems('/repo-root', 10, undefined, undefined, 'origin')
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
expect.arrayContaining([
'repos/fork/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
]),
{ cwd: '/repo-root' }
)
})
it("preference='origin' + no upstream → queries origin", async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'solo', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'solo', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
await listWorkItems('/repo-root', 10, undefined, undefined, 'origin')
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
1,
expect.arrayContaining([
'repos/solo/orca/issues?per_page=10&state=open&sort=updated&direction=desc'
]),
{ cwd: '/repo-root' }
)
})
it('surfaces upstreamCandidate in sources regardless of effective preference', async () => {
// Why: the renderer selector needs to keep rendering after the user picks
// 'origin'. That requires the envelope to carry the raw upstream even
// when `sources.issues` has collapsed onto origin.
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'fork', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
getOwnerRepoForRemoteMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
const result = await listWorkItems('/repo-root', 10, undefined, undefined, 'origin')
expect(result.sources).toEqual({
issues: { owner: 'fork', repo: 'orca' },
prs: { owner: 'fork', repo: 'orca' },
originCandidate: { owner: 'fork', repo: 'orca' },
upstreamCandidate: { owner: 'stablyai', repo: 'orca' }
})
})
it('keeps raw origin metadata when effective PR source is upstream', async () => {
resolveIssueSourceMock.mockResolvedValueOnce({
source: { owner: 'stablyai', repo: 'orca' },
fellBack: false
})
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
getOwnerRepoForRemoteMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
stdout: '[]'
})
const result = await listWorkItems('/repo-root', 10, undefined, undefined, 'upstream')
expect(result.sources).toEqual({
issues: { owner: 'stablyai', repo: 'orca' },
prs: { owner: 'stablyai', repo: 'orca' },
originCandidate: { owner: 'fork', repo: 'orca' },
upstreamCandidate: { owner: 'stablyai', repo: 'orca' }
})
})
})
})