Loading files
import { describe, it, expect, vi } from 'vitest'
import { createPullRequestReader, githubRepository } from './gitPullRequest.js'
const row = (extra = {}) => ({ number: 12, html_url: 'https://github.com/acme/repo/pull/12', state: 'open', draft: false, merged_at: null, head: { ref: 'feature', repo: { full_name: 'acme/repo' } }, ...extra })
function fixture(rows: unknown = [row()]) {
let branch = 'feature'
const run = vi.fn(async (cmd: string, args: string[]) => cmd === 'git'
? args[0] === 'symbolic-ref' ? branch : 'git@github.com:acme/repo.git'
: JSON.stringify(args.at(-1)?.includes('/pulls?') ? rows : { full_name: 'acme/repo' }))
return { run, read: createPullRequestReader(run), branch: (b: string) => { branch = b } }
}
describe('worktree PR status', () => {
it.each([['https://github.com/acme/repo.git', 'acme/repo'], ['git@github.com:acme/repo.git', 'acme/repo'], ['https://token@github.com/acme/repo', null], ['https://example.com/acme/repo', null]])('validates repository %s', (remote, expected) => expect(githubRepository(remote!)).toBe(expected))
it.each([['Open', {}], ['Draft', { draft: true }], ['Closed', { state: 'closed' }], ['Merged', { state: 'closed', merged_at: '2026-09-23' }]])('reports %s', async (state, extra) => {
expect(await fixture([row(extra)]).read('/worktree')).toMatchObject({ status: 'found', state })
})
it('distinguishes no PR from missing auth/tool/network', async () => {
expect(await fixture([]).read('/worktree')).toEqual({ status: 'none' })
expect(await createPullRequestReader(async () => { throw Error('auth') })('/worktree')).toEqual({ status: 'unavailable' })
})
it('prefers an open PR, filters fork collisions, and refuses untrusted URLs', async () => {
expect(await fixture([row({ state: 'closed', merged_at: 'date' }), row({ draft: true })]).read('/worktree')).toMatchObject({ state: 'Draft' })
expect(await fixture([row({ head: { ref: 'feature', repo: { full_name: 'other/repo' } } })]).read('/worktree')).toEqual({ status: 'none' })
expect(await fixture([row({ html_url: 'https://evil.example/pull/12' })]).read('/worktree')).toEqual({ status: 'unavailable' })
})
it('coalesces lookups and invalidates when the checked-out branch changes', async () => {
const f = fixture()
await Promise.all([f.read('/worktree'), f.read('/worktree')])
expect(f.run.mock.calls.filter(([cmd, args]) => cmd === 'gh' && args.at(-1)?.includes('/pulls?'))).toHaveLength(1)
f.branch('another')
await f.read('/worktree')
expect(f.run.mock.calls.filter(([cmd, args]) => cmd === 'gh' && args.at(-1)?.includes('/pulls?'))).toHaveLength(2)
})
it('uses canonical repository identity after an origin rename', async () => {
const calls: string[][] = []
const read = createPullRequestReader(async (cmd, args) => {
if (cmd === 'git') return args[0] === 'symbolic-ref' ? 'feature' : 'https://github.com/acme/old-name.git'
calls.push(args)
return JSON.stringify(args.at(-1)?.includes('/pulls?') ? [row()] : { full_name: 'acme/repo' })
})
expect(await read('/worktree')).toMatchObject({ status: 'found', url: 'https://github.com/acme/repo/pull/12' })
expect(calls[1].at(-1)).toContain('repos/acme/repo/pulls?')
})
it('does not execute anything for invalid paths', async () => {
const f = fixture()
expect(await f.read('relative')).toEqual({ status: 'unavailable' })
expect(f.run).not.toHaveBeenCalled()
})
})
fc8f8511dThis file is pinned to the selected release. Download the original to open it in your design tool.