mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
* fix(github): fail closed on unavailable stack metadata * fix(github): validate REST pull request response shape * fix(github): allow omitted stack metadata * test(github): cover enterprise stack probe failure * test(github): cover null ordinary stack metadata --------- Co-authored-by: E2E Test <e2e@test.local>
608 lines
20 KiB
TypeScript
608 lines
20 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
type RateLimitGuardResult =
|
|
| { blocked: false }
|
|
| { blocked: true; remaining: number; limit: number; resetAt: number }
|
|
|
|
const {
|
|
execFileAsyncMock,
|
|
ghExecFileAsyncMock,
|
|
getOwnerRepoMock,
|
|
getIssueOwnerRepoMock,
|
|
getOwnerRepoForRemoteMock,
|
|
getEnterpriseGitHubRepoSlugMock,
|
|
resolvePRRepositoryCandidatesMock,
|
|
getRemoteUrlForRepoMock,
|
|
gitExecFileAsyncMock,
|
|
getRateLimitMock,
|
|
rateLimitGuardMock,
|
|
noteRateLimitSpendMock,
|
|
acquireMock,
|
|
releaseMock
|
|
} = vi.hoisted(() => ({
|
|
execFileAsyncMock: vi.fn(),
|
|
ghExecFileAsyncMock: vi.fn(),
|
|
getOwnerRepoMock: vi.fn(),
|
|
getIssueOwnerRepoMock: vi.fn(),
|
|
getOwnerRepoForRemoteMock: vi.fn(),
|
|
getEnterpriseGitHubRepoSlugMock: vi.fn(),
|
|
resolvePRRepositoryCandidatesMock: vi.fn(),
|
|
getRemoteUrlForRepoMock: vi.fn(),
|
|
gitExecFileAsyncMock: vi.fn(),
|
|
getRateLimitMock: vi.fn(),
|
|
rateLimitGuardMock: vi.fn<() => RateLimitGuardResult>(() => ({ blocked: false })),
|
|
noteRateLimitSpendMock: vi.fn(),
|
|
acquireMock: vi.fn(),
|
|
releaseMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./gh-utils', () => ({
|
|
execFileAsync: execFileAsyncMock,
|
|
ghExecFileAsync: ghExecFileAsyncMock,
|
|
gitExecFileAsync: gitExecFileAsyncMock,
|
|
extractExecError: (err: unknown) => ({
|
|
stderr: err instanceof Error ? err.message : String(err),
|
|
stdout: ''
|
|
}),
|
|
acquire: acquireMock,
|
|
release: releaseMock,
|
|
getOwnerRepo: getOwnerRepoMock,
|
|
getIssueOwnerRepo: getIssueOwnerRepoMock,
|
|
getOwnerRepoForRemote: getOwnerRepoForRemoteMock,
|
|
resolvePRRepositoryCandidates: resolvePRRepositoryCandidatesMock,
|
|
resolveIssueSource: vi.fn(),
|
|
classifyGhError: (message: string) => ({ type: 'unknown', message }),
|
|
classifyListIssuesError: (message: string) => ({ type: 'unknown', message }),
|
|
ghRepoExecOptions: (context: {
|
|
repoPath: string
|
|
connectionId?: string | null
|
|
wslDistro?: string
|
|
}) =>
|
|
context.connectionId
|
|
? {}
|
|
: { cwd: context.repoPath, ...(context.wslDistro ? { wslDistro: context.wslDistro } : {}) },
|
|
githubRepoContext: (
|
|
repoPath: string,
|
|
connectionId?: string | null,
|
|
localGitOptions: { wslDistro?: string } = {}
|
|
) => ({
|
|
repoPath,
|
|
connectionId: connectionId ?? null,
|
|
...localGitOptions
|
|
}),
|
|
getRemoteUrlForRepo: getRemoteUrlForRepoMock,
|
|
_resetOwnerRepoCache: vi.fn()
|
|
}))
|
|
|
|
vi.mock('../git/runner', () => ({
|
|
gitExecFileAsync: gitExecFileAsyncMock
|
|
}))
|
|
|
|
vi.mock('./github-enterprise-repository', () => ({
|
|
getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock,
|
|
getEnterpriseGitHubRepoSlugForRemote: getEnterpriseGitHubRepoSlugMock,
|
|
isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true)
|
|
}))
|
|
|
|
vi.mock('../providers/ssh-git-dispatch', () => ({
|
|
getSshGitProvider: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./conflict-summary', () => ({
|
|
getPRConflictSummary: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./rate-limit', () => ({
|
|
getRateLimit: getRateLimitMock,
|
|
rateLimitGuard: rateLimitGuardMock,
|
|
noteRateLimitSpend: noteRateLimitSpendMock,
|
|
repositoryRateLimitGuard: vi.fn(() => ({ blocked: false })),
|
|
noteRepositoryRateLimitSpend: vi.fn(),
|
|
spendsSharedGitHubComQuota: (
|
|
repository?: { host?: string } | null,
|
|
executionOptions?: { wslDistro?: string }
|
|
) =>
|
|
(!repository?.host || repository.host.toLowerCase() === 'github.com') &&
|
|
!executionOptions?.wslDistro
|
|
}))
|
|
|
|
import {
|
|
addPRReviewComment,
|
|
addPRReviewCommentReply,
|
|
getPRCheckDetails,
|
|
getPRChecks,
|
|
getWorkItemByOwnerRepo,
|
|
getPRComments,
|
|
mergePR,
|
|
removePRReviewers,
|
|
rerunPRChecks,
|
|
requestPRReviewers,
|
|
resolveReviewThread,
|
|
setPRAutoMerge,
|
|
updatePRDetails,
|
|
updatePRState,
|
|
updatePRTitle
|
|
} from './client'
|
|
|
|
import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository'
|
|
|
|
// The origin-repository cache is module-level state; reset it so slugs
|
|
// resolved by one test cannot leak into the next.
|
|
beforeEach(() => {
|
|
_resetOriginGitHubApiRepositoryCache()
|
|
})
|
|
|
|
describe('GitHub PR local runtime routing', () => {
|
|
beforeEach(() => {
|
|
execFileAsyncMock.mockReset()
|
|
ghExecFileAsyncMock.mockReset()
|
|
getOwnerRepoMock.mockReset()
|
|
getIssueOwnerRepoMock.mockReset()
|
|
getOwnerRepoForRemoteMock.mockReset()
|
|
// Why: host-less prRepo backfill and origin resolution use getOwnerRepoForRemote.
|
|
getOwnerRepoForRemoteMock.mockImplementation(
|
|
async (repoPath: string, remoteName: string, connectionId?: string | null, opts = {}) =>
|
|
remoteName === 'origin' ? getOwnerRepoMock(repoPath, connectionId, opts) : null
|
|
)
|
|
getEnterpriseGitHubRepoSlugMock.mockReset()
|
|
getEnterpriseGitHubRepoSlugMock.mockResolvedValue(null)
|
|
resolvePRRepositoryCandidatesMock.mockReset()
|
|
getRemoteUrlForRepoMock.mockReset()
|
|
gitExecFileAsyncMock.mockReset()
|
|
getRateLimitMock.mockReset()
|
|
getRateLimitMock.mockResolvedValue({ resources: {} })
|
|
rateLimitGuardMock.mockReset()
|
|
rateLimitGuardMock.mockReturnValue({ blocked: false })
|
|
noteRateLimitSpendMock.mockReset()
|
|
acquireMock.mockReset()
|
|
releaseMock.mockReset()
|
|
acquireMock.mockResolvedValue(undefined)
|
|
})
|
|
|
|
it('routes PR details and mutations through the selected WSL distro', async () => {
|
|
const localGitOptions = { wslDistro: 'Ubuntu' }
|
|
const prRepo = { owner: 'acme', repo: 'orca' }
|
|
rateLimitGuardMock.mockReturnValue({
|
|
blocked: true,
|
|
remaining: 0,
|
|
limit: 5000,
|
|
resetAt: 1_800_000_000
|
|
})
|
|
getOwnerRepoMock.mockResolvedValue(prRepo)
|
|
ghExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
const endpoint = args.find((arg) => arg.startsWith('repos/acme/orca/')) ?? ''
|
|
const query = args.find((arg) => arg.startsWith('query=')) ?? ''
|
|
|
|
if (args[0] === 'pr' && args[1] === 'view') {
|
|
const jsonFields = args[args.indexOf('--json') + 1]
|
|
if (jsonFields === 'id,headRefOid,baseRefName') {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
id: 'PR_local',
|
|
headRefOid: 'head-oid',
|
|
baseRefName: 'main'
|
|
})
|
|
}
|
|
}
|
|
return {
|
|
stdout: JSON.stringify({
|
|
id: 'PR_kwDO123',
|
|
number: 7,
|
|
title: 'PR',
|
|
state: 'OPEN',
|
|
url: 'https://github.com/acme/orca/pull/7',
|
|
statusCheckRollup: [],
|
|
updatedAt: '2026-04-01T00:00:00Z',
|
|
isDraft: false,
|
|
mergeable: 'MERGEABLE',
|
|
reviewDecision: 'APPROVED',
|
|
mergeStateStatus: 'CLEAN',
|
|
autoMergeRequest: null,
|
|
baseRefName: 'main',
|
|
baseRefOid: 'base-oid',
|
|
headRefOid: 'head-oid'
|
|
})
|
|
}
|
|
}
|
|
if (query.includes('reviewThreads')) {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
data: {
|
|
repository: {
|
|
pullRequest: {
|
|
reviewThreads: { nodes: [] },
|
|
comments: { nodes: [] }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
if (endpoint.endsWith('/issues/7/comments?per_page=100')) {
|
|
return { stdout: '[]' }
|
|
}
|
|
if (endpoint.endsWith('/pulls/7/reviews?per_page=100')) {
|
|
return { stdout: '[]' }
|
|
}
|
|
if (endpoint.endsWith('/pulls/7/comments/11/replies')) {
|
|
return {
|
|
stdout: JSON.stringify({ id: 12, node_id: 'PRRC_reply_12', user: null, body: 'Reply' })
|
|
}
|
|
}
|
|
if (endpoint.endsWith('/pulls/7/comments')) {
|
|
return {
|
|
stdout: JSON.stringify({ id: 13, node_id: 'PRRC_inline_13', user: null, body: 'Inline' })
|
|
}
|
|
}
|
|
if (args.length === 2 && endpoint === 'repos/acme/orca/pulls/7') {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
number: 7,
|
|
head: { ref: 'feature', sha: 'head-oid' },
|
|
base: { ref: 'main', sha: 'base-oid' }
|
|
})
|
|
}
|
|
}
|
|
return { stdout: '', stderr: '' }
|
|
})
|
|
|
|
await getPRComments('/repo-root', 7, { prRepo }, null, localGitOptions)
|
|
await expect(
|
|
resolveReviewThread('/repo-root', 'thread-1', true, null, prRepo, localGitOptions)
|
|
).resolves.toBe(true)
|
|
await expect(
|
|
addPRReviewCommentReply(
|
|
'/repo-root',
|
|
7,
|
|
11,
|
|
'Reply',
|
|
'thread-1',
|
|
'src/app.ts',
|
|
10,
|
|
null,
|
|
prRepo,
|
|
localGitOptions
|
|
)
|
|
).resolves.toMatchObject({
|
|
ok: true,
|
|
comment: { reactionSubjectId: 'PRRC_reply_12' }
|
|
})
|
|
await expect(
|
|
addPRReviewComment({
|
|
repoPath: '/repo-root',
|
|
connectionId: null,
|
|
localGitOptions,
|
|
prRepo,
|
|
prNumber: 7,
|
|
body: 'Inline',
|
|
commitId: 'head-oid',
|
|
path: 'src/app.ts',
|
|
line: 10
|
|
})
|
|
).resolves.toMatchObject({
|
|
ok: true,
|
|
comment: { reactionSubjectId: 'PRRC_inline_13' }
|
|
})
|
|
await expect(
|
|
updatePRTitle('/repo-root', 7, 'New title', null, prRepo, localGitOptions)
|
|
).resolves.toBe(true)
|
|
await expect(
|
|
updatePRDetails('/repo-root', 7, { body: 'New body' }, null, prRepo, localGitOptions)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(
|
|
updatePRState('/repo-root', 7, { state: 'closed' }, null, prRepo, localGitOptions)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(
|
|
requestPRReviewers('/repo-root', 7, ['octo'], null, prRepo, localGitOptions)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(
|
|
removePRReviewers('/repo-root', 7, ['octo'], null, prRepo, localGitOptions)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(
|
|
setPRAutoMerge('/repo-root', 7, true, 'squash', null, prRepo, localGitOptions)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(
|
|
mergePR('/repo-root', 7, 'squash', null, prRepo, localGitOptions)
|
|
).resolves.toEqual({ ok: true })
|
|
|
|
expect(getOwnerRepoMock).toHaveBeenCalledWith('/repo-root', null, localGitOptions)
|
|
expect(ghExecFileAsyncMock.mock.calls.every((call) => call[1]?.wslDistro === 'Ubuntu')).toBe(
|
|
true
|
|
)
|
|
expect(getRateLimitMock).not.toHaveBeenCalled()
|
|
expect(rateLimitGuardMock).not.toHaveBeenCalled()
|
|
expect(noteRateLimitSpendMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('never falls through to the default gh host for an unresolved SSH repository', async () => {
|
|
const legacyRepo = { owner: 'team', repo: 'orca' }
|
|
getOwnerRepoMock.mockResolvedValue(null)
|
|
getEnterpriseGitHubRepoSlugMock.mockResolvedValue(null)
|
|
|
|
await expect(getPRComments('/remote/repo', 7, { prRepo: legacyRepo }, 'ssh-1')).rejects.toThrow(
|
|
'GitHub remote'
|
|
)
|
|
await expect(
|
|
getPRChecks('/remote/repo', 7, undefined, legacyRepo, undefined, 'ssh-1')
|
|
).rejects.toThrow('GitHub remote')
|
|
await expect(mergePR('/remote/repo', 7, 'squash', 'ssh-1', legacyRepo)).resolves.toMatchObject({
|
|
ok: false
|
|
})
|
|
await expect(
|
|
setPRAutoMerge('/remote/repo', 7, true, 'squash', 'ssh-1', legacyRepo)
|
|
).resolves.toMatchObject({ ok: false })
|
|
await expect(updatePRTitle('/remote/repo', 7, 'New title', 'ssh-1', legacyRepo)).resolves.toBe(
|
|
false
|
|
)
|
|
await expect(requestPRReviewers('/remote/repo', 7, ['octo'], 'ssh-1')).resolves.toMatchObject({
|
|
ok: false
|
|
})
|
|
await expect(removePRReviewers('/remote/repo', 7, ['octo'], 'ssh-1')).resolves.toMatchObject({
|
|
ok: false
|
|
})
|
|
await expect(
|
|
addPRReviewCommentReply(
|
|
'/remote/repo',
|
|
7,
|
|
11,
|
|
'Reply',
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
'ssh-1',
|
|
legacyRepo
|
|
)
|
|
).resolves.toMatchObject({ ok: false })
|
|
|
|
expect(ghExecFileAsyncMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('refuses unresolved local PR mutations instead of using ambient gh defaults', async () => {
|
|
const legacyRepo = { owner: 'team', repo: 'orca' }
|
|
getOwnerRepoMock.mockResolvedValue(null)
|
|
getEnterpriseGitHubRepoSlugMock.mockResolvedValue(null)
|
|
|
|
await expect(
|
|
resolveReviewThread('/repo-root', 'thread-1', true, null, legacyRepo)
|
|
).resolves.toBe(false)
|
|
await expect(updatePRTitle('/repo-root', 7, 'New title', null, legacyRepo)).resolves.toBe(false)
|
|
await expect(
|
|
requestPRReviewers('/repo-root', 7, ['octo'], null, legacyRepo)
|
|
).resolves.toMatchObject({ ok: false })
|
|
await expect(
|
|
removePRReviewers('/repo-root', 7, ['octo'], null, legacyRepo)
|
|
).resolves.toMatchObject({ ok: false })
|
|
await expect(mergePR('/repo-root', 7, 'squash', null, legacyRepo)).resolves.toMatchObject({
|
|
ok: false
|
|
})
|
|
await expect(
|
|
setPRAutoMerge('/repo-root', 7, true, 'squash', null, legacyRepo)
|
|
).resolves.toMatchObject({ ok: false })
|
|
|
|
expect(ghExecFileAsyncMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('preserves a ported GHES host in SSH-backed review reads and mutations', async () => {
|
|
const enterpriseRepo = {
|
|
owner: 'team',
|
|
repo: 'orca',
|
|
host: 'github.acme-corp.com:8443'
|
|
}
|
|
getOwnerRepoMock.mockResolvedValue(null)
|
|
getEnterpriseGitHubRepoSlugMock.mockResolvedValue(enterpriseRepo)
|
|
ghExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
const endpoint = args.find((arg) => arg.startsWith('repos/team/orca/')) ?? ''
|
|
const query = args.find((arg) => arg.startsWith('query=')) ?? ''
|
|
if (args[0] === 'pr' && args[1] === 'checks') {
|
|
return { stdout: '[]' }
|
|
}
|
|
if (args[0] === 'pr' && args[1] === 'view') {
|
|
if (args.includes('id,headRefOid,baseRefName')) {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
id: 'PR_enterprise',
|
|
headRefOid: 'head-sha',
|
|
baseRefName: 'main'
|
|
})
|
|
}
|
|
}
|
|
return {
|
|
stdout: JSON.stringify({
|
|
number: 7,
|
|
title: 'Enterprise PR',
|
|
state: 'OPEN',
|
|
url: 'https://github.acme-corp.com:8443/team/orca/pull/7',
|
|
labels: [],
|
|
updatedAt: '2026-07-16T00:00:00Z',
|
|
author: { login: 'pr-author' },
|
|
headRefName: 'feature',
|
|
baseRefName: 'main'
|
|
})
|
|
}
|
|
}
|
|
if (query.includes('reviewThreads')) {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
data: {
|
|
repository: {
|
|
pullRequest: {
|
|
reviewThreads: { nodes: [] },
|
|
comments: { nodes: [] }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
if (
|
|
endpoint.endsWith('/issues/7/comments?per_page=100') ||
|
|
endpoint.endsWith('/pulls/7/reviews?per_page=100')
|
|
) {
|
|
return { stdout: '[]' }
|
|
}
|
|
if (endpoint.endsWith('/commits/head-sha/check-runs?per_page=100')) {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
check_runs: [
|
|
{
|
|
id: 88,
|
|
name: 'lint',
|
|
status: 'completed',
|
|
conclusion: 'failure',
|
|
details_url: 'https://github.acme-corp.com:8443/team/orca/actions/runs/77/job/88'
|
|
}
|
|
]
|
|
})
|
|
}
|
|
}
|
|
if (endpoint.endsWith('/commits/head-sha/status?per_page=100')) {
|
|
return { stdout: JSON.stringify({ statuses: [] }) }
|
|
}
|
|
if (endpoint.endsWith('/commits/head-sha/check-suites?per_page=100')) {
|
|
return { stdout: JSON.stringify({ check_suites: [] }) }
|
|
}
|
|
if (endpoint.endsWith('/check-runs/88')) {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
id: 88,
|
|
name: 'lint',
|
|
status: 'completed',
|
|
conclusion: 'failure',
|
|
details_url: 'https://github.acme-corp.com:8443/team/orca/actions/runs/77/job/88',
|
|
output: { title: 'Lint failed', summary: 'One error' }
|
|
})
|
|
}
|
|
}
|
|
if (endpoint.endsWith('/check-runs/88/annotations?per_page=20')) {
|
|
return { stdout: '[]' }
|
|
}
|
|
if (args.length === 2 && endpoint === 'repos/team/orca/pulls/7') {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
number: 7,
|
|
head: { ref: 'feature', sha: 'head-sha' },
|
|
base: { ref: 'main', sha: 'base-sha' }
|
|
})
|
|
}
|
|
}
|
|
if (query) {
|
|
return { stdout: JSON.stringify({ data: { repository: {} } }) }
|
|
}
|
|
return {
|
|
stdout: JSON.stringify({
|
|
id: 13,
|
|
user: null,
|
|
body: 'Enterprise inline comment'
|
|
})
|
|
}
|
|
})
|
|
|
|
await expect(
|
|
getWorkItemByOwnerRepo('/remote/repo', enterpriseRepo, 7, 'pr', 'ssh-1')
|
|
).resolves.toMatchObject({ number: 7, title: 'Enterprise PR' })
|
|
await expect(
|
|
getWorkItemByOwnerRepo('/remote/repo', { owner: 'team', repo: 'orca' }, 7, 'pr', 'ssh-1')
|
|
).resolves.toMatchObject({ number: 7, title: 'Enterprise PR' })
|
|
await expect(
|
|
getPRComments('/remote/repo', 7, { prRepo: enterpriseRepo }, 'ssh-1')
|
|
).resolves.toEqual([])
|
|
await expect(
|
|
getPRChecks('/remote/repo', 7, undefined, enterpriseRepo, undefined, 'ssh-1')
|
|
).resolves.toEqual([])
|
|
await expect(
|
|
getPRCheckDetails('/remote/repo', { checkRunId: 88, prRepo: enterpriseRepo }, 'ssh-1')
|
|
).resolves.toMatchObject({ name: 'lint', conclusion: 'failure' })
|
|
await expect(
|
|
rerunPRChecks(
|
|
'/remote/repo',
|
|
7,
|
|
{ headSha: 'head-sha', failedOnly: true, prRepo: enterpriseRepo },
|
|
'ssh-1'
|
|
)
|
|
).resolves.toEqual({ ok: true, count: 1 })
|
|
await expect(
|
|
resolveReviewThread('/remote/repo', 'thread-1', true, 'ssh-1', enterpriseRepo)
|
|
).resolves.toBe(true)
|
|
await expect(
|
|
addPRReviewCommentReply(
|
|
'/remote/repo',
|
|
7,
|
|
11,
|
|
'Enterprise reply',
|
|
'thread-1',
|
|
'src/enterprise.ts',
|
|
10,
|
|
'ssh-1',
|
|
enterpriseRepo
|
|
)
|
|
).resolves.toMatchObject({ ok: true })
|
|
await expect(
|
|
addPRReviewComment({
|
|
repoPath: '/remote/repo',
|
|
connectionId: 'ssh-1',
|
|
prRepo: enterpriseRepo,
|
|
prNumber: 7,
|
|
body: 'Enterprise inline comment',
|
|
commitId: 'head-sha',
|
|
path: 'src/enterprise.ts',
|
|
line: 10
|
|
})
|
|
).resolves.toMatchObject({ ok: true })
|
|
await expect(
|
|
updatePRTitle('/remote/repo', 7, 'New title', 'ssh-1', enterpriseRepo)
|
|
).resolves.toBe(true)
|
|
await expect(
|
|
updatePRDetails('/remote/repo', 7, { body: 'New body' }, 'ssh-1', enterpriseRepo)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(
|
|
updatePRState('/remote/repo', 7, { state: 'closed' }, 'ssh-1', enterpriseRepo)
|
|
).resolves.toEqual({
|
|
ok: true
|
|
})
|
|
await expect(
|
|
requestPRReviewers('/remote/repo', 7, ['octo'], 'ssh-1', enterpriseRepo)
|
|
).resolves.toEqual({
|
|
ok: true
|
|
})
|
|
await expect(
|
|
removePRReviewers('/remote/repo', 7, ['octo'], 'ssh-1', enterpriseRepo)
|
|
).resolves.toEqual({
|
|
ok: true
|
|
})
|
|
await expect(
|
|
setPRAutoMerge('/remote/repo', 7, true, 'squash', 'ssh-1', enterpriseRepo)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(
|
|
setPRAutoMerge('/remote/repo', 7, false, 'squash', 'ssh-1', enterpriseRepo)
|
|
).resolves.toEqual({ ok: true })
|
|
await expect(mergePR('/remote/repo', 7, 'squash', 'ssh-1', enterpriseRepo)).resolves.toEqual({
|
|
ok: true
|
|
})
|
|
|
|
const prViewCall = ghExecFileAsyncMock.mock.calls.find(
|
|
([args]) => args[0] === 'pr' && args[1] === 'view'
|
|
)
|
|
// The runner host-qualifies argv at spawn time from options.host, so the
|
|
// mocked call sees the unqualified --repo plus the host in exec options.
|
|
expect(prViewCall?.[0]).toEqual(expect.arrayContaining(['--repo', 'team/orca']))
|
|
expect(prViewCall?.[1]).toEqual({ host: 'github.acme-corp.com:8443' })
|
|
const prCalls = ghExecFileAsyncMock.mock.calls.filter(([args]) => args[0] === 'pr')
|
|
expect(
|
|
prCalls.every(
|
|
([args]) => args.includes('--repo') && args[args.indexOf('--repo') + 1] === 'team/orca'
|
|
)
|
|
).toBe(true)
|
|
const apiCalls = ghExecFileAsyncMock.mock.calls.filter(([args]) => args[0] === 'api')
|
|
expect(apiCalls.length).toBeGreaterThan(0)
|
|
expect(
|
|
apiCalls.every(
|
|
([args, options]) =>
|
|
!args.includes('--hostname') &&
|
|
options.host === 'github.acme-corp.com:8443' &&
|
|
options.cwd === undefined &&
|
|
options.wslDistro === undefined
|
|
)
|
|
).toBe(true)
|
|
})
|
|
})
|