Files
orca/src/shared/source-control-create-review-intent.test.ts
T

215 lines
6.5 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
resolveCreatePrIntentRemoteStep,
resolveCreateReviewIntentEligibility,
type CreateReviewIntentKind
} from './source-control-create-review-intent'
import type { HostedReviewCreationBlockedReason } from './hosted-review'
import type { GitUpstreamStatus } from './git-status-types'
function unavailableEligibility(blockedReason: HostedReviewCreationBlockedReason) {
return {
provider: 'github' as const,
review: null,
canCreate: false,
blockedReason,
nextAction: null,
defaultBaseRef: 'main',
reviewLookupOutcome: 'unavailable' as const
}
}
describe('resolveCreateReviewIntentEligibility', () => {
it('rejects unavailable eligibility when the default branch is unknown', () => {
expect(
resolveCreateReviewIntentEligibility({
stagedCount: 1,
hasStageableChanges: true,
hasMessage: true,
hasUnresolvedConflicts: false,
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 },
hostedReviewCreation: {
...unavailableEligibility('dirty'),
defaultBaseRef: null
}
})
).toEqual({ eligible: false, kind: null })
})
it('stays ineligible when no local blocker remains under unavailable lookup', () => {
expect(
resolveCreateReviewIntentEligibility({
stagedCount: 0,
hasStageableChanges: false,
hasMessage: true,
hasUnresolvedConflicts: false,
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 },
hostedReviewCreation: {
...unavailableEligibility('dirty'),
blockedReason: null
}
})
).toEqual({ eligible: false, kind: null })
})
it('keeps dirty local preparation eligible when review lookup is unavailable', () => {
expect(
resolveCreateReviewIntentEligibility({
stagedCount: 0,
hasStageableChanges: true,
hasMessage: true,
hasUnresolvedConflicts: false,
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 },
hostedReviewCreation: unavailableEligibility('dirty')
})
).toEqual({ eligible: true, kind: 'dirty' })
})
it('still requires a message before committing staged changes without lookup authority', () => {
expect(
resolveCreateReviewIntentEligibility({
stagedCount: 1,
hasStageableChanges: false,
hasMessage: false,
hasUnresolvedConflicts: false,
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 },
hostedReviewCreation: unavailableEligibility('dirty')
})
).toEqual({ eligible: true, kind: 'message_required' })
})
it.each<{
blockedReason: HostedReviewCreationBlockedReason
blockedKind: CreateReviewIntentKind
stagedCount?: number
hasStageableChanges?: boolean
branchCommitsAhead?: number
upstreamStatus?: GitUpstreamStatus
}>([
{
blockedReason: 'no_upstream',
blockedKind: 'no_upstream',
branchCommitsAhead: 1,
upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 }
},
{
blockedReason: 'needs_push',
blockedKind: 'needs_push',
upstreamStatus: {
hasUpstream: true,
upstreamName: 'origin/feature',
ahead: 1,
behind: 0
}
},
{
blockedReason: 'needs_sync',
blockedKind: 'needs_sync',
upstreamStatus: {
hasUpstream: true,
upstreamName: 'origin/feature',
ahead: 0,
behind: 1
}
},
{
blockedReason: 'needs_sync',
blockedKind: 'force_push',
branchCommitsAhead: 1,
upstreamStatus: {
hasUpstream: true,
upstreamName: 'origin/feature',
ahead: 2,
behind: 1,
behindCommitsArePatchEquivalent: true
}
}
])(
'keeps recoverable $blockedKind preparation eligible when review lookup is unavailable',
({
blockedReason,
blockedKind,
stagedCount = 0,
hasStageableChanges = false,
branchCommitsAhead,
upstreamStatus
}) => {
expect(
resolveCreateReviewIntentEligibility({
stagedCount,
hasStageableChanges,
hasMessage: true,
hasUnresolvedConflicts: false,
upstreamStatus,
hostedReviewCreation: unavailableEligibility(blockedReason),
branchCommitsAhead
})
).toEqual({ eligible: true, kind: blockedKind })
}
)
})
describe('resolveCreatePrIntentRemoteStep', () => {
it('resolves safe remote steps for publish, push, and patch-equivalent force-push', () => {
expect(
resolveCreatePrIntentRemoteStep({
upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 },
branchCommitsAhead: 2,
hasCurrentBranch: true,
hostedReviewCreation: { canCreate: false, blockedReason: 'no_upstream' }
})
).toBe('publish')
expect(
resolveCreatePrIntentRemoteStep({
upstreamStatus: { hasUpstream: true, ahead: 2, behind: 0 },
hasCurrentBranch: true,
hostedReviewCreation: { canCreate: false, blockedReason: 'needs_push' }
})
).toBe('push')
expect(
resolveCreatePrIntentRemoteStep({
upstreamStatus: {
hasUpstream: true,
ahead: 3,
behind: 2,
behindCommitsArePatchEquivalent: true
},
branchCommitsAhead: 3,
hasCurrentBranch: true,
hostedReviewCreation: { canCreate: false, blockedReason: 'needs_sync' }
})
).toBe('force_push')
})
it('fast-forwards behind-only branches, blocks diverged and unpublished-without-commits branches', () => {
// Diverged branches require an explicit stop; automatic merge would reconcile without consent.
expect(
resolveCreatePrIntentRemoteStep({
upstreamStatus: { hasUpstream: true, ahead: 1, behind: 1 },
hasCurrentBranch: true,
hostedReviewCreation: { canCreate: false, blockedReason: 'needs_sync' }
})
).toBe('blocked')
// Behind with no local commits: pure --ff-only (never plain merge sync).
expect(
resolveCreatePrIntentRemoteStep({
upstreamStatus: { hasUpstream: true, ahead: 0, behind: 3 },
hasCurrentBranch: true,
hostedReviewCreation: { canCreate: false, blockedReason: 'needs_sync' }
})
).toBe('fast_forward')
expect(
resolveCreatePrIntentRemoteStep({
upstreamStatus: { hasUpstream: false, ahead: 0, behind: 0 },
branchCommitsAhead: 0,
hasCurrentBranch: true,
hostedReviewCreation: { canCreate: false, blockedReason: 'no_upstream' }
})
).toBe('blocked')
})
})