2025-09-08 16:19:52 -04:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
import {
vi ,
describe ,
it ,
expect ,
beforeEach ,
afterEach ,
type Mock ,
} from 'vitest' ;
2025-10-28 10:32:15 -07:00
import { act } from 'react' ;
import { renderHook } from '../../test-utils/render.js' ;
2025-09-08 16:19:52 -04:00
import {
type Config ,
type FallbackModelHandler ,
2025-10-30 11:50:26 -07:00
type FallbackIntent ,
2025-09-08 16:19:52 -04:00
UserTierId ,
AuthType ,
2025-10-24 11:09:06 -07:00
TerminalQuotaError ,
2025-09-08 16:19:52 -04:00
makeFakeConfig ,
2025-10-24 11:09:06 -07:00
type GoogleApiError ,
RetryableQuotaError ,
2025-11-18 12:01:16 -05:00
PREVIEW_GEMINI_MODEL ,
ModelNotFoundError ,
2025-11-25 16:17:22 -05:00
DEFAULT_GEMINI_MODEL ,
DEFAULT_GEMINI_FLASH_MODEL ,
2025-09-08 16:19:52 -04:00
} from '@google/gemini-cli-core' ;
import { useQuotaAndFallback } from './useQuotaAndFallback.js' ;
import type { UseHistoryManagerReturn } from './useHistoryManager.js' ;
2025-11-06 13:43:21 -05:00
import { MessageType } from '../types.js' ;
2025-09-08 16:19:52 -04:00
// Use a type alias for SpyInstance as it's not directly exported
type SpyInstance = ReturnType < typeof vi.spyOn > ;
describe ( 'useQuotaAndFallback' , ( ) = > {
let mockConfig : Config ;
let mockHistoryManager : UseHistoryManagerReturn ;
let mockSetModelSwitchedFromQuotaError : Mock ;
let setFallbackHandlerSpy : SpyInstance ;
2025-10-24 11:09:06 -07:00
let mockGoogleApiError : GoogleApiError ;
2025-09-08 16:19:52 -04:00
beforeEach ( ( ) = > {
mockConfig = makeFakeConfig ( ) ;
2025-10-24 11:09:06 -07:00
mockGoogleApiError = {
code : 429 ,
message : 'mock error' ,
details : [ ] ,
} ;
2025-09-08 16:19:52 -04:00
// Spy on the method that requires the private field and mock its return.
// This is cleaner than modifying the config class for tests.
vi . spyOn ( mockConfig , 'getContentGeneratorConfig' ) . mockReturnValue ( {
authType : AuthType.LOGIN_WITH_GOOGLE ,
} ) ;
mockHistoryManager = {
addItem : vi.fn ( ) ,
history : [ ] ,
updateItem : vi.fn ( ) ,
clearItems : vi.fn ( ) ,
loadHistory : vi.fn ( ) ,
} ;
mockSetModelSwitchedFromQuotaError = vi . fn ( ) ;
setFallbackHandlerSpy = vi . spyOn ( mockConfig , 'setFallbackModelHandler' ) ;
vi . spyOn ( mockConfig , 'setQuotaErrorOccurred' ) ;
2025-12-26 11:08:44 -05:00
vi . spyOn ( mockConfig , 'setModel' ) ;
2025-09-08 16:19:52 -04:00
} ) ;
afterEach ( ( ) = > {
vi . clearAllMocks ( ) ;
} ) ;
it ( 'should register a fallback handler on initialization' , ( ) = > {
renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
expect ( setFallbackHandlerSpy ) . toHaveBeenCalledTimes ( 1 ) ;
expect ( setFallbackHandlerSpy . mock . calls [ 0 ] [ 0 ] ) . toBeInstanceOf ( Function ) ;
} ) ;
describe ( 'Fallback Handler Logic' , ( ) = > {
// Helper function to render the hook and extract the registered handler
2025-11-18 12:01:16 -05:00
const getRegisteredHandler = ( ) : FallbackModelHandler = > {
renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
2025-09-08 16:19:52 -04:00
) ;
return setFallbackHandlerSpy . mock . calls [ 0 ] [ 0 ] as FallbackModelHandler ;
} ;
it ( 'should return null and take no action if authType is not LOGIN_WITH_GOOGLE' , async ( ) = > {
// Override the default mock from beforeEach for this specific test
vi . spyOn ( mockConfig , 'getContentGeneratorConfig' ) . mockReturnValue ( {
authType : AuthType.USE_GEMINI ,
} ) ;
const handler = getRegisteredHandler ( ) ;
const result = await handler ( 'gemini-pro' , 'gemini-flash' , new Error ( ) ) ;
expect ( result ) . toBeNull ( ) ;
expect ( mockHistoryManager . addItem ) . not . toHaveBeenCalled ( ) ;
} ) ;
2025-11-06 13:43:21 -05:00
describe ( 'Interactive Fallback' , ( ) = > {
2025-11-18 12:01:16 -05:00
it ( 'should set an interactive request for a terminal quota error' , async ( ) = > {
2025-09-08 16:19:52 -04:00
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
2025-10-30 11:50:26 -07:00
let promise : Promise < FallbackIntent | null > ;
2025-11-18 12:01:16 -05:00
const error = new TerminalQuotaError (
'pro quota' ,
mockGoogleApiError ,
1000 * 60 * 5 ,
) ; // 5 minutes
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
promise = handler ( 'gemini-pro' , 'gemini-flash' , error ) ;
2025-10-30 11:50:26 -07:00
} ) ;
2025-09-08 16:19:52 -04:00
// The hook should now have a pending request for the UI to handle
2025-11-18 12:01:16 -05:00
const request = result . current . proQuotaRequest ;
expect ( request ) . not . toBeNull ( ) ;
expect ( request ? . failedModel ) . toBe ( 'gemini-pro' ) ;
expect ( request ? . isTerminalQuotaError ) . toBe ( true ) ;
const message = request ! . message ;
expect ( message ) . toContain ( 'Usage limit reached for gemini-pro.' ) ;
expect ( message ) . toContain ( 'Access resets at' ) ; // From getResetTimeMessage
expect ( message ) . toContain ( '/stats for usage details' ) ;
expect ( message ) . toContain ( '/auth to switch to API key.' ) ;
expect ( mockHistoryManager . addItem ) . not . toHaveBeenCalled ( ) ;
2025-09-08 16:19:52 -04:00
// Simulate the user choosing to continue with the fallback model
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
result . current . handleProQuotaChoice ( 'retry_always' ) ;
2025-09-08 16:19:52 -04:00
} ) ;
// The original promise from the handler should now resolve
2025-10-30 11:50:26 -07:00
const intent = await promise ! ;
2025-11-18 12:01:16 -05:00
expect ( intent ) . toBe ( 'retry_always' ) ;
2025-09-08 16:19:52 -04:00
2025-12-26 11:08:44 -05:00
// Verify setModel was called with isFallbackModel=true
expect ( mockConfig . setModel ) . toHaveBeenCalledWith ( 'gemini-flash' , true ) ;
2025-09-08 16:19:52 -04:00
// The pending request should be cleared from the state
expect ( result . current . proQuotaRequest ) . toBeNull ( ) ;
2025-11-18 12:01:16 -05:00
expect ( mockHistoryManager . addItem ) . toHaveBeenCalledTimes ( 1 ) ;
2025-09-08 16:19:52 -04:00
} ) ;
it ( 'should handle race conditions by stopping subsequent requests' , async ( ) = > {
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
2025-10-30 11:50:26 -07:00
let promise1 : Promise < FallbackIntent | null > ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-10-30 11:50:26 -07:00
promise1 = handler (
'gemini-pro' ,
'gemini-flash' ,
new TerminalQuotaError ( 'pro quota 1' , mockGoogleApiError ) ,
) ;
} ) ;
2025-09-08 16:19:52 -04:00
const firstRequest = result . current . proQuotaRequest ;
expect ( firstRequest ) . not . toBeNull ( ) ;
2025-10-30 11:50:26 -07:00
let result2 : FallbackIntent | null ;
await act ( async ( ) = > {
result2 = await handler (
'gemini-pro' ,
'gemini-flash' ,
new TerminalQuotaError ( 'pro quota 2' , mockGoogleApiError ) ,
) ;
} ) ;
2025-09-08 16:19:52 -04:00
// The lock should have stopped the second request
2025-10-30 11:50:26 -07:00
expect ( result2 ! ) . toBe ( 'stop' ) ;
2025-09-08 16:19:52 -04:00
expect ( result . current . proQuotaRequest ) . toBe ( firstRequest ) ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
result . current . handleProQuotaChoice ( 'retry_always' ) ;
2025-09-08 16:19:52 -04:00
} ) ;
2025-10-30 11:50:26 -07:00
const intent1 = await promise1 ! ;
2025-11-18 12:01:16 -05:00
expect ( intent1 ) . toBe ( 'retry_always' ) ;
2025-09-08 16:19:52 -04:00
expect ( result . current . proQuotaRequest ) . toBeNull ( ) ;
} ) ;
2025-11-06 13:43:21 -05:00
2025-11-18 12:01:16 -05:00
// Non-TerminalQuotaError test cases
2025-11-06 13:43:21 -05:00
const testCases = [
{
2025-11-18 12:01:16 -05:00
description : 'generic error' ,
2025-11-06 13:43:21 -05:00
error : new Error ( 'some error' ) ,
} ,
{
2025-11-18 12:01:16 -05:00
description : 'retryable quota error' ,
2025-11-06 13:43:21 -05:00
error : new RetryableQuotaError (
'retryable quota' ,
mockGoogleApiError ,
5 ,
) ,
} ,
] ;
2025-11-18 12:01:16 -05:00
for ( const { description , error } of testCases ) {
2025-11-06 13:43:21 -05:00
it ( ` should handle ${ description } correctly ` , async ( ) = > {
2025-11-18 12:01:16 -05:00
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError :
mockSetModelSwitchedFromQuotaError ,
} ) ,
2025-11-06 13:43:21 -05:00
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
let promise : Promise < FallbackIntent | null > ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-06 13:43:21 -05:00
promise = handler ( 'model-A' , 'model-B' , error ) ;
} ) ;
// The hook should now have a pending request for the UI to handle
2025-11-18 12:01:16 -05:00
const request = result . current . proQuotaRequest ;
expect ( request ) . not . toBeNull ( ) ;
expect ( request ? . failedModel ) . toBe ( 'model-A' ) ;
expect ( request ? . isTerminalQuotaError ) . toBe ( false ) ;
// Check that the correct initial message was generated
expect ( mockHistoryManager . addItem ) . not . toHaveBeenCalled ( ) ;
const message = request ! . message ;
expect ( message ) . toContain (
2025-12-17 09:43:21 -08:00
'We are currently experiencing high demand.' ,
2025-11-06 13:43:21 -05:00
) ;
// Simulate the user choosing to continue with the fallback model
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
result . current . handleProQuotaChoice ( 'retry_always' ) ;
2025-11-06 13:43:21 -05:00
} ) ;
expect ( mockSetModelSwitchedFromQuotaError ) . toHaveBeenCalledWith ( true ) ;
// The original promise from the handler should now resolve
const intent = await promise ! ;
2025-11-18 12:01:16 -05:00
expect ( intent ) . toBe ( 'retry_always' ) ;
2025-11-06 13:43:21 -05:00
2025-12-26 11:08:44 -05:00
// Verify setModel was called with isFallbackModel=true
expect ( mockConfig . setModel ) . toHaveBeenCalledWith ( 'model-B' , true ) ;
2025-11-06 13:43:21 -05:00
// The pending request should be cleared from the state
expect ( result . current . proQuotaRequest ) . toBeNull ( ) ;
expect ( mockConfig . setQuotaErrorOccurred ) . toHaveBeenCalledWith ( true ) ;
2025-11-18 12:01:16 -05:00
// Check for the "Switched to fallback model" message
expect ( mockHistoryManager . addItem ) . toHaveBeenCalledTimes ( 1 ) ;
const lastCall = ( mockHistoryManager . addItem as Mock ) . mock
. calls [ 0 ] [ 0 ] ;
expect ( lastCall . type ) . toBe ( MessageType . INFO ) ;
2025-12-17 09:43:21 -08:00
expect ( lastCall . text ) . toContain ( 'Switched to fallback model model-B' ) ;
2025-11-06 13:43:21 -05:00
} ) ;
}
2025-11-18 12:01:16 -05:00
it ( 'should handle ModelNotFoundError correctly' , async ( ) = > {
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
let promise : Promise < FallbackIntent | null > ;
const error = new ModelNotFoundError ( 'model not found' , 404 ) ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
promise = handler ( 'gemini-3-pro-preview' , 'gemini-2.5-pro' , error ) ;
} ) ;
// The hook should now have a pending request for the UI to handle
const request = result . current . proQuotaRequest ;
expect ( request ) . not . toBeNull ( ) ;
expect ( request ? . failedModel ) . toBe ( 'gemini-3-pro-preview' ) ;
expect ( request ? . isTerminalQuotaError ) . toBe ( false ) ;
expect ( request ? . isModelNotFoundError ) . toBe ( true ) ;
const message = request ! . message ;
expect ( message ) . toBe (
2025-12-17 09:43:21 -08:00
` It seems like you don't have access to gemini-3-pro-preview.
2025-11-18 12:01:16 -05:00
Learn more at https : //goo.gle/enable-preview-features
2025-12-17 09:43:21 -08:00
To disable gemini - 3 - pro - preview , disable "Preview features" in / s e t t i n g s . ` ,
2025-11-18 12:01:16 -05:00
) ;
// Simulate the user choosing to switch
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
result . current . handleProQuotaChoice ( 'retry_always' ) ;
} ) ;
const intent = await promise ! ;
expect ( intent ) . toBe ( 'retry_always' ) ;
2025-12-26 11:08:44 -05:00
// Verify setModel was called with isFallbackModel=true
expect ( mockConfig . setModel ) . toHaveBeenCalledWith (
'gemini-2.5-pro' ,
true ,
) ;
2025-11-18 12:01:16 -05:00
expect ( result . current . proQuotaRequest ) . toBeNull ( ) ;
} ) ;
2025-09-08 16:19:52 -04:00
} ) ;
} ) ;
describe ( 'handleProQuotaChoice' , ( ) = > {
it ( 'should do nothing if there is no pending pro quota request' , ( ) = > {
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
act ( ( ) = > {
2025-11-06 13:43:21 -05:00
result . current . handleProQuotaChoice ( 'retry_later' ) ;
2025-09-08 16:19:52 -04:00
} ) ;
expect ( mockHistoryManager . addItem ) . not . toHaveBeenCalled ( ) ;
} ) ;
2025-11-06 13:43:21 -05:00
it ( 'should resolve intent to "retry_later"' , async ( ) = > {
2025-09-08 16:19:52 -04:00
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
2025-10-30 11:50:26 -07:00
let promise : Promise < FallbackIntent | null > ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-10-30 11:50:26 -07:00
promise = handler (
'gemini-pro' ,
'gemini-flash' ,
new TerminalQuotaError ( 'pro quota' , mockGoogleApiError ) ,
) ;
} ) ;
2025-09-08 16:19:52 -04:00
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-06 13:43:21 -05:00
result . current . handleProQuotaChoice ( 'retry_later' ) ;
2025-09-08 16:19:52 -04:00
} ) ;
2025-10-30 11:50:26 -07:00
const intent = await promise ! ;
2025-11-06 13:43:21 -05:00
expect ( intent ) . toBe ( 'retry_later' ) ;
2025-09-08 16:19:52 -04:00
expect ( result . current . proQuotaRequest ) . toBeNull ( ) ;
} ) ;
2025-11-18 12:01:16 -05:00
it ( 'should resolve intent to "retry_always" and add info message on continue' , async ( ) = > {
2025-09-08 16:19:52 -04:00
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
2025-11-18 12:01:16 -05:00
2025-10-30 11:50:26 -07:00
let promise : Promise < FallbackIntent | null > ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-10-30 11:50:26 -07:00
promise = handler (
'gemini-pro' ,
'gemini-flash' ,
new TerminalQuotaError ( 'pro quota' , mockGoogleApiError ) ,
) ;
} ) ;
2025-09-08 16:19:52 -04:00
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
result . current . handleProQuotaChoice ( 'retry_always' ) ;
2025-09-08 16:19:52 -04:00
} ) ;
2025-10-30 11:50:26 -07:00
const intent = await promise ! ;
2025-11-18 12:01:16 -05:00
expect ( intent ) . toBe ( 'retry_always' ) ;
2025-09-08 16:19:52 -04:00
expect ( result . current . proQuotaRequest ) . toBeNull ( ) ;
2025-12-26 11:08:44 -05:00
// Verify setModel was called with isFallbackModel=true
expect ( mockConfig . setModel ) . toHaveBeenCalledWith ( 'gemini-flash' , true ) ;
2025-11-18 12:01:16 -05:00
// Check for the "Switched to fallback model" message
expect ( mockHistoryManager . addItem ) . toHaveBeenCalledTimes ( 1 ) ;
const lastCall = ( mockHistoryManager . addItem as Mock ) . mock . calls [ 0 ] [ 0 ] ;
2025-09-08 16:19:52 -04:00
expect ( lastCall . type ) . toBe ( MessageType . INFO ) ;
2025-12-17 09:43:21 -08:00
expect ( lastCall . text ) . toContain (
'Switched to fallback model gemini-flash' ,
) ;
2025-09-08 16:19:52 -04:00
} ) ;
2025-11-18 12:01:16 -05:00
it ( 'should show a special message when falling back from the preview model' , async ( ) = > {
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
let promise : Promise < FallbackIntent | null > ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
promise = handler (
PREVIEW_GEMINI_MODEL ,
2025-11-25 16:17:22 -05:00
DEFAULT_GEMINI_MODEL ,
new Error ( 'preview model failed' ) ,
) ;
} ) ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-25 16:17:22 -05:00
result . current . handleProQuotaChoice ( 'retry_always' ) ;
} ) ;
await promise ! ;
expect ( mockHistoryManager . addItem ) . toHaveBeenCalledTimes ( 1 ) ;
const lastCall = ( mockHistoryManager . addItem as Mock ) . mock . calls [ 0 ] [ 0 ] ;
expect ( lastCall . type ) . toBe ( MessageType . INFO ) ;
expect ( lastCall . text ) . toContain (
2025-12-17 09:43:21 -08:00
` Switched to fallback model gemini-2.5-pro ` ,
2025-11-25 16:17:22 -05:00
) ;
} ) ;
it ( 'should show a special message when falling back from the preview model, but do not show periodical check message for flash model fallback' , async ( ) = > {
const { result } = renderHook ( ( ) = >
useQuotaAndFallback ( {
config : mockConfig ,
historyManager : mockHistoryManager ,
userTier : UserTierId.FREE ,
setModelSwitchedFromQuotaError : mockSetModelSwitchedFromQuotaError ,
} ) ,
) ;
const handler = setFallbackHandlerSpy . mock
. calls [ 0 ] [ 0 ] as FallbackModelHandler ;
let promise : Promise < FallbackIntent | null > ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-25 16:17:22 -05:00
promise = handler (
PREVIEW_GEMINI_MODEL ,
DEFAULT_GEMINI_FLASH_MODEL ,
2025-11-18 12:01:16 -05:00
new Error ( 'preview model failed' ) ,
) ;
} ) ;
2025-12-16 21:28:18 -08:00
act ( ( ) = > {
2025-11-18 12:01:16 -05:00
result . current . handleProQuotaChoice ( 'retry_always' ) ;
} ) ;
await promise ! ;
expect ( mockHistoryManager . addItem ) . toHaveBeenCalledTimes ( 1 ) ;
const lastCall = ( mockHistoryManager . addItem as Mock ) . mock . calls [ 0 ] [ 0 ] ;
expect ( lastCall . type ) . toBe ( MessageType . INFO ) ;
expect ( lastCall . text ) . toContain (
2025-12-17 09:43:21 -08:00
` Switched to fallback model gemini-2.5-flash ` ,
2025-11-18 12:01:16 -05:00
) ;
} ) ;
2025-09-08 16:19:52 -04:00
} ) ;
} ) ;