mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 10:10:56 -07:00
fix(cli): resolve TTY hang on headless environments by unconditionally resuming process.stdin before React Ink launch (#23673)
This commit is contained in:
@@ -860,6 +860,85 @@ describe('oauth2', () => {
|
||||
global.setTimeout = originalSetTimeout;
|
||||
});
|
||||
|
||||
it('should clear the authorization timeout immediately upon successful web login to prevent memory leaks', async () => {
|
||||
const mockAuthUrl = 'https://example.com/auth';
|
||||
const mockCode = 'test-code';
|
||||
const mockState = 'test-state';
|
||||
|
||||
const mockOAuth2Client = {
|
||||
generateAuthUrl: vi.fn().mockReturnValue(mockAuthUrl),
|
||||
getToken: vi.fn().mockResolvedValue({
|
||||
tokens: {
|
||||
access_token: 'test-token',
|
||||
refresh_token: 'test-refresh',
|
||||
},
|
||||
}),
|
||||
setCredentials: vi.fn().mockImplementation(function (
|
||||
this: { credentials?: unknown },
|
||||
creds: unknown,
|
||||
) {
|
||||
this.credentials = creds;
|
||||
}),
|
||||
getAccessToken: vi.fn().mockResolvedValue({ token: 'test-token' }),
|
||||
on: vi.fn(),
|
||||
credentials: {},
|
||||
} as unknown as OAuth2Client;
|
||||
vi.mocked(OAuth2Client).mockImplementation(() => mockOAuth2Client);
|
||||
|
||||
vi.spyOn(crypto, 'randomBytes').mockReturnValue(mockState as never);
|
||||
vi.mocked(open).mockImplementation(
|
||||
async () => ({ on: vi.fn() }) as never,
|
||||
);
|
||||
|
||||
let requestCallback!: http.RequestListener;
|
||||
let serverListeningCallback: (value: unknown) => void;
|
||||
const serverListeningPromise = new Promise(
|
||||
(resolve) => (serverListeningCallback = resolve),
|
||||
);
|
||||
|
||||
const mockHttpServer = {
|
||||
listen: vi.fn(
|
||||
(_port: number, _host: string, callback?: () => void) => {
|
||||
if (callback) callback();
|
||||
serverListeningCallback(undefined);
|
||||
},
|
||||
),
|
||||
close: vi.fn(),
|
||||
on: vi.fn(),
|
||||
address: () => ({ port: 3000 }),
|
||||
};
|
||||
(http.createServer as Mock).mockImplementation((cb) => {
|
||||
requestCallback = cb;
|
||||
return mockHttpServer as unknown as http.Server;
|
||||
});
|
||||
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
const clientPromise = getOauthClient(
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
mockConfig,
|
||||
);
|
||||
await serverListeningPromise;
|
||||
|
||||
const mockReq = {
|
||||
url: `/oauth2callback?code=${mockCode}&state=${mockState}`,
|
||||
} as http.IncomingMessage;
|
||||
const mockRes = {
|
||||
writeHead: vi.fn(),
|
||||
end: vi.fn(),
|
||||
on: vi.fn(),
|
||||
} as unknown as http.ServerResponse;
|
||||
|
||||
// Trigger the successful server response
|
||||
requestCallback(mockReq, mockRes);
|
||||
await clientPromise;
|
||||
|
||||
// Verify that the watchdog timer was cleared correctly
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle OAuth callback errors with descriptive messages', async () => {
|
||||
const mockAuthUrl = 'https://example.com/auth';
|
||||
const mockOAuth2Client = {
|
||||
|
||||
@@ -332,8 +332,9 @@ async function initOauthClient(
|
||||
|
||||
// Add timeout to prevent infinite waiting when browser tab gets stuck
|
||||
const authTimeout = 5 * 60 * 1000; // 5 minutes timeout
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
timeoutId = setTimeout(() => {
|
||||
reject(
|
||||
new FatalAuthenticationError(
|
||||
'Authentication timed out after 5 minutes. The browser tab may have gotten stuck in a loading state. ' +
|
||||
@@ -371,6 +372,9 @@ async function initOauthClient(
|
||||
cancellationPromise,
|
||||
]);
|
||||
} finally {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
if (sigIntHandler) {
|
||||
process.removeListener('SIGINT', sigIntHandler);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user