Files
orca/src/shared/github/account-binding.test.ts
T
c34b944136 feat(github): bind projects to a specific gh account (#13664)
* feat(github): bind projects to a specific gh account

Adds per-project `Repo.ghAccount` so repo-scoped gh calls (create-worktree
issue/PR search, work items, hosted-review reads and mutations) run as the bound
account via ephemeral child-env token injection instead of the globally active
gh login. Multi-account resolution is capability-gated (gh >= 2.40) and fails
closed when the bound account or host is unavailable; Project View stays
ambient by design.

Repository settings gains a section for selecting or clearing a keyring-backed
account (shadcn `Select`), with mixed-version "not enforced" handling for older
remote runtimes. Attached `-Rhost/owner/repo` forms are covered by the host-drift
guard and its tests; es/ja/ko/zh catalogs carry the section's strings.

`getLocalProjectGhExecOptions` centralizes the binding lookup so every gh
execution path picks it up, including the Electron `hostedReview:*` handlers
that previously stayed on the ambient login. `gh auth token` (a keyring read)
is exempt from the rate-limit breaker gate so a tripped bucket cannot turn a
bound-token resolve into a false "unavailable".

The `ghAccount` update field and the two binding RPC methods live in the shared
RPC params contract; the generated catalog is regenerated.

Fixes #13612

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012B3QEP5iP4WGGEpPLtkHqA

* fix(settings): make GitHub account refresh secondary

* fix(github): satisfy strict casting quality checks

* test(rpc): use runtime fixture for repo binding

* fix(github): preserve project account for PR worktree lookups

* test(rpc): avoid incomplete runtime settings fixture

* fix(i18n): add GitHub account refresh label

* fix(i18n): refresh runtime required catalog

* fix(windows): preserve mobile patch bytes

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
2026-09-18 20:39:52 -07:00

65 lines
2.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
ghAccountBindingsEqual,
ghTokenEnvVarForHost,
normalizeGhAccountBinding
} from './account-binding'
describe('normalizeGhAccountBinding', () => {
it('trims, lowercases host, and preserves user case', () => {
expect(normalizeGhAccountBinding({ host: ' GitHub.COM ', user: ' Alice ' })).toEqual({
host: 'github.com',
user: 'Alice'
})
})
it('rejects malformed shapes', () => {
expect(normalizeGhAccountBinding(null)).toBeNull()
expect(normalizeGhAccountBinding({ host: '', user: 'a' })).toBeNull()
expect(normalizeGhAccountBinding({ host: 'github.com', user: '' })).toBeNull()
expect(normalizeGhAccountBinding({ host: 'bad host', user: 'a' })).toBeNull()
expect(normalizeGhAccountBinding({ host: 'github.com', user: 'has space' })).toBeNull()
})
it('accepts GHES/LDAP logins with underscores and dots', () => {
expect(normalizeGhAccountBinding({ host: 'github.acme.com', user: 'svc_build' })).toEqual({
host: 'github.acme.com',
user: 'svc_build'
})
expect(normalizeGhAccountBinding({ host: 'github.acme.com', user: 'first.last' })).toEqual({
host: 'github.acme.com',
user: 'first.last'
})
})
})
describe('ghTokenEnvVarForHost', () => {
it('selects GH_TOKEN for github.com, github.localhost, and *.ghe.com', () => {
expect(ghTokenEnvVarForHost('github.com')).toBe('GH_TOKEN')
expect(ghTokenEnvVarForHost('github.localhost')).toBe('GH_TOKEN')
expect(ghTokenEnvVarForHost('acme.ghe.com')).toBe('GH_TOKEN')
})
it('selects GH_ENTERPRISE_TOKEN for classic GHES hosts', () => {
expect(ghTokenEnvVarForHost('github.acme-corp.com')).toBe('GH_ENTERPRISE_TOKEN')
expect(ghTokenEnvVarForHost('ghe.internal')).toBe('GH_ENTERPRISE_TOKEN')
})
})
describe('ghAccountBindingsEqual', () => {
it('compares normalized host/user pairs', () => {
expect(
ghAccountBindingsEqual(
{ host: 'github.com', user: 'Alice' },
{ host: 'github.com', user: 'Alice' }
)
).toBe(true)
expect(
ghAccountBindingsEqual(
{ host: 'github.com', user: 'Alice' },
{ host: 'github.com', user: 'alice' }
)
).toBe(false)
})
})