1414 * - mock `bnb-price-oracle` so we don't hit the network for BNB quotes.
1515 */
1616
17- import {
18- afterAll ,
19- beforeAll ,
20- beforeEach ,
21- describe ,
22- expect ,
23- test ,
24- vi ,
25- } from "vitest" ;
17+ import { afterAll , beforeAll , beforeEach , describe , expect , test , vi } from "vitest" ;
2618
2719// This integration test relies on Vitest-only module-mock plumbing
2820// (`vi.mock(id, async () => ({ ...await vi.importActual<T>(id), ...overrides }))`)
@@ -39,8 +31,7 @@ import {
3931// state-machine paths are exercised by other integration suites that use
4032// real test fixtures rather than vi.mock.
4133const SUPPORTS_VITEST_MOCK_API =
42- typeof ( vi as unknown as { importActual ?: unknown } ) . importActual ===
43- "function" ;
34+ typeof ( vi as unknown as { importActual ?: unknown } ) . importActual === "function" ;
4435const d = SUPPORTS_VITEST_MOCK_API ? describe : describe . skip ;
4536
4637// --- Required env BEFORE any imports of cloud-shared/db ---------------------
@@ -83,90 +74,92 @@ interface FakeTx {
8374
8475const chainTxs = new Map < string , FakeTx > ( ) ;
8576
86- if ( SUPPORTS_VITEST_MOCK_API ) vi . mock ( "viem" , async ( ) => {
87- const actual = ( await vi . importActual ( "viem" ) ) as typeof import ( "viem" ) ;
88- return {
89- ...actual ,
90- createPublicClient : ( ) => ( {
91- async getTransactionReceipt ( { hash } : { hash : string } ) {
92- const tx = chainTxs . get ( hash ) ;
93- if ( ! tx ) {
94- const err = new Error ( "Transaction receipt not found" ) ;
95- err . name = "TransactionReceiptNotFoundError" ;
96- throw err ;
97- }
98- if ( tx . throwNotFound ) {
99- const err = new Error ( "could not be found" ) ;
100- err . name = "TransactionReceiptNotFoundError" ;
101- throw err ;
102- }
103- if ( tx . throwTerminal ) {
104- throw new Error ( tx . throwTerminal ) ;
105- }
106- return {
107- status : tx . status ,
108- blockNumber : 12345n ,
109- logs : tx . erc20
110- ? [
111- {
112- address : tx . erc20 . tokenAddress ,
113- topics : [ ] ,
114- data : "0x" ,
115- // parseEventLogs uses these — we shortcut via stubbed parseEventLogs below
77+ if ( SUPPORTS_VITEST_MOCK_API )
78+ vi . mock ( "viem" , async ( ) => {
79+ const actual = ( await vi . importActual ( "viem" ) ) as typeof import ( "viem" ) ;
80+ return {
81+ ...actual ,
82+ createPublicClient : ( ) => ( {
83+ async getTransactionReceipt ( { hash } : { hash : string } ) {
84+ const tx = chainTxs . get ( hash ) ;
85+ if ( ! tx ) {
86+ const err = new Error ( "Transaction receipt not found" ) ;
87+ err . name = "TransactionReceiptNotFoundError" ;
88+ throw err ;
89+ }
90+ if ( tx . throwNotFound ) {
91+ const err = new Error ( "could not be found" ) ;
92+ err . name = "TransactionReceiptNotFoundError" ;
93+ throw err ;
94+ }
95+ if ( tx . throwTerminal ) {
96+ throw new Error ( tx . throwTerminal ) ;
97+ }
98+ return {
99+ status : tx . status ,
100+ blockNumber : 12345n ,
101+ logs : tx . erc20
102+ ? [
103+ {
104+ address : tx . erc20 . tokenAddress ,
105+ topics : [ ] ,
106+ data : "0x" ,
107+ // parseEventLogs uses these — we shortcut via stubbed parseEventLogs below
108+ } ,
109+ ]
110+ : [ ] ,
111+ } ;
112+ } ,
113+ async getTransaction ( { hash } : { hash : string } ) {
114+ const tx = chainTxs . get ( hash ) ;
115+ if ( ! tx ) throw new Error ( "not found" ) ;
116+ return { from : tx . from , to : tx . to , value : tx . value } ;
117+ } ,
118+ async readContract ( ) {
119+ return 18n ;
120+ } ,
121+ } ) ,
122+ parseEventLogs : ( { logs } : { logs : Array < { address : string } > } ) => {
123+ // Map the stub-receipt log back to a parsed Transfer event using the
124+ // chainTxs entry whose tokenAddress matches.
125+ const out : Array < {
126+ address : string ;
127+ args : { from : string ; to : string ; value : bigint } ;
128+ } > = [ ] ;
129+ for ( const log of logs ) {
130+ for ( const tx of chainTxs . values ( ) ) {
131+ if ( tx . erc20 && tx . erc20 . tokenAddress . toLowerCase ( ) === log . address . toLowerCase ( ) ) {
132+ out . push ( {
133+ address : tx . erc20 . tokenAddress ,
134+ args : {
135+ from : tx . erc20 . from ,
136+ to : tx . erc20 . to ,
137+ value : tx . erc20 . value ,
116138 } ,
117- ]
118- : [ ] ,
119- } ;
120- } ,
121- async getTransaction ( { hash } : { hash : string } ) {
122- const tx = chainTxs . get ( hash ) ;
123- if ( ! tx ) throw new Error ( "not found" ) ;
124- return { from : tx . from , to : tx . to , value : tx . value } ;
125- } ,
126- async readContract ( ) {
127- return 18n ;
128- } ,
129- } ) ,
130- parseEventLogs : ( { logs } : { logs : Array < { address : string } > } ) => {
131- // Map the stub-receipt log back to a parsed Transfer event using the
132- // chainTxs entry whose tokenAddress matches.
133- const out : Array < {
134- address : string ;
135- args : { from : string ; to : string ; value : bigint } ;
136- } > = [ ] ;
137- for ( const log of logs ) {
138- for ( const tx of chainTxs . values ( ) ) {
139- if ( tx . erc20 && tx . erc20 . tokenAddress . toLowerCase ( ) === log . address . toLowerCase ( ) ) {
140- out . push ( {
141- address : tx . erc20 . tokenAddress ,
142- args : {
143- from : tx . erc20 . from ,
144- to : tx . erc20 . to ,
145- value : tx . erc20 . value ,
146- } ,
147- } ) ;
148- break ;
139+ } ) ;
140+ break ;
141+ }
149142 }
150143 }
151- }
152- return out ;
153- } ,
154- } ;
155- } ) ;
144+ return out ;
145+ } ,
146+ } ;
147+ } ) ;
156148
157149// BNB price oracle — fixed quote so the math is predictable.
158- if ( SUPPORTS_VITEST_MOCK_API ) vi . mock ( "../bnb-price-oracle" , async ( ) => {
159- const Decimal = ( await import ( "decimal.js" ) ) . default ;
160- return {
161- getBnbUsdQuote : vi . fn ( async ( ) => ( {
162- priceUsd : new Decimal ( 600 ) ,
163- source : "chainlink" ,
164- feedAddress : "0xfeed" ,
165- updatedAt : "2026-01-01T00:00:00Z" ,
166- fetchedAt : "2026-01-01T00:00:01Z" ,
167- } ) ) ,
168- } ;
169- } ) ;
150+ if ( SUPPORTS_VITEST_MOCK_API )
151+ vi . mock ( "../bnb-price-oracle" , async ( ) => {
152+ const Decimal = ( await import ( "decimal.js" ) ) . default ;
153+ return {
154+ getBnbUsdQuote : vi . fn ( async ( ) => ( {
155+ priceUsd : new Decimal ( 600 ) ,
156+ source : "chainlink" ,
157+ feedAddress : "0xfeed" ,
158+ updatedAt : "2026-01-01T00:00:00Z" ,
159+ fetchedAt : "2026-01-01T00:00:01Z" ,
160+ } ) ) ,
161+ } ;
162+ } ) ;
170163
171164// Solana — we don't test the Solana confirm path through verify (would need a
172165// huge mock of getParsedTransaction + ATA owner check). The Solana createPayment
@@ -179,39 +172,43 @@ const solanaTestState = vi.hoisted(() => ({
179172 parsedTxOverride : null as unknown ,
180173} ) ) ;
181174
182- if ( SUPPORTS_VITEST_MOCK_API ) vi . mock ( "@solana/spl-token" , async ( ) => {
183- const actual = ( await vi . importActual ( "@solana/spl-token" ) ) as typeof import ( "@solana/spl-token" ) ;
184- return {
185- ...actual ,
186- getAccount : vi . fn ( async ( _connection : unknown , ata : { toBase58 ( ) : string } ) => {
187- if ( solanaTestState . ataOwnerOverride ) {
188- const { PublicKey } = await import ( "@solana/web3.js" ) ;
189- return {
190- address : ata ,
191- owner : new PublicKey ( solanaTestState . ataOwnerOverride ) ,
192- mint : ata ,
193- amount : 0n ,
194- } as unknown as Awaited < ReturnType < typeof actual . getAccount > > ;
195- }
196- return actual . getAccount ( _connection as never , ata as never ) ;
197- } ) ,
198- } ;
199- } ) ;
200-
201- if ( SUPPORTS_VITEST_MOCK_API ) vi . mock ( "@solana/web3.js" , async ( ) => {
202- const actual = ( await vi . importActual ( "@solana/web3.js" ) ) as typeof import ( "@solana/web3.js" ) ;
203- return {
204- ...actual ,
205- Connection : class FakeConnection {
206- async getParsedTransaction ( ) {
207- return solanaTestState . parsedTxOverride ;
208- }
209- async getAccountInfo ( ) {
210- return null ;
211- }
212- } ,
213- } ;
214- } ) ;
175+ if ( SUPPORTS_VITEST_MOCK_API )
176+ vi . mock ( "@solana/spl-token" , async ( ) => {
177+ const actual = ( await vi . importActual (
178+ "@solana/spl-token" ,
179+ ) ) as typeof import ( "@solana/spl-token" ) ;
180+ return {
181+ ...actual ,
182+ getAccount : vi . fn ( async ( _connection : unknown , ata : { toBase58 ( ) : string } ) => {
183+ if ( solanaTestState . ataOwnerOverride ) {
184+ const { PublicKey } = await import ( "@solana/web3.js" ) ;
185+ return {
186+ address : ata ,
187+ owner : new PublicKey ( solanaTestState . ataOwnerOverride ) ,
188+ mint : ata ,
189+ amount : 0n ,
190+ } as unknown as Awaited < ReturnType < typeof actual . getAccount > > ;
191+ }
192+ return actual . getAccount ( _connection as never , ata as never ) ;
193+ } ) ,
194+ } ;
195+ } ) ;
196+
197+ if ( SUPPORTS_VITEST_MOCK_API )
198+ vi . mock ( "@solana/web3.js" , async ( ) => {
199+ const actual = ( await vi . importActual ( "@solana/web3.js" ) ) as typeof import ( "@solana/web3.js" ) ;
200+ return {
201+ ...actual ,
202+ Connection : class FakeConnection {
203+ async getParsedTransaction ( ) {
204+ return solanaTestState . parsedTxOverride ;
205+ }
206+ async getAccountInfo ( ) {
207+ return null ;
208+ }
209+ } ,
210+ } ;
211+ } ) ;
215212
216213// creditsService stand-in: respects stripePaymentIntentId idempotency, which
217214// is the contract that prevents double-credit on retry.
@@ -221,43 +218,45 @@ const creditsLedger: Array<{
221218 stripePaymentIntentId : string | undefined ;
222219} > = [ ] ;
223220
224- if ( SUPPORTS_VITEST_MOCK_API ) vi . mock ( "../credits" , ( ) => ( {
225- creditsService : {
226- async addCredits ( params : {
227- organizationId : string ;
228- amount : number ;
229- description : string ;
230- stripePaymentIntentId ?: string ;
231- metadata ?: Record < string , unknown > ;
232- } ) {
233- if ( params . stripePaymentIntentId ) {
234- const existing = creditsLedger . find (
235- ( l ) => l . stripePaymentIntentId === params . stripePaymentIntentId ,
236- ) ;
237- if ( existing ) {
238- return { transaction : { id : "existing" } , newBalance : 0 } ;
221+ if ( SUPPORTS_VITEST_MOCK_API )
222+ vi . mock ( "../credits" , ( ) => ( {
223+ creditsService : {
224+ async addCredits ( params : {
225+ organizationId : string ;
226+ amount : number ;
227+ description : string ;
228+ stripePaymentIntentId ?: string ;
229+ metadata ?: Record < string , unknown > ;
230+ } ) {
231+ if ( params . stripePaymentIntentId ) {
232+ const existing = creditsLedger . find (
233+ ( l ) => l . stripePaymentIntentId === params . stripePaymentIntentId ,
234+ ) ;
235+ if ( existing ) {
236+ return { transaction : { id : "existing" } , newBalance : 0 } ;
237+ }
239238 }
240- }
241- creditsLedger . push ( {
242- organizationId : params . organizationId ,
243- amount : params . amount ,
244- stripePaymentIntentId : params . stripePaymentIntentId ,
245- } ) ;
246- return { transaction : { id : "new" } , newBalance : params . amount } ;
239+ creditsLedger . push ( {
240+ organizationId : params . organizationId ,
241+ amount : params . amount ,
242+ stripePaymentIntentId : params . stripePaymentIntentId ,
243+ } ) ;
244+ return { transaction : { id : "new" } , newBalance : params . amount } ;
245+ } ,
247246 } ,
248- } ,
249- } ) ) ;
247+ } ) ) ;
250248
251- if ( SUPPORTS_VITEST_MOCK_API ) vi . mock ( "../invoices" , ( ) => ( {
252- invoicesService : {
253- async getByStripeInvoiceId ( ) {
254- return undefined ;
255- } ,
256- async create ( ) {
257- return { id : "invoice-stub" } ;
249+ if ( SUPPORTS_VITEST_MOCK_API )
250+ vi . mock ( "../invoices" , ( ) => ( {
251+ invoicesService : {
252+ async getByStripeInvoiceId ( ) {
253+ return undefined ;
254+ } ,
255+ async create ( ) {
256+ return { id : "invoice-stub" } ;
257+ } ,
258258 } ,
259- } ,
260- } ) ) ;
259+ } ) ) ;
261260
262261// ---------------------------------------------------------------------------
263262// Test harness
0 commit comments