Files
openchamber/packages/web/server/lib/github/gh-cli-credential.test.js
T
Tom Rochette 33e614c76b Fallback to gh CLI credentials if available (#1515)
Adds `gh` CLI as a GitHub credential fallback for users who already have
`gh auth login` configured locally. OpenChamber-owned OAuth credentials
remain the primary source of truth; the `gh` token is only used when no
stored OpenChamber GitHub access token exists and the fallback is not
disabled.

The fallback is implemented as a credential provider only: GitHub features
continue to use the existing Octokit/GitHub API paths for issues, pull
requests, checks, merges, and related operations. The PR does not replace
those endpoints with `gh issue` or `gh pr` CLI commands.

Server changes:
- Add `gh-cli-credential.js` to read `gh auth token` with a bounded timeout.
- Cache the `gh` token lookup for 30 seconds, including negative results,
  to avoid repeated subprocess spawning on status/polling paths.
- Hide the subprocess window on Windows via `windowsHide: true`.
- Clear the gh CLI token cache when the fallback setting changes.
- Update `getOctokitOrNull()` to prefer stored OpenChamber OAuth tokens and
  fall back to the `gh` token only when enabled.
- Add `ghCliDisabled` persistence in the existing settings file with atomic
  writes and `0o600` file permissions.
- Add `POST /api/github/auth/gh-cli` to enable or disable the fallback.
- Extend `/api/github/auth/status` with `ghCli` metadata: availability,
  disabled state, active state, and active user when applicable.

UI/runtime changes:
- Extend `GitHubAuthStatus` and `GitHubAPI` with gh CLI fallback metadata
  and toggle support.
- Add web RuntimeAPI support for toggling the gh CLI fallback through
  `runtimeFetch`, preserving active runtime/remote target behavior.
- Add deterministic VS Code unsupported handling for the gh CLI toggle.
- Update GitHub Settings to show gh CLI availability and active status.
- When gh CLI is the active auth source, show it in the connected account
  card and offer Disable instead of Disconnect.
- Keep Add Account available so users can still connect an OpenChamber OAuth
  account, which then takes priority over gh CLI.
- Add localized gh CLI settings strings across supported settings locales.

Fixes addressed during review:
- Removed unreachable UI branches in the inactive gh CLI card.
- Avoided duplicate and repeated `gh auth token` subprocess calls.
- Hardened settings file permissions for the new persisted flag.
- Routed the gh CLI toggle through the RuntimeAPI/runtimeFetch path instead
  of direct browser `fetch`.
- Added targeted tests for hidden subprocess options and negative-result
  cache behavior.
- Fixed a VS Code webview Response body typing issue that blocked type-check.
2026-06-11 18:43:41 +03:00

44 lines
1.2 KiB
JavaScript

import { beforeEach, describe, expect, mock, test } from 'bun:test';
const execFileSyncMock = mock(() => '');
mock.module('child_process', () => ({
execFileSync: execFileSyncMock,
}));
const { clearGhCliTokenCache, getGhCliToken } = await import('./gh-cli-credential.js');
describe('gh CLI credential lookup', () => {
beforeEach(() => {
execFileSyncMock.mockReset();
clearGhCliTokenCache();
});
test('hides the subprocess window on Windows', () => {
execFileSyncMock.mockReturnValueOnce('token\n');
expect(getGhCliToken()).toBe('token');
expect(execFileSyncMock).toHaveBeenCalledWith('gh', ['auth', 'token'], {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 5000,
windowsHide: true,
});
});
test('caches unavailable gh CLI result until cache is cleared', () => {
execFileSyncMock.mockImplementation(() => {
throw new Error('gh unavailable');
});
expect(getGhCliToken()).toBeNull();
expect(getGhCliToken()).toBeNull();
expect(execFileSyncMock).toHaveBeenCalledTimes(1);
clearGhCliTokenCache();
expect(getGhCliToken()).toBeNull();
expect(execFileSyncMock).toHaveBeenCalledTimes(2);
});
});