Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion src/__tests__/gf-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,16 @@ vi.mock('node:fs', () => ({
},
}));

import os from 'node:os';
import { spawnSync } from 'node:child_process';
import { execSync } from 'node:child_process';
import { gfGetOAuthToken, gfMrCreate } from '../providers/tgit/gf-cli.js';
import {
gfGetOAuthToken,
gfMrCreate,
gfAuthWhoami,
gfIsAuthenticated,
gfAuthLogin,
} from '../providers/tgit/gf-cli.js';

describe('gfGetOAuthToken', () => {
beforeEach(() => {
Expand Down Expand Up @@ -219,3 +226,59 @@ describe('gfMrCreate', () => {
).toThrow('gf mr create failed: auth required');
});
});

describe('gf auth commands run from a neutral cwd', () => {
const mockSpawnSync = vi.mocked(spawnSync);
const mockExecSync = vi.mocked(execSync);

beforeEach(() => {
vi.clearAllMocks();
// Make getGfPath() find gf in PATH
mockExecSync.mockImplementation((cmd: string) => {
if (cmd.includes('test -x')) throw new Error('not found');
if (cmd === 'which gf') return '/usr/bin/gf' as any;
throw new Error('unexpected');
});
});

// gf `auth` commands inspect the current git repo's origin remote and scope
// the auth check to that host. Running them from the repo cwd wrongly reports
// "not logged in" when origin is a non-git.woa.com mirror (e.g. GitHub). They
// must run from a neutral, non-git directory (os.tmpdir()) so the host-scoped
// credential is found regardless of where teamai was invoked.
it('gfAuthWhoami spawns with cwd = os.tmpdir()', () => {
mockSpawnSync.mockReturnValue({
stdout: '当前登录用户:jeffyxu',
stderr: '',
status: 0,
} as any);

expect(gfAuthWhoami()).toBe('jeffyxu');

const opts = mockSpawnSync.mock.calls[0][2] as { cwd?: string };
expect(opts.cwd).toBe(os.tmpdir());
});

it('gfIsAuthenticated spawns with cwd = os.tmpdir()', () => {
mockSpawnSync.mockReturnValue({
stdout: '当前登录用户:jeffyxu',
stderr: '',
status: 0,
} as any);

expect(gfIsAuthenticated()).toBe(true);

const opts = mockSpawnSync.mock.calls[0][2] as { cwd?: string };
expect(opts.cwd).toBe(os.tmpdir());
});

it('gfAuthLogin spawns with cwd = os.tmpdir() and inherited stdio', () => {
mockSpawnSync.mockReturnValue({ status: 0 } as any);

gfAuthLogin();

const opts = mockSpawnSync.mock.calls[0][2] as { cwd?: string; stdio?: string };
expect(opts.cwd).toBe(os.tmpdir());
expect(opts.stdio).toBe('inherit');
});
});
21 changes: 18 additions & 3 deletions src/providers/tgit/gf-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,27 @@ export async function ensureGfInstalled(): Promise<void> {

// ─── Authentication ──────────────────────────────────────

/**
* A neutral working directory for `gf auth` commands.
*
* `gf auth whoami` / `gf auth login` inspect the *current* git repository's
* `origin` remote and scope the authentication check to that remote's host.
* When teamai runs inside a repo whose origin is not git.woa.com (e.g. a GitHub
* mirror), gf reports "not logged in" even though a valid git.woa.com token
* exists — so teamai wrongly re-triggers interactive login and fails.
*
* Running these commands from the system temp dir (which is not a git repo)
* removes the cwd dependency, so the host-scoped credential is found reliably
* regardless of where teamai was invoked.
*/
const AUTH_CWD = os.tmpdir();

/**
* Check if gf is authenticated. Returns true if `gf auth whoami` succeeds.
*/
export function gfIsAuthenticated(): boolean {
try {
const result = gfExec(['auth', 'whoami']);
const result = gfExec(['auth', 'whoami'], { cwd: AUTH_CWD });
return result.status === 0 && result.stdout.includes('当前登录用户');
} catch {
return false;
Expand All @@ -189,7 +204,7 @@ export function gfIsAuthenticated(): boolean {
*/
export function gfAuthWhoami(): string | null {
try {
const result = gfExec(['auth', 'whoami']);
const result = gfExec(['auth', 'whoami'], { cwd: AUTH_CWD });
if (result.status !== 0) return null;

// Parse "当前登录用户:<username>" or similar
Expand All @@ -207,7 +222,7 @@ export function gfAuthWhoami(): string | null {
*/
export function gfAuthLogin(): void {
log.info('Starting gf authentication...');
const result = gfExec(['auth', 'login'], { inheritStdio: true });
const result = gfExec(['auth', 'login'], { inheritStdio: true, cwd: AUTH_CWD });
if (result.status !== 0) {
throw new Error('gf auth login failed. Please try again.');
}
Expand Down
Loading