@@ -62,6 +62,84 @@ Triggers when:
6262- Amount >= 500 credits (minimum)
6363- If debt exists: amount = max(configured amount, debt amount)
6464
65- ## Testing
66-
67- Mock database module directly, not getOrderedActiveGrants. Pass explicit 'now' parameter to control grant expiration.
65+ ## Testing with Dependency Injection
66+
67+ All billing functions support dependency injection (DI) via optional ` deps ` parameters, enabling comprehensive unit testing without mocking modules.
68+
69+ ### DI Patterns
70+
71+ Each function accepts an optional ` deps ` object with injectable dependencies:
72+
73+ ``` typescript
74+ // Example: Testing consumeCreditsAndAddAgentStep
75+ await consumeCreditsAndAddAgentStep ({
76+ messageId: ' test-msg' ,
77+ userId: ' user-1' ,
78+ // ... other params
79+ deps: {
80+ withSerializableTransaction: mockTransaction ,
81+ trackEvent: vi .fn (),
82+ reportPurchasedCreditsToStripe: vi .fn (),
83+ },
84+ })
85+ ```
86+
87+ ### Available DI Interfaces
88+
89+ | Function | Deps Interface | Injectable Dependencies |
90+ | ----------| ----------------| ------------------------|
91+ | ` consumeCreditsAndAddAgentStep ` | ` ConsumeCreditsAndAddAgentStepDeps ` | ` withSerializableTransaction ` , ` trackEvent ` , ` reportPurchasedCreditsToStripe ` |
92+ | ` calculateUsageThisCycle ` | ` CalculateUsageThisCycleDeps ` | ` db ` |
93+ | ` validateAutoTopupStatus ` | ` ValidateAutoTopupStatusDeps ` | ` db ` , ` stripeServer ` |
94+ | ` checkAndTriggerAutoTopup ` | ` CheckAndTriggerAutoTopupDeps ` | ` db ` , ` stripeServer ` , ` calculateUsageAndBalanceFn ` , ` validateAutoTopupStatusFn ` , ` processAndGrantCreditFn ` |
95+ | ` checkAndTriggerOrgAutoTopup ` | ` CheckAndTriggerOrgAutoTopupDeps ` | ` db ` , ` stripeServer ` , ` calculateOrganizationUsageAndBalanceFn ` , ` grantOrganizationCreditsFn ` |
96+ | ` reportPurchasedCreditsToStripe ` | ` ReportPurchasedCreditsToStripeDeps ` | ` db ` , ` stripeServer ` , ` shouldAttemptStripeMetering ` |
97+ | ` getPreviousFreeGrantAmount ` | ` GetPreviousFreeGrantAmountDeps ` | ` db ` |
98+ | ` calculateTotalReferralBonus ` | ` CalculateTotalReferralBonusDeps ` | ` db ` |
99+ | ` processAndGrantCredit ` | ` ProcessAndGrantCreditDeps ` | ` grantCreditFn ` , ` logSyncFailure ` |
100+ | ` syncOrganizationBillingCycle ` | ` SyncOrganizationBillingCycleDeps ` | ` db ` , ` stripeServer ` |
101+ | ` findOrganizationForRepository ` | ` FindOrganizationForRepositoryDeps ` | ` db ` |
102+ | ` consumeOrganizationCredits ` | ` ConsumeOrgCreditsDeps ` | ` withSerializableTransaction ` , ` trackEvent ` , ` reportToStripe ` |
103+ | ` grantOrganizationCredits ` | ` GrantOrgCreditsDeps ` | ` db ` , ` transaction ` |
104+
105+ ### Functions with ` conn ` Parameter
106+
107+ Some functions accept a ` conn ` parameter for transaction context:
108+
109+ - ` getOrderedActiveGrants({ userId, now, conn }) ` - Pass ` tx ` inside transactions
110+ - ` getOrderedActiveOrganizationGrants({ organizationId, now, conn }) ` - Same pattern
111+ - ` calculateUsageAndBalance({ ..., conn }) ` - Pass transaction for consistent reads
112+
113+ ### Testing Best Practices
114+
115+ 1 . ** Use ` createMockBillingDb() ` ** from ` @codebuff/common/testing/mock-db ` for database mocking
116+ 2 . ** Pass explicit ` now ` parameter** to control grant expiration in tests
117+ 3 . ** Mock Stripe** by injecting a mock ` stripeServer ` via deps
118+ 4 . ** Use ` vi.fn() ` ** for tracking function calls (analytics, Stripe reporting)
119+
120+ ### Example Test
121+
122+ ``` typescript
123+ import { createMockBillingDb } from ' @codebuff/common/testing/mock-db'
124+ import { checkAndTriggerAutoTopup } from ' @codebuff/billing/auto-topup'
125+
126+ test (' triggers auto-topup when balance is low' , async () => {
127+ const mockDb = createMockBillingDb ()
128+ const mockStripe = { paymentMethods: { list: vi .fn () }, ... }
129+ const mockCalculateBalance = vi .fn ().mockResolvedValue ({
130+ balance: { totalRemaining: 100 , totalDebt: 0 }
131+ })
132+
133+ await checkAndTriggerAutoTopup ({
134+ userId: ' user-1' ,
135+ logger: mockLogger ,
136+ deps: {
137+ db: mockDb ,
138+ stripeServer: mockStripe ,
139+ calculateUsageAndBalanceFn: mockCalculateBalance ,
140+ },
141+ })
142+
143+ expect (mockCalculateBalance ).toHaveBeenCalled ()
144+ })
145+ ```
0 commit comments