Skip to content

Commit c57fd7f

Browse files
committed
billing fixes
1 parent fd95c05 commit c57fd7f

File tree

2 files changed

+188
-348
lines changed

2 files changed

+188
-348
lines changed

packages/billing/src/__tests__/balance-calculator.test.ts

Lines changed: 0 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -404,147 +404,6 @@ describe('Balance Calculator - calculateUsageAndBalance', () => {
404404
})
405405
})
406406

407-
describe('shouldBlockFreeUserOverdraw', () => {
408-
afterEach(() => {
409-
clearMockedModules()
410-
})
411-
412-
async function importModule() {
413-
await mockModule('@codebuff/internal/db', () => ({
414-
default: {},
415-
}))
416-
await mockModule('@codebuff/common/analytics', () => ({
417-
trackEvent: () => {},
418-
}))
419-
return import('@codebuff/billing/balance-calculator')
420-
}
421-
422-
it('should block when exhausted free-tier user tries to consume', async () => {
423-
const { shouldBlockFreeUserOverdraw } = await importModule()
424-
expect(
425-
shouldBlockFreeUserOverdraw([{ balance: 0, type: 'free' }], 100),
426-
).toBe(true)
427-
})
428-
429-
it('should block when free-tier user balance is less than charge', async () => {
430-
const { shouldBlockFreeUserOverdraw } = await importModule()
431-
expect(
432-
shouldBlockFreeUserOverdraw([{ balance: 50, type: 'free' }], 100),
433-
).toBe(true)
434-
})
435-
436-
it('should not block when free-tier user has sufficient balance', async () => {
437-
const { shouldBlockFreeUserOverdraw } = await importModule()
438-
expect(
439-
shouldBlockFreeUserOverdraw([{ balance: 500, type: 'free' }], 100),
440-
).toBe(false)
441-
})
442-
443-
it('should not block when user has a subscription grant even with zero balance', async () => {
444-
const { shouldBlockFreeUserOverdraw } = await importModule()
445-
expect(
446-
shouldBlockFreeUserOverdraw(
447-
[
448-
{ balance: 0, type: 'free' },
449-
{ balance: 0, type: 'subscription' },
450-
],
451-
100,
452-
),
453-
).toBe(false)
454-
})
455-
456-
it('should not block when user has a purchase grant', async () => {
457-
const { shouldBlockFreeUserOverdraw } = await importModule()
458-
expect(
459-
shouldBlockFreeUserOverdraw(
460-
[
461-
{ balance: 0, type: 'free' },
462-
{ balance: 10, type: 'purchase' },
463-
],
464-
100,
465-
),
466-
).toBe(false)
467-
})
468-
469-
it('should not block when credits to charge is 0 (free-mode agent)', async () => {
470-
const { shouldBlockFreeUserOverdraw } = await importModule()
471-
expect(
472-
shouldBlockFreeUserOverdraw([{ balance: 0, type: 'free' }], 0),
473-
).toBe(false)
474-
})
475-
476-
it('should block referral-only user with insufficient credits', async () => {
477-
const { shouldBlockFreeUserOverdraw } = await importModule()
478-
expect(
479-
shouldBlockFreeUserOverdraw([{ balance: 50, type: 'referral' }], 100),
480-
).toBe(true)
481-
})
482-
483-
it('should block user in debt with no paid grants', async () => {
484-
const { shouldBlockFreeUserOverdraw } = await importModule()
485-
expect(
486-
shouldBlockFreeUserOverdraw([{ balance: -100, type: 'free' }], 50),
487-
).toBe(true)
488-
})
489-
490-
it('should aggregate balance across multiple unpaid grants', async () => {
491-
const { shouldBlockFreeUserOverdraw } = await importModule()
492-
// Total balance: 110, charge: 100 → not blocked
493-
expect(
494-
shouldBlockFreeUserOverdraw(
495-
[
496-
{ balance: 30, type: 'free' },
497-
{ balance: 80, type: 'referral' },
498-
],
499-
100,
500-
),
501-
).toBe(false)
502-
})
503-
})
504-
505-
describe('InsufficientCreditsError', () => {
506-
afterEach(() => {
507-
clearMockedModules()
508-
})
509-
510-
async function importModule() {
511-
await mockModule('@codebuff/internal/db', () => ({
512-
default: {},
513-
}))
514-
await mockModule('@codebuff/common/analytics', () => ({
515-
trackEvent: () => {},
516-
}))
517-
return import('@codebuff/billing/balance-calculator')
518-
}
519-
520-
it('should be an instance of Error with the correct name and fields', async () => {
521-
const { InsufficientCreditsError } = await importModule()
522-
const err = new InsufficientCreditsError(-50, 200)
523-
expect(err).toBeInstanceOf(Error)
524-
expect(err).toBeInstanceOf(InsufficientCreditsError)
525-
expect(err.name).toBe('InsufficientCreditsError')
526-
expect(err.netBalance).toBe(-50)
527-
expect(err.chargeAmount).toBe(200)
528-
expect(err.message).toBe(
529-
'Insufficient credits for free-tier user: balance=-50, charge=200',
530-
)
531-
})
532-
533-
it('should be exported from the billing barrel (@codebuff/billing)', async () => {
534-
await mockModule('@codebuff/internal/db', () => ({
535-
default: {},
536-
}))
537-
await mockModule('@codebuff/common/analytics', () => ({
538-
trackEvent: () => {},
539-
}))
540-
const billing = await import('@codebuff/billing')
541-
expect(typeof billing.InsufficientCreditsError).toBe('function')
542-
const err = new billing.InsufficientCreditsError(0, 100)
543-
expect(err).toBeInstanceOf(Error)
544-
expect(err.name).toBe('InsufficientCreditsError')
545-
})
546-
})
547-
548407
describe('consumeFromOrderedGrants - credit consumption bugs', () => {
549408
// Regression tests for two compounding bugs:
550409
// 1. Pass 1 ("repay debt") was directionally wrong: consumption reduced debt instead of

0 commit comments

Comments
 (0)