mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 20:14:44 -07:00
Add interactive ValidationDialog for handling 403 VALIDATION_REQUIRED errors. (#16231)
This commit is contained in:
@@ -498,4 +498,186 @@ To disable gemini-3-pro-preview, disable "Preview features" in /settings.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Validation Handler', () => {
|
||||
let setValidationHandlerSpy: SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
setValidationHandlerSpy = vi.spyOn(mockConfig, 'setValidationHandler');
|
||||
});
|
||||
|
||||
it('should register a validation handler on initialization', () => {
|
||||
renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(setValidationHandlerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(setValidationHandlerSpy.mock.calls[0][0]).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('should set a validation request when handler is called', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
}),
|
||||
);
|
||||
|
||||
const handler = setValidationHandlerSpy.mock.calls[0][0] as (
|
||||
validationLink?: string,
|
||||
validationDescription?: string,
|
||||
learnMoreUrl?: string,
|
||||
) => Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
|
||||
let promise: Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
act(() => {
|
||||
promise = handler(
|
||||
'https://example.com/verify',
|
||||
'Please verify',
|
||||
'https://example.com/help',
|
||||
);
|
||||
});
|
||||
|
||||
const request = result.current.validationRequest;
|
||||
expect(request).not.toBeNull();
|
||||
expect(request?.validationLink).toBe('https://example.com/verify');
|
||||
expect(request?.validationDescription).toBe('Please verify');
|
||||
expect(request?.learnMoreUrl).toBe('https://example.com/help');
|
||||
|
||||
// Simulate user choosing verify
|
||||
act(() => {
|
||||
result.current.handleValidationChoice('verify');
|
||||
});
|
||||
|
||||
const intent = await promise!;
|
||||
expect(intent).toBe('verify');
|
||||
expect(result.current.validationRequest).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle race conditions by returning cancel for subsequent requests', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
}),
|
||||
);
|
||||
|
||||
const handler = setValidationHandlerSpy.mock.calls[0][0] as (
|
||||
validationLink?: string,
|
||||
) => Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
|
||||
let promise1: Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
act(() => {
|
||||
promise1 = handler('https://example.com/verify1');
|
||||
});
|
||||
|
||||
const firstRequest = result.current.validationRequest;
|
||||
expect(firstRequest).not.toBeNull();
|
||||
|
||||
let result2: 'verify' | 'change_auth' | 'cancel';
|
||||
await act(async () => {
|
||||
result2 = await handler('https://example.com/verify2');
|
||||
});
|
||||
|
||||
// The lock should have stopped the second request
|
||||
expect(result2!).toBe('cancel');
|
||||
expect(result.current.validationRequest).toBe(firstRequest);
|
||||
|
||||
// Complete the first request
|
||||
act(() => {
|
||||
result.current.handleValidationChoice('verify');
|
||||
});
|
||||
|
||||
const intent1 = await promise1!;
|
||||
expect(intent1).toBe('verify');
|
||||
expect(result.current.validationRequest).toBeNull();
|
||||
});
|
||||
|
||||
it('should add info message when change_auth is chosen', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
}),
|
||||
);
|
||||
|
||||
const handler = setValidationHandlerSpy.mock.calls[0][0] as (
|
||||
validationLink?: string,
|
||||
) => Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
|
||||
let promise: Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
act(() => {
|
||||
promise = handler('https://example.com/verify');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.handleValidationChoice('change_auth');
|
||||
});
|
||||
|
||||
const intent = await promise!;
|
||||
expect(intent).toBe('change_auth');
|
||||
|
||||
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(1);
|
||||
const lastCall = (mockHistoryManager.addItem as Mock).mock.calls[0][0];
|
||||
expect(lastCall.type).toBe(MessageType.INFO);
|
||||
expect(lastCall.text).toBe('Use /auth to change authentication method.');
|
||||
});
|
||||
|
||||
it('should not add info message when cancel is chosen', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
}),
|
||||
);
|
||||
|
||||
const handler = setValidationHandlerSpy.mock.calls[0][0] as (
|
||||
validationLink?: string,
|
||||
) => Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
|
||||
let promise: Promise<'verify' | 'change_auth' | 'cancel'>;
|
||||
act(() => {
|
||||
promise = handler('https://example.com/verify');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.handleValidationChoice('cancel');
|
||||
});
|
||||
|
||||
const intent = await promise!;
|
||||
expect(intent).toBe('cancel');
|
||||
|
||||
expect(mockHistoryManager.addItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing if handleValidationChoice is called without pending request', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQuotaAndFallback({
|
||||
config: mockConfig,
|
||||
historyManager: mockHistoryManager,
|
||||
userTier: UserTierId.FREE,
|
||||
setModelSwitchedFromQuotaError: mockSetModelSwitchedFromQuotaError,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleValidationChoice('verify');
|
||||
});
|
||||
|
||||
expect(mockHistoryManager.addItem).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user