feat(cli): Display user identity (auth, email, tier) on startup (#17591)

Co-authored-by: Keith Guerin <keithguerin@gmail.com>
Co-authored-by: Yuna Seol <yunaseol@google.com>
This commit is contained in:
Yuna Seol
2026-01-29 00:07:52 -05:00
committed by GitHub
parent 6d36219e55
commit 7f066fd3a7
5 changed files with 287 additions and 2 deletions
+168
View File
@@ -21,6 +21,7 @@ import { act, useContext, type ReactElement } from 'react';
import { AppContainer } from './AppContainer.js';
import { SettingsContext } from './contexts/SettingsContext.js';
import { type TrackedToolCall } from './hooks/useReactToolScheduler.js';
import { MessageType } from './types.js';
import {
type Config,
makeFakeConfig,
@@ -28,6 +29,8 @@ import {
type UserFeedbackPayload,
type ResumedSessionData,
AuthType,
UserAccountManager,
type ContentGeneratorConfig,
type AgentDefinition,
MessageBusType,
QuestionType,
@@ -50,6 +53,11 @@ const mockIdeClient = vi.hoisted(() => ({
getInstance: vi.fn().mockReturnValue(new Promise(() => {})),
}));
// Mock UserAccountManager
const mockUserAccountManager = vi.hoisted(() => ({
getCachedGoogleAccount: vi.fn().mockReturnValue(null),
}));
// Mock stdout
const mocks = vi.hoisted(() => ({
mockStdout: { write: vi.fn() },
@@ -79,6 +87,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
})),
enableMouseEvents: vi.fn(),
disableMouseEvents: vi.fn(),
UserAccountManager: vi
.fn()
.mockImplementation(() => mockUserAccountManager),
FileDiscoveryService: vi.fn().mockImplementation(() => ({
initialize: vi.fn(),
})),
@@ -405,6 +416,7 @@ describe('AppContainer State Management', () => {
...defaultMergedSettings.ui,
showStatusInTitle: false,
hideWindowTitle: false,
showUserIdentity: true,
},
useAlternateBuffer: false,
},
@@ -476,6 +488,162 @@ describe('AppContainer State Management', () => {
});
});
describe('Authentication Check', () => {
it('displays correct message for LOGIN_WITH_GOOGLE auth type', async () => {
// Explicitly mock implementation to ensure we control the instance
(UserAccountManager as unknown as Mock).mockImplementation(
() => mockUserAccountManager,
);
mockUserAccountManager.getCachedGoogleAccount.mockReturnValue(
'test@example.com',
);
const mockAddItem = vi.fn();
mockedUseHistory.mockReturnValue({
history: [],
addItem: mockAddItem,
updateItem: vi.fn(),
clearItems: vi.fn(),
loadHistory: vi.fn(),
});
// Explicitly enable showUserIdentity
mockSettings.merged.ui = {
...mockSettings.merged.ui,
showUserIdentity: true,
};
// Need to ensure config.getContentGeneratorConfig() returns appropriate authType
const authConfig = makeFakeConfig();
// Mock getTargetDir as well since makeFakeConfig might not set it up fully for the component
vi.spyOn(authConfig, 'getTargetDir').mockReturnValue('/test/workspace');
vi.spyOn(authConfig, 'initialize').mockResolvedValue(undefined);
vi.spyOn(authConfig, 'getExtensionLoader').mockReturnValue(
mockExtensionManager,
);
vi.spyOn(authConfig, 'getContentGeneratorConfig').mockReturnValue({
authType: AuthType.LOGIN_WITH_GOOGLE,
} as unknown as ContentGeneratorConfig);
vi.spyOn(authConfig, 'getUserTierName').mockReturnValue('Standard Tier');
let unmount: () => void;
await act(async () => {
const result = renderAppContainer({ config: authConfig });
unmount = result.unmount;
});
await waitFor(() => {
expect(UserAccountManager).toHaveBeenCalled();
expect(
mockUserAccountManager.getCachedGoogleAccount,
).toHaveBeenCalled();
expect(mockAddItem).toHaveBeenCalledWith(
expect.objectContaining({
text: 'Logged in with Google: test@example.com (Plan: Standard Tier)',
}),
);
});
await act(async () => {
unmount!();
});
});
it('displays correct message for USE_GEMINI auth type', async () => {
// Explicitly mock implementation to ensure we control the instance
(UserAccountManager as unknown as Mock).mockImplementation(
() => mockUserAccountManager,
);
mockUserAccountManager.getCachedGoogleAccount.mockReturnValue(null);
const mockAddItem = vi.fn();
mockedUseHistory.mockReturnValue({
history: [],
addItem: mockAddItem,
updateItem: vi.fn(),
clearItems: vi.fn(),
loadHistory: vi.fn(),
});
const authConfig = makeFakeConfig();
vi.spyOn(authConfig, 'getTargetDir').mockReturnValue('/test/workspace');
vi.spyOn(authConfig, 'initialize').mockResolvedValue(undefined);
vi.spyOn(authConfig, 'getExtensionLoader').mockReturnValue(
mockExtensionManager,
);
vi.spyOn(authConfig, 'getContentGeneratorConfig').mockReturnValue({
authType: AuthType.USE_GEMINI,
} as unknown as ContentGeneratorConfig);
vi.spyOn(authConfig, 'getUserTierName').mockReturnValue('Standard Tier');
let unmount: () => void;
await act(async () => {
const result = renderAppContainer({ config: authConfig });
unmount = result.unmount;
});
await waitFor(() => {
expect(mockAddItem).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining('Authenticated with gemini-api-key'),
}),
);
});
await act(async () => {
unmount!();
});
});
it('does not display authentication message if showUserIdentity is false', async () => {
mockUserAccountManager.getCachedGoogleAccount.mockReturnValue(
'test@example.com',
);
const mockAddItem = vi.fn();
mockedUseHistory.mockReturnValue({
history: [],
addItem: mockAddItem,
updateItem: vi.fn(),
clearItems: vi.fn(),
loadHistory: vi.fn(),
});
mockSettings.merged.ui = {
...mockSettings.merged.ui,
showUserIdentity: false,
};
const authConfig = makeFakeConfig();
vi.spyOn(authConfig, 'getTargetDir').mockReturnValue('/test/workspace');
vi.spyOn(authConfig, 'initialize').mockResolvedValue(undefined);
vi.spyOn(authConfig, 'getExtensionLoader').mockReturnValue(
mockExtensionManager,
);
vi.spyOn(authConfig, 'getContentGeneratorConfig').mockReturnValue({
authType: AuthType.LOGIN_WITH_GOOGLE,
} as unknown as ContentGeneratorConfig);
let unmount: () => void;
await act(async () => {
const result = renderAppContainer({ config: authConfig });
unmount = result.unmount;
});
// Give it some time to potentially call addItem
await new Promise((resolve) => setTimeout(resolve, 100));
expect(mockAddItem).not.toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
}),
);
await act(async () => {
unmount!();
});
});
});
describe('Context Providers', () => {
it('provides AppContext with correct values', async () => {
let unmount: () => void;