@@ -6,7 +6,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
66
77const {
88 mockCheckInternalApiKey,
9- mockRecordUsage,
109 mockRecordCumulativeUsage,
1110 mockCheckAndBillOverageThreshold,
1211 mockCheckAndBillPayerOverageThreshold,
@@ -19,7 +18,6 @@ const {
1918 billingState,
2019} = vi . hoisted ( ( ) => ( {
2120 mockCheckInternalApiKey : vi . fn ( ) ,
22- mockRecordUsage : vi . fn ( ) ,
2321 mockRecordCumulativeUsage : vi . fn ( ) ,
2422 mockCheckAndBillOverageThreshold : vi . fn ( ) ,
2523 mockCheckAndBillPayerOverageThreshold : vi . fn ( ) ,
@@ -59,7 +57,6 @@ vi.mock('@/lib/copilot/request/otel', () => ({
5957
6058vi . mock ( '@/lib/billing/core/usage-log' , ( ) => ( {
6159 CumulativeUsageContextMismatchError : MockCumulativeUsageContextMismatchError ,
62- recordUsage : mockRecordUsage ,
6360 recordCumulativeUsage : mockRecordCumulativeUsage ,
6461} ) )
6562
@@ -150,13 +147,21 @@ const OLD_GO_OPAQUE_WORKSPACE_UPDATE_COST_BODY = {
150147 workspaceId : 'local-self-hosted-workspace' ,
151148} as const
152149
150+ const KEYLESS_UPDATE_COST_BODY = {
151+ userId : 'user-1' ,
152+ cost : 0.5 ,
153+ model : 'gpt' ,
154+ inputTokens : 1 ,
155+ outputTokens : 2 ,
156+ source : 'copilot' ,
157+ } as const
158+
153159describe ( 'POST /api/billing/update-cost — workspaceId attribution' , ( ) => {
154160 beforeEach ( ( ) => {
155161 vi . clearAllMocks ( )
156162 billingState . isBillingEnabled = true
157163 billingState . isCopilotBillingProtocolRequired = false
158164 mockCheckInternalApiKey . mockReturnValue ( { success : true } )
159- mockRecordUsage . mockResolvedValue ( undefined )
160165 mockRecordCumulativeUsage . mockResolvedValue ( { billed : true , delta : 0.5 , total : 0.5 } )
161166 mockCheckAndBillOverageThreshold . mockResolvedValue ( undefined )
162167 mockCheckAndBillPayerOverageThreshold . mockResolvedValue ( undefined )
@@ -191,7 +196,6 @@ describe('POST /api/billing/update-cost — workspaceId attribution', () => {
191196 error : 'Invalid internal API key' ,
192197 } )
193198 expect ( mockCheckInternalApiKey ) . toHaveBeenCalledTimes ( 1 )
194- expect ( mockRecordUsage ) . not . toHaveBeenCalled ( )
195199 expect ( mockRecordCumulativeUsage ) . not . toHaveBeenCalled ( )
196200 } )
197201
@@ -218,7 +222,6 @@ describe('POST /api/billing/update-cost — workspaceId attribution', () => {
218222 data : { billingEnabled : false } ,
219223 } )
220224 expect ( mockCheckInternalApiKey ) . toHaveBeenCalledTimes ( 1 )
221- expect ( mockRecordUsage ) . not . toHaveBeenCalled ( )
222225 expect ( mockRecordCumulativeUsage ) . not . toHaveBeenCalled ( )
223226 } )
224227
@@ -232,6 +235,17 @@ describe('POST /api/billing/update-cost — workspaceId attribution', () => {
232235 ) . toBe ( true )
233236 } )
234237
238+ it ( 'rejects billing-enabled callbacks without a stable idempotency key' , async ( ) => {
239+ const res = await POST (
240+ createMockRequest ( 'POST' , KEYLESS_UPDATE_COST_BODY , { 'x-api-key' : 'internal' } )
241+ )
242+
243+ expect ( res . status ) . toBe ( 400 )
244+ expect ( mockRecordCumulativeUsage ) . not . toHaveBeenCalled ( )
245+ expect ( mockCheckAndBillOverageThreshold ) . not . toHaveBeenCalled ( )
246+ expect ( mockCheckAndBillPayerOverageThreshold ) . not . toHaveBeenCalled ( )
247+ } )
248+
235249 it ( 'bills the routed workspace payer for the exact markerless hosted callback' , async ( ) => {
236250 const res = await POST (
237251 createMockRequest ( 'POST' , OLD_GO_HOSTED_UPDATE_COST_BODY , { 'x-api-key' : 'internal' } )
@@ -565,7 +579,6 @@ describe('POST /api/billing/update-cost — workspaceId attribution', () => {
565579 code : 'DUPLICATE_BILLING_EVENT' ,
566580 } )
567581 expect ( mockRecordCumulativeUsage ) . toHaveBeenCalledTimes ( 2 )
568- expect ( mockRecordUsage ) . not . toHaveBeenCalled ( )
569582 expect ( mockResolveLegacyV0BillingAttribution ) . toHaveBeenCalledTimes ( 2 )
570583 expect ( mockCheckAndBillPayerOverageThreshold ) . toHaveBeenCalledTimes ( 2 )
571584 expect ( mockCheckAndBillPayerOverageThreshold ) . toHaveBeenNthCalledWith (
0 commit comments