@@ -13,6 +13,7 @@ import type {
1313 Logger ,
1414 LoggerWithContextFn ,
1515} from '@codebuff/common/types/contracts/logger'
16+ import type { BlockGrantResult } from '@codebuff/billing/subscription'
1617
1718describe ( '/api/v1/docs-search POST endpoint' , ( ) => {
1819 let mockLogger : Logger
@@ -153,4 +154,73 @@ describe('/api/v1/docs-search POST endpoint', () => {
153154 const body = await res . json ( )
154155 expect ( body . documentation ) . toContain ( 'Some documentation text' )
155156 } )
157+
158+ test ( '200 for subscriber with 0 a-la-carte credits but active block grant' , async ( ) => {
159+ mockGetUserUsageData = mock ( async ( { includeSubscriptionCredits } : { includeSubscriptionCredits ?: boolean } ) => ( {
160+ usageThisCycle : 0 ,
161+ balance : {
162+ totalRemaining : includeSubscriptionCredits ? 350 : 0 ,
163+ totalDebt : 0 ,
164+ netBalance : includeSubscriptionCredits ? 350 : 0 ,
165+ breakdown : { } ,
166+ } ,
167+ nextQuotaReset : 'soon' ,
168+ } ) )
169+ const mockEnsureSubscriberBlockGrant = mock ( async ( ) => ( {
170+ grantId : 'grant-1' ,
171+ credits : 350 ,
172+ expiresAt : new Date ( Date . now ( ) + 5 * 60 * 60 * 1000 ) ,
173+ isNew : true ,
174+ } ) ) as unknown as ( params : { userId : string ; logger : Logger } ) => Promise < BlockGrantResult | null >
175+
176+ const req = new NextRequest ( 'http://localhost:3000/api/v1/docs-search' , {
177+ method : 'POST' ,
178+ headers : { Authorization : 'Bearer valid' } ,
179+ body : JSON . stringify ( { libraryTitle : 'React' } ) ,
180+ } )
181+ const res = await postDocsSearch ( {
182+ req,
183+ getUserInfoFromApiKey : mockGetUserInfoFromApiKey ,
184+ logger : mockLogger ,
185+ loggerWithContext : mockLoggerWithContext ,
186+ trackEvent : mockTrackEvent ,
187+ getUserUsageData : mockGetUserUsageData ,
188+ consumeCreditsWithFallback : mockConsumeCreditsWithFallback ,
189+ fetch : mockFetch ,
190+ ensureSubscriberBlockGrant : mockEnsureSubscriberBlockGrant ,
191+ } )
192+ expect ( res . status ) . toBe ( 200 )
193+ } )
194+
195+ test ( '402 for non-subscriber with 0 credits and no block grant' , async ( ) => {
196+ mockGetUserUsageData = mock ( async ( ) => ( {
197+ usageThisCycle : 0 ,
198+ balance : {
199+ totalRemaining : 0 ,
200+ totalDebt : 0 ,
201+ netBalance : 0 ,
202+ breakdown : { } ,
203+ } ,
204+ nextQuotaReset : 'soon' ,
205+ } ) )
206+ const mockEnsureSubscriberBlockGrant = mock ( async ( ) => null ) as unknown as ( params : { userId : string ; logger : Logger } ) => Promise < BlockGrantResult | null >
207+
208+ const req = new NextRequest ( 'http://localhost:3000/api/v1/docs-search' , {
209+ method : 'POST' ,
210+ headers : { Authorization : 'Bearer valid' } ,
211+ body : JSON . stringify ( { libraryTitle : 'React' } ) ,
212+ } )
213+ const res = await postDocsSearch ( {
214+ req,
215+ getUserInfoFromApiKey : mockGetUserInfoFromApiKey ,
216+ logger : mockLogger ,
217+ loggerWithContext : mockLoggerWithContext ,
218+ trackEvent : mockTrackEvent ,
219+ getUserUsageData : mockGetUserUsageData ,
220+ consumeCreditsWithFallback : mockConsumeCreditsWithFallback ,
221+ fetch : mockFetch ,
222+ ensureSubscriberBlockGrant : mockEnsureSubscriberBlockGrant ,
223+ } )
224+ expect ( res . status ) . toBe ( 402 )
225+ } )
156226} )
0 commit comments