Files
orca/src/main/github/review-head-remote.test.ts
T
JinjingandOrca 33bd676644 fix(github): align PR source and review head origin (#10677)
* fix(github): align PR source and review head origin

* fix(github): pin number-based work item open to the repo source preference

Open-by-number and details still ran the upstream-first multi-candidate PR
probe, so a fork and its upstream sharing a PR number opened different PRs
than the list and start-point paths did once #10677 pinned those to origin.

Thread repo.issueSourcePreference through dispatchWorkItem, getWorkItemDetails,
getRepoWorkItem, and getRepoWorkItemDetails. getWorkItemByOwnerRepo is left
alone: explicit owner/repo already pins identity. auto/upstream/undefined keep
the multi-candidate probe.

Co-authored-by: Orca <help@stably.ai>

* test(github): enforce origin preference in review head origin resolution

The explicit origin preference must short-circuit before any identity probe, so no remote queries should occur. Add validation to reject unexpected remotes and tighten the test assertion to verify no remote get-url calls happen at all.

* fix(github): enforce origin preference in issue open-by-number lookup

listWorkItems and getWorkItem must share preference so origin/upstream
toggles cannot disagree. Explicit origin preference now fail-closes when
origin identity is unresolved (no bare-lookup fallback), matching the
PR candidate resolution rule.

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-25 22:48:53 -07:00

132 lines
4.5 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const { getDefaultRemoteMock, getGitHubApiRepositoryForRemoteMock } = vi.hoisted(() => ({
getDefaultRemoteMock: vi.fn(),
getGitHubApiRepositoryForRemoteMock: vi.fn()
}))
vi.mock('../git/repo', () => ({ getDefaultRemote: getDefaultRemoteMock }))
vi.mock('./github-api-repository', () => ({
getGitHubApiRepositoryForRemote: getGitHubApiRepositoryForRemoteMock
}))
import { resolveGitHubReviewHeadRemote } from './review-head-remote'
function gitExecWithRemotes(remotes: string[]) {
return vi.fn(async (args: string[]) => {
if (args[0] === 'remote') {
return { stdout: `${remotes.join('\n')}\n`, stderr: '' }
}
throw new Error(`unexpected git call: ${args.join(' ')}`)
})
}
describe('resolveGitHubReviewHeadRemote', () => {
beforeEach(() => {
getDefaultRemoteMock.mockReset()
getGitHubApiRepositoryForRemoteMock.mockReset()
})
it('prefers upstream on a contributor clone whose origin is a fork', async () => {
// Why: PR work-item resolution probes upstream first; refs/pull/<N>/head for
// an upstream PR does not exist on the fork origin.
getGitHubApiRepositoryForRemoteMock.mockImplementation(async (_path, remote) =>
remote === 'upstream'
? { owner: 'org', repo: 'project' }
: { owner: 'contributor', repo: 'project' }
)
const remote = await resolveGitHubReviewHeadRemote({
repoPath: '/repo',
issueSourcePreference: 'auto',
gitExec: gitExecWithRemotes(['origin', 'upstream'])
})
expect(remote).toBe('upstream')
expect(getDefaultRemoteMock).not.toHaveBeenCalled()
})
it('falls back to origin when upstream is not a GitHub project', async () => {
getGitHubApiRepositoryForRemoteMock.mockImplementation(async (_path, remote) =>
remote === 'origin' ? { owner: 'org', repo: 'project' } : null
)
const remote = await resolveGitHubReviewHeadRemote({
repoPath: '/repo',
issueSourcePreference: 'upstream',
gitExec: gitExecWithRemotes(['origin', 'upstream'])
})
expect(remote).toBe('origin')
})
it('uses explicit origin without probing hosting identity on a dual-remote clone', async () => {
getGitHubApiRepositoryForRemoteMock.mockResolvedValue({ owner: 'org', repo: 'project' })
const remote = await resolveGitHubReviewHeadRemote({
repoPath: '/repo',
issueSourcePreference: 'origin',
gitExec: gitExecWithRemotes(['origin', 'upstream'])
})
expect(remote).toBe('origin')
expect(getGitHubApiRepositoryForRemoteMock).not.toHaveBeenCalled()
expect(getDefaultRemoteMock).not.toHaveBeenCalled()
})
it('rejects explicit origin when that remote is not configured', async () => {
await expect(
resolveGitHubReviewHeadRemote({
repoPath: '/repo',
issueSourcePreference: 'origin',
gitExec: gitExecWithRemotes(['upstream'])
})
).rejects.toThrow('Repo has no configured origin remote.')
expect(getGitHubApiRepositoryForRemoteMock).not.toHaveBeenCalled()
expect(getDefaultRemoteMock).not.toHaveBeenCalled()
})
it('skips identity probes for a single-remote clone and uses the local default', async () => {
getDefaultRemoteMock.mockResolvedValue('origin')
const remote = await resolveGitHubReviewHeadRemote({
repoPath: '/repo',
localGitOptions: { wslDistro: 'Ubuntu' },
gitExec: gitExecWithRemotes(['origin'])
})
expect(remote).toBe('origin')
expect(getGitHubApiRepositoryForRemoteMock).not.toHaveBeenCalled()
expect(getDefaultRemoteMock).toHaveBeenCalledWith('/repo', { wslDistro: 'Ubuntu' })
})
it('prefers origin over other remotes on SSH repos when no identity resolves', async () => {
getGitHubApiRepositoryForRemoteMock.mockResolvedValue(null)
const remote = await resolveGitHubReviewHeadRemote({
repoPath: '/remote/repo',
connectionId: 'ssh-1',
gitExec: gitExecWithRemotes(['fork', 'origin'])
})
expect(remote).toBe('origin')
expect(getDefaultRemoteMock).not.toHaveBeenCalled()
})
it('threads connection and WSL options through the identity probe', async () => {
getGitHubApiRepositoryForRemoteMock.mockResolvedValue({ owner: 'org', repo: 'project' })
await resolveGitHubReviewHeadRemote({
repoPath: '/repo',
connectionId: 'ssh-1',
localGitOptions: { wslDistro: 'Ubuntu' },
gitExec: gitExecWithRemotes(['origin', 'upstream'])
})
expect(getGitHubApiRepositoryForRemoteMock).toHaveBeenCalledWith('/repo', 'upstream', 'ssh-1', {
wslDistro: 'Ubuntu'
})
})
})