mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
On Windows with no host `glab.exe`, the cwd-less `glab auth status` known-hosts
probe fell through to `wsl.exe -d <default distro>`. Probe failures are never
cached, so when that WSL leg also fails (glab absent or logged out inside the
distro) every forge detection re-booted the distro; when it succeeds it cached
that distro's auth hosts under the 'native' execution key, which the comment two
lines above the call already forbids. gitlab-auth-and-rate-limit.ts already
passes allowDefaultWslFallback: false for this exact command; the known-hosts
probe now agrees with it.
Connection-keyed probes keep the fallback: glab has no SSH/relay dispatch, so the
`glab api` calls this gates run the same local CLI with no cwd and would
otherwise disagree with the probe. wsl:<distro>-keyed probes were already
unreachable by the fallback, so passing the flag there is inert.
Counted child_process.execFile calls over 3 sequential probes, process.platform
forced to 'win32', host glab mocked ENOENT (wsl.exe / glab.exe spawns):
native key, glab absent in the distro too: 3/3 -> 0/3
native key, glab logged in in the distro: 1/1 -> 0/3, and that distro's
self-hosted hosts stop reaching the native known-hosts list
connection key: 1/1 -> 1/1, unchanged
Residual risk on that second config: a repo on a \\wsl$\ UNC path can be keyed
'native' (no project runtime match) while its own glab calls still route into
WSL by cwd, so it loses the seeded host and must re-derive it through
`glab auth status --hostname`. A co-resident Windows-path repo on the same host
can now write a shared `native\0<host>` unauthenticated negative that stalls that
recovery for one NEGATIVE_ENTRY_TTL_MS window. Fixing that properly means keying
the cache by the host that actually served the call, which needs an exec-layer
API change and is deliberately out of scope here.
Also splits the getGlabKnownHosts suite out of gl-utils.test.ts (796 counted
lines against the 800-line cap for tests) into gitlab-known-host-probe.test.ts.
294 lines
11 KiB
TypeScript
294 lines
11 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { glabExecFileAsyncMock } = vi.hoisted(() => ({ glabExecFileAsyncMock: vi.fn() }))
|
|
|
|
vi.mock('../git/runner', () => ({ glabExecFileAsync: glabExecFileAsyncMock }))
|
|
|
|
import {
|
|
_resetKnownHostsCache,
|
|
getGlabKnownHosts,
|
|
rememberGlabKnownHost,
|
|
rememberGlabKnownHosts
|
|
} from './gitlab-known-host-probe'
|
|
import { registerSshGitProvider, unregisterSshGitProvider } from '../providers/ssh-git-dispatch'
|
|
|
|
describe('getGlabKnownHosts', () => {
|
|
// A locally-keyed probe must never answer from the default WSL distro.
|
|
const LOCAL_PROBE_OPTIONS = { timeout: 10_000, allowDefaultWslFallback: false }
|
|
|
|
beforeEach(() => {
|
|
glabExecFileAsyncMock.mockReset()
|
|
_resetKnownHostsCache()
|
|
})
|
|
|
|
it('returns gitlab.com plus auth-status hosts, deduped', async () => {
|
|
glabExecFileAsyncMock.mockResolvedValueOnce({
|
|
stdout: '✓ Logged in to gitlab.com as user\n✓ Logged in to gitlab.example.com as user\n',
|
|
stderr: ''
|
|
})
|
|
|
|
await expect(getGlabKnownHosts()).resolves.toEqual(['gitlab.com', 'gitlab.example.com'])
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledWith(['auth', 'status'], LOCAL_PROBE_OPTIONS)
|
|
})
|
|
|
|
it('preserves WSL and background admission on the cold auth-status probe', async () => {
|
|
glabExecFileAsyncMock.mockResolvedValueOnce({ stdout: '', stderr: '' })
|
|
|
|
await getGlabKnownHosts(undefined, {
|
|
wslDistro: 'Ubuntu',
|
|
admissionTier: 'background'
|
|
})
|
|
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledWith(['auth', 'status'], {
|
|
...LOCAL_PROBE_OPTIONS,
|
|
wslDistro: 'Ubuntu',
|
|
admissionTier: 'background'
|
|
})
|
|
})
|
|
|
|
it('falls back to default when glab auth status fails', async () => {
|
|
glabExecFileAsyncMock.mockRejectedValueOnce(new Error('glab not authenticated'))
|
|
|
|
await expect(getGlabKnownHosts()).resolves.toEqual(['gitlab.com'])
|
|
})
|
|
|
|
it('caches the result across calls', async () => {
|
|
glabExecFileAsyncMock.mockResolvedValueOnce({
|
|
stdout: '✓ Logged in to gitlab.com as user\n',
|
|
stderr: ''
|
|
})
|
|
|
|
await getGlabKnownHosts()
|
|
await getGlabKnownHosts()
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('coalesces many simultaneous callers in one execution context', async () => {
|
|
let resolveProbe!: (value: { stdout: string; stderr: string }) => void
|
|
glabExecFileAsyncMock.mockImplementationOnce(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
resolveProbe = resolve
|
|
})
|
|
)
|
|
|
|
const probes = Array.from({ length: 64 }, () => getGlabKnownHosts())
|
|
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(1)
|
|
resolveProbe({ stdout: 'Logged in to gitlab.concurrent.test as user\n', stderr: '' })
|
|
const results = await Promise.all(probes)
|
|
expect(results.every((result) => result === results[0])).toBe(true)
|
|
expect(results[0]).toEqual(['gitlab.com', 'gitlab.concurrent.test'])
|
|
})
|
|
|
|
it('keeps simultaneous native, WSL distro, and connection probes isolated', async () => {
|
|
glabExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to ubuntu.test as user\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to debian.test as user\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to native.test as user\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to ssh.test as user\n', stderr: '' })
|
|
|
|
const [ubuntu, ubuntuAgain, debian, native, ssh] = await Promise.all([
|
|
getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' }),
|
|
getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' }),
|
|
getGlabKnownHosts(undefined, { wslDistro: 'Debian' }),
|
|
getGlabKnownHosts(),
|
|
getGlabKnownHosts('conn-1')
|
|
])
|
|
|
|
expect(ubuntuAgain).toBe(ubuntu)
|
|
expect(ubuntu).toEqual(['gitlab.com', 'ubuntu.test'])
|
|
expect(debian).toEqual(['gitlab.com', 'debian.test'])
|
|
expect(native).toEqual(['gitlab.com', 'native.test'])
|
|
expect(ssh).toEqual(['gitlab.com', 'ssh.test'])
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(4)
|
|
expect(glabExecFileAsyncMock).toHaveBeenNthCalledWith(1, ['auth', 'status'], {
|
|
...LOCAL_PROBE_OPTIONS,
|
|
wslDistro: 'Ubuntu'
|
|
})
|
|
expect(glabExecFileAsyncMock).toHaveBeenNthCalledWith(2, ['auth', 'status'], {
|
|
...LOCAL_PROBE_OPTIONS,
|
|
wslDistro: 'Debian'
|
|
})
|
|
})
|
|
|
|
it('preserves a native auth refresh while an older native probe is in flight', async () => {
|
|
let resolveProbe!: (value: { stdout: string; stderr: string }) => void
|
|
glabExecFileAsyncMock.mockImplementationOnce(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
resolveProbe = resolve
|
|
})
|
|
)
|
|
|
|
const staleProbe = getGlabKnownHosts()
|
|
rememberGlabKnownHost('gitlab.refreshed.test')
|
|
resolveProbe({ stdout: 'Logged in to gitlab.com as user\n', stderr: '' })
|
|
|
|
await expect(staleProbe).resolves.toEqual(['gitlab.com', 'gitlab.refreshed.test'])
|
|
await expect(getGlabKnownHosts()).resolves.toEqual(['gitlab.com', 'gitlab.refreshed.test'])
|
|
})
|
|
|
|
it('preserves a native auth refresh when an older native probe fails', async () => {
|
|
let rejectProbe!: (error: Error) => void
|
|
glabExecFileAsyncMock.mockImplementationOnce(
|
|
() =>
|
|
new Promise((_resolve, reject) => {
|
|
rejectProbe = reject
|
|
})
|
|
)
|
|
|
|
const staleProbe = getGlabKnownHosts()
|
|
rememberGlabKnownHost('gitlab.refreshed.test')
|
|
rejectProbe(new Error('stale auth probe failed'))
|
|
|
|
await expect(staleProbe).resolves.toEqual(['gitlab.com', 'gitlab.refreshed.test'])
|
|
await expect(getGlabKnownHosts()).resolves.toEqual(['gitlab.com', 'gitlab.refreshed.test'])
|
|
})
|
|
|
|
it('keeps a remembered native host out of WSL and SSH caches', async () => {
|
|
glabExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to native.test as user\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to wsl.test as user\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to ssh.test as user\n', stderr: '' })
|
|
|
|
await Promise.all([
|
|
getGlabKnownHosts(),
|
|
getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' }),
|
|
getGlabKnownHosts('conn-1')
|
|
])
|
|
rememberGlabKnownHost('gitlab.refreshed.test')
|
|
|
|
await expect(getGlabKnownHosts()).resolves.toEqual([
|
|
'gitlab.com',
|
|
'native.test',
|
|
'gitlab.refreshed.test'
|
|
])
|
|
await expect(getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' })).resolves.toEqual([
|
|
'gitlab.com',
|
|
'wsl.test'
|
|
])
|
|
await expect(getGlabKnownHosts('conn-1')).resolves.toEqual(['gitlab.com', 'ssh.test'])
|
|
})
|
|
|
|
it('batch-normalizes and deduplicates hosts in first-seen order per execution context', async () => {
|
|
rememberGlabKnownHosts([' Native-B.test ', 'native-a.test', 'NATIVE-B.TEST'])
|
|
rememberGlabKnownHosts(['WSL-B.test', ' wsl-a.test ', 'wsl-b.test'], undefined, {
|
|
wslDistro: 'Ubuntu'
|
|
})
|
|
rememberGlabKnownHosts(['SSH-B.test', 'ssh-a.test', ' ssh-b.test '], 'conn-batch')
|
|
|
|
await expect(getGlabKnownHosts()).resolves.toEqual([
|
|
'gitlab.com',
|
|
'native-b.test',
|
|
'native-a.test'
|
|
])
|
|
await expect(getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' })).resolves.toEqual([
|
|
'gitlab.com',
|
|
'wsl-b.test',
|
|
'wsl-a.test'
|
|
])
|
|
await expect(getGlabKnownHosts('conn-batch')).resolves.toEqual([
|
|
'gitlab.com',
|
|
'ssh-b.test',
|
|
'ssh-a.test'
|
|
])
|
|
expect(glabExecFileAsyncMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('recognizes a self-hosted host on a non-default port', async () => {
|
|
glabExecFileAsyncMock.mockResolvedValueOnce({
|
|
stdout: '✓ Logged in to gitlab.example.com:8080 as user\n',
|
|
stderr: ''
|
|
})
|
|
|
|
await expect(getGlabKnownHosts()).resolves.toEqual(['gitlab.com', 'gitlab.example.com:8080'])
|
|
})
|
|
|
|
it('caches per connection — the local probe does not satisfy a connection probe', async () => {
|
|
glabExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: '✓ Logged in to gitlab.com as user\n', stderr: '' })
|
|
.mockResolvedValueOnce({
|
|
stdout: '✓ Logged in to gitlab.example.com:8080 as user\n',
|
|
stderr: ''
|
|
})
|
|
|
|
await expect(getGlabKnownHosts()).resolves.toEqual(['gitlab.com'])
|
|
await expect(getGlabKnownHosts('conn-1')).resolves.toEqual([
|
|
'gitlab.com',
|
|
'gitlab.example.com:8080'
|
|
])
|
|
// A second probe for the same connection is served from cache.
|
|
await expect(getGlabKnownHosts('conn-1')).resolves.toEqual([
|
|
'gitlab.com',
|
|
'gitlab.example.com:8080'
|
|
])
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('does not permanently cache the failure fallback — a later probe can re-discover hosts', async () => {
|
|
glabExecFileAsyncMock
|
|
.mockRejectedValueOnce(new Error('ssh tunnel not ready'))
|
|
.mockResolvedValueOnce({
|
|
stdout: '✓ Logged in to gitlab.example.com:8080 as user\n',
|
|
stderr: ''
|
|
})
|
|
|
|
// First probe fails → canonical default, NOT cached.
|
|
await expect(getGlabKnownHosts('conn-1')).resolves.toEqual(['gitlab.com'])
|
|
// Re-probe (e.g. after tunnel comes up) discovers the real host.
|
|
await expect(getGlabKnownHosts('conn-1')).resolves.toEqual([
|
|
'gitlab.com',
|
|
'gitlab.example.com:8080'
|
|
])
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('removes a timed-out probe from in-flight state so a later call retries', async () => {
|
|
let rejectProbe!: (error: Error) => void
|
|
glabExecFileAsyncMock
|
|
.mockImplementationOnce(
|
|
() =>
|
|
new Promise((_resolve, reject) => {
|
|
rejectProbe = reject
|
|
})
|
|
)
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to recovered.test as user\n', stderr: '' })
|
|
|
|
const first = getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' })
|
|
const concurrent = getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' })
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(1)
|
|
rejectProbe(new Error('wsl.exe timed out.'))
|
|
|
|
await expect(Promise.all([first, concurrent])).resolves.toEqual([
|
|
['gitlab.com'],
|
|
['gitlab.com']
|
|
])
|
|
await expect(getGlabKnownHosts(undefined, { wslDistro: 'Ubuntu' })).resolves.toEqual([
|
|
'gitlab.com',
|
|
'recovered.test'
|
|
])
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('does not reuse a successful result after an SSH provider reconnects', async () => {
|
|
const connectionId = 'conn-reconnected'
|
|
registerSshGitProvider(connectionId, {} as never)
|
|
glabExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to old-tunnel.test as user\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'Logged in to new-tunnel.test as user\n', stderr: '' })
|
|
|
|
await expect(getGlabKnownHosts(connectionId)).resolves.toEqual([
|
|
'gitlab.com',
|
|
'old-tunnel.test'
|
|
])
|
|
registerSshGitProvider(connectionId, {} as never)
|
|
await expect(getGlabKnownHosts(connectionId)).resolves.toEqual([
|
|
'gitlab.com',
|
|
'new-tunnel.test'
|
|
])
|
|
expect(glabExecFileAsyncMock).toHaveBeenCalledTimes(2)
|
|
unregisterSshGitProvider(connectionId)
|
|
})
|
|
})
|