Files
orca/tests/e2e/github-pr-hosted-receipt-loop.unit.test.ts
Brennan BensonandJinjing a36620afd0 perf(github): share automatic PR reads and reuse the host budget (#42033)
* perf(github): share and pace timed pull request reads on the host

Reuse exact completed results for automatic refreshes, back off unchanged
settled pull requests, and route server revalidation through the existing
host budget while preserving foreground reads and older clients.

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>

* fix(github): preserve shared producer settlement and selected demand

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>

* test(github): cover paired receipt retries and watch fairness

* test(github): explain shared fake-clock fixture

* test(github): typecheck host and renderer integration together

* test(github): name exported fixture mock types

* Revert "test(github): typecheck host and renderer integration together"

This reverts commit 756a38787c.

* test(github): place host and renderer loops in cross-layer suite

* fix(github): preserve refresh policy and shared callers

Use the same validated freshness for cached queue reads and their follow-up. Keep forced mergeability rereads and detach cancelled consumers independently from the shared lookup.

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>

* fix(github): refresh settled rows when exposed again

Use the same finite foreground age to admit and consume the queued read. A recent exact result can still satisfy it, and settled rows do not acquire a recurring timer.

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>

* fix(github): accept only current PR read completions

Keep shared publication and retry planning on the accepted actual read. Preserve legacy own replies, terminal loading and ordinary work after a superseded lookup, and honor the accepted error gate before automatic recovery.

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>

* test(github): follow moved request contracts

* test(github): explain the partial event fixture type assertion

* fix(github): preserve queued deadlines and detachable PR waits

* fix(github): compare refresh wakes on the elapsed clock

* test(github): format queued refresh deadline cases

* fix(github): charge actual producers and fence retry gates

* fix(github): preserve fresh intent and producer error policy

* fix(github): retain owned cold-read retry gates

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-11-13 16:33:07 -08:00

285 lines
10 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import {
commands,
fixture,
found,
mocks,
rpcRead
} from '../../src/main/runtime/integrations/reviews/runtime-hosted-pr-snapshot-test-harness'
import type { PRRefreshOutcome } from '../../src/shared/github/pull-requests/refresh/pull-request-refresh-types'
describe('hosted PR receipt delivery', () => {
it('carries a real deferred reply through the visible target, store and scheduler to its completion', async () => {
const {
createTestStore,
makePRRefreshWorktree,
resetRemoteRuntimeMocks,
runtimeEnvironmentCall
} = await import('../../src/renderer/src/store/github/github-slice-test-harness')
const { getVisibleHostedReviewRefreshTargets } =
await import('../../src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-targets')
const { createVisibleHostedReviewRefreshScheduler } =
await import('../../src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-scheduler')
const { PrForBranch } =
await import('../../src/shared/rpc-contract/github/github-pull-request-params')
const readReceipt = (reason: 'active' | 'swr') =>
commands.getRepoPRForBranch(
'repo',
'receipt',
12,
null,
false,
'head-a',
reason,
undefined,
undefined,
undefined,
true
)
resetRemoteRuntimeMocks()
const store = createTestStore()
const seed = await readReceipt('active')
if (!('kind' in seed) || seed.kind !== 'found') {
throw new Error('expected the initial PR')
}
store.setState({
repos: [
{
id: 'repo',
path: '/repo',
kind: 'git',
executionHostId: 'runtime:env-1',
displayName: 'repo',
badgeColor: 'blue',
addedAt: 0
}
],
worktreesByRepo: {
repo: [
makePRRefreshWorktree({
id: 'receipt-worktree',
repoId: 'repo',
branch: 'receipt',
head: 'head-a',
linkedPR: 12,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
})
]
},
detectedWorktreesByRepo: {},
activeWorktreeId: 'receipt-worktree',
activeWorkspaceExecutionHostId: 'runtime:env-1',
visibleReviewWorktreeIds: ['receipt-worktree'],
prCache: { 'runtime:env-1::repo::receipt': { data: seed.pr, fetchedAt: 1_000 } }
})
const reasons: string[] = []
const replies: unknown[] = []
runtimeEnvironmentCall.mockImplementation(async (args) => {
if (args.method !== 'github.prForBranch') {
throw new Error('unexpected fixture RPC')
}
const params = PrForBranch.parse(args.params)
expect(params).toMatchObject({
repo: 'repo',
branch: 'receipt',
linkedPRNumber: 12,
currentHeadOid: 'head-a',
reason: 'swr',
acceptRevalidationOnly: true,
selected: true
})
reasons.push(params.reason!)
const result = await rpcRead(args.params)
replies.push(result)
return { id: 'rpc', ok: true, result }
})
const timerProvider = Promise.withResolvers<PRRefreshOutcome>()
const freshProvider = Promise.withResolvers<PRRefreshOutcome>()
mocks.resolve
.mockReturnValueOnce(timerProvider.promise)
.mockReturnValueOnce(freshProvider.promise)
const scheduler = createVisibleHostedReviewRefreshScheduler()
const update = () =>
scheduler.update(getVisibleHostedReviewRefreshTargets(store.getState(), store.getState))
const unsubscribe = store.subscribe(update)
try {
expect(getVisibleHostedReviewRefreshTargets(store.getState(), store.getState)).toMatchObject([
{ selected: true, intervalMs: 60_000, fetchedAt: 1_000 }
])
update()
scheduler.setVisible(true)
await vi.advanceTimersByTimeAsync(60_000)
// Fake timers defer the host's nested zero-delay drain to the next clock tick.
await vi.advanceTimersByTimeAsync(1)
expect(reasons).toEqual(['swr'])
expect(replies).toEqual([])
expect(mocks.resolve).toHaveBeenCalledTimes(2)
const otherClient = readReceipt('active')
await vi.advanceTimersByTimeAsync(0)
fixture.title = 'superseded provider result'
timerProvider.resolve(found())
await vi.advanceTimersByTimeAsync(0)
expect(replies).toMatchObject([{ fetchedAt: 1_000, revalidation: { retryAfterMs: 10_000 } }])
expect(store.getState().prCache['runtime:env-1::repo::receipt']?.data?.title).toBe('before')
expect(store.getState().prCache['runtime:env-1::repo::receipt']?.fetchedAt).toBe(1_000)
fixture.title = 'completed by another client'
freshProvider.resolve(found())
await otherClient
await vi.advanceTimersByTimeAsync(9_999)
expect(reasons).toEqual(['swr'])
await vi.advanceTimersByTimeAsync(1)
expect(reasons).toEqual(['swr', 'swr'])
expect(store.getState().prCache['runtime:env-1::repo::receipt']?.data?.title).toBe(
'completed by another client'
)
expect(mocks.resolve).toHaveBeenCalledTimes(3)
} finally {
unsubscribe()
scheduler.dispose()
}
})
})
it('applies data-bearing retry replies to both clients and sends selection after an unselected wait', async () => {
const seed = await commands.getRepoPRForBranch(
'repo',
'shared',
12,
null,
false,
'head-a',
'active'
)
if (!('kind' in seed) || seed.kind !== 'found') {
throw new Error('expected initial PR')
}
const clients: {
select: () => void
title: () => string | undefined
fetchedAt: () => number | undefined
dispose: () => void
}[] = []
const selection: boolean[][] = [[], []]
const receipts: unknown[][] = [[], []]
const reasons: string[] = []
for (let client = 0; client < 2; client += 1) {
// Separate module graphs retain each renderer's own request coalescer.
vi.resetModules()
const {
createTestStore,
makePRRefreshWorktree,
resetRemoteRuntimeMocks,
runtimeEnvironmentCall
} = await import('../../src/renderer/src/store/github/github-slice-test-harness')
const { getVisibleHostedReviewRefreshTargets } =
await import('../../src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-targets')
const { createVisibleHostedReviewRefreshScheduler } =
await import('../../src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-scheduler')
resetRemoteRuntimeMocks()
runtimeEnvironmentCall.mockImplementation(async (args) => {
if (args.method !== 'github.prForBranch') {
throw new Error('unexpected fixture RPC')
}
const { PrForBranch } =
await import('../../src/shared/rpc-contract/github/github-pull-request-params')
const params = PrForBranch.parse(args.params)
const requestClient = 'selector' in args && args.selector === 'env-client-0' ? 0 : 1
reasons.push(params.reason ?? 'missing')
selection[requestClient].push(params.selected === true)
const result = await rpcRead(args.params)
receipts[requestClient].push(result)
return { id: 'rpc', ok: true, result }
})
const store = createTestStore()
store.setState({
repos: [
{
id: 'repo',
path: '/repo',
kind: 'git',
executionHostId: `runtime:env-client-${client}`,
displayName: 'repo',
badgeColor: 'blue',
addedAt: 0
}
],
worktreesByRepo: {
repo: [
makePRRefreshWorktree({
id: 'shared-worktree',
repoId: 'repo',
branch: 'shared',
head: 'head-a',
linkedPR: 12,
hostId: `runtime:env-client-${client}`,
runtimeOwnerEnvironmentId: `env-client-${client}`
})
]
},
detectedWorktreesByRepo: {},
activeWorktreeId: null,
activeWorkspaceExecutionHostId: `runtime:env-client-${client}`,
visibleReviewWorktreeIds: ['shared-worktree'],
prCache: {
[`runtime:env-client-${client}::repo::shared`]: { data: seed.pr, fetchedAt: seed.fetchedAt }
}
})
const scheduler = createVisibleHostedReviewRefreshScheduler()
const update = () =>
scheduler.update(getVisibleHostedReviewRefreshTargets(store.getState(), store.getState))
const unsubscribe = store.subscribe(update)
update()
scheduler.setVisible(true)
clients.push({
select: () => store.setState({ activeWorktreeId: 'shared-worktree' }),
title: () =>
store.getState().prCache[`runtime:env-client-${client}::repo::shared`]?.data?.title,
fetchedAt: () =>
store.getState().prCache[`runtime:env-client-${client}::repo::shared`]?.fetchedAt,
dispose: () => {
unsubscribe()
scheduler.dispose()
}
})
}
try {
// A second client's fresh result can settle in the same millisecond as the first.
fixture.title = 'changed on another client'
await commands.getRepoPRForBranch('repo', 'shared', 12, null, false, 'head-a', 'active')
mocks.gate.mockResolvedValue(181_000)
await vi.advanceTimersByTimeAsync(120_001)
// The first job learns the quota pause after admission, so its wait ends at the response deadline.
await vi.advanceTimersByTimeAsync(25_000)
expect(selection).toEqual([[false], [false]])
for (let client = 0; client < 2; client += 1) {
expect(receipts[client]).toMatchObject([
{
kind: 'found',
pr: { title: 'changed on another client' },
revalidation: { retryAfterMs: expect.any(Number) }
}
])
expect(clients[client].title()).toBe('changed on another client')
expect(clients[client].fetchedAt()).toBe(1_000)
clients[client].select()
}
mocks.gate.mockResolvedValue(null)
await vi.advanceTimersByTimeAsync(70_000)
expect(selection).toEqual([
[false, true],
[false, true]
])
expect(reasons).toEqual(['swr', 'swr', 'swr', 'swr'])
for (const client of clients) {
expect(client.title()).toBe('changed on another client')
expect(client.fetchedAt()).toBe(181_000)
}
expect(mocks.resolve).toHaveBeenCalledTimes(3)
} finally {
for (const client of clients) {
client.dispose()
}
}
})