11/**
22 * @vitest -environment node
33 */
4+ import { resetEnvMock , setEnv } from '@sim/testing/mocks/env.mock'
45import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
56
6- const { consumeTokens, getCooldownUntil, setCooldownUntil, mockEnv } = vi . hoisted ( ( ) => ( {
7+ const { consumeTokens, getCooldownUntil, setCooldownUntil } = vi . hoisted ( ( ) => ( {
78 consumeTokens : vi . fn ( ) ,
89 getCooldownUntil : vi . fn ( ) ,
910 setCooldownUntil : vi . fn ( ) ,
10- mockEnv : { } as Record < string , string | undefined > ,
11- } ) )
12- vi . mock ( '@/lib/core/config/env' , ( ) => ( {
13- env : mockEnv ,
14- envNumber : ( value : string | undefined , fallback : number ) =>
15- value === undefined ? fallback : Number ( value ) ,
1611} ) )
1712vi . mock ( '@/lib/core/rate-limiter/storage/factory' , ( ) => ( {
1813 createStorageAdapter : ( ) => ( {
@@ -41,7 +36,10 @@ describe('provider admission', () => {
4136 consumeTokens . mockResolvedValue ( { allowed : true , tokensRemaining : 1 , resetAt : new Date ( ) } )
4237 } )
4338
44- afterEach ( ( ) => vi . useRealTimers ( ) )
39+ afterEach ( ( ) => {
40+ vi . useRealTimers ( )
41+ resetEnvMock ( )
42+ } )
4543
4644 it ( 'shares both credential dimensions in one reservation across concurrent callers' , async ( ) => {
4745 await Promise . all ( [ waitForProviderAdmission ( INPUT ) , waitForProviderAdmission ( INPUT ) ] )
@@ -92,15 +90,11 @@ describe('provider admission', () => {
9290 )
9391 } )
9492
95- it ( 'caps the bulk lane below the aggregate budget so interactive callers keep headroom' , async ( ) => {
96- await waitForProviderAdmission ( { ...INPUT , lane : 'bulk' } )
97- await waitForProviderAdmission ( { ...INPUT , lane : 'interactive' } )
98- const [ bulkReservations , bulkOptions ] = consumeTokens . mock . calls [ 0 ]
99- expect ( bulkReservations ) . toMatchObject ( [
100- {
101- key : 'provider:embedding:openai:hashed-credential:tokens' ,
102- config : { maxTokens : 600_000 , refillRate : 10_000 } ,
103- } ,
93+ it ( 'caps bulk work below the aggregate budget so interactive callers keep headroom' , async ( ) => {
94+ await waitForProviderAdmission ( { ...INPUT , bulk : true } )
95+ const [ reservations , options ] = consumeTokens . mock . calls [ 0 ]
96+ expect ( reservations ) . toMatchObject ( [
97+ { key : 'provider:embedding:openai:hashed-credential:tokens' , config : { maxTokens : 600_000 } } ,
10498 { key : 'provider:embedding:openai:hashed-credential:requests' , config : { maxTokens : 64 } } ,
10599 {
106100 key : 'provider:embedding:openai:hashed-credential:bulk:tokens' ,
@@ -112,31 +106,24 @@ describe('provider admission', () => {
112106 config : { maxTokens : 57 , refillRate : 9 } ,
113107 } ,
114108 ] )
115- expect ( bulkOptions . cooldownKeys ) . toEqual ( [
109+ expect ( options . cooldownKeys ) . toEqual ( [
116110 'provider:embedding:openai:hashed-credential:cooldown' ,
117111 'provider:embedding:openai:hashed-credential:quota' ,
118112 ] )
119- const [ interactiveReservations , interactiveOptions ] = consumeTokens . mock . calls [ 1 ]
120- expect ( interactiveReservations . map ( ( item : { key : string } ) => item . key ) ) . toEqual ( [
121- 'provider:embedding:openai:hashed-credential:tokens' ,
122- 'provider:embedding:openai:hashed-credential:requests' ,
123- ] )
124- expect ( interactiveOptions . cooldownKeys ) . toEqual ( bulkOptions . cooldownKeys )
125113 } )
126114
127- it ( 'never shrinks a bulk bucket below one valid reservation' , async ( ) => {
128- mockEnv . KB_CONFIG_EMBEDDING_REQUESTS_PER_MINUTE = '1'
129- mockEnv . KB_CONFIG_EMBEDDING_TOKENS_PER_MINUTE = '100'
130- try {
131- await waitForProviderAdmission ( { ...INPUT , inputTokens : 95 , lane : 'bulk' } )
132- } finally {
133- mockEnv . KB_CONFIG_EMBEDDING_REQUESTS_PER_MINUTE = undefined
134- mockEnv . KB_CONFIG_EMBEDDING_TOKENS_PER_MINUTE = undefined
135- }
136- expect ( consumeTokens . mock . calls [ 0 ] [ 0 ] ) . toMatchObject ( [
137- { key : 'provider:embedding:openai:hashed-credential:tokens' , config : { maxTokens : 100 } } ,
138- { key : 'provider:embedding:openai:hashed-credential:requests' , config : { maxTokens : 1 } } ,
139- { key : 'provider:embedding:openai:hashed-credential:bulk:tokens' , config : { maxTokens : 95 } } ,
115+ it ( 'rejects a bulk batch the lane can never hold and keeps one request slot at a minimal burst' , async ( ) => {
116+ setEnv ( {
117+ KB_CONFIG_EMBEDDING_REQUESTS_PER_MINUTE : '1' ,
118+ KB_CONFIG_EMBEDDING_TOKENS_PER_MINUTE : '100' ,
119+ } )
120+ await expect (
121+ waitForProviderAdmission ( { ...INPUT , inputTokens : 95 , bulk : true } )
122+ ) . rejects . toThrow ( 'exceeds the configured per-credential token budget' )
123+ await waitForProviderAdmission ( { ...INPUT , inputTokens : 95 } )
124+ await waitForProviderAdmission ( { ...INPUT , inputTokens : 90 , bulk : true } )
125+ expect ( consumeTokens . mock . calls [ 1 ] [ 0 ] . slice ( 2 ) ) . toMatchObject ( [
126+ { key : 'provider:embedding:openai:hashed-credential:bulk:tokens' , config : { maxTokens : 90 } } ,
140127 {
141128 key : 'provider:embedding:openai:hashed-credential:bulk:requests' ,
142129 config : { maxTokens : 1 } ,
0 commit comments