@@ -289,18 +289,30 @@ describe('grant-credits', () => {
289289 where : ( ) => Promise . resolve ( ) ,
290290 } ) ,
291291 } ) ,
292- // Note: grantCreditOperation uses `db.insert` directly when no debt,
293- // not `tx.insert`. This test verifies the debt-checking path works.
292+ insert : ( ) => ( {
293+ values : ( values : any ) => {
294+ insertedGrants . push ( values )
295+ return Promise . resolve ( )
296+ } ,
297+ } ) ,
294298 }
295299
296- // Since the function uses db.insert directly for no-debt case,
297- // we test by verifying that the debt check path returns empty array
298- // and the function completes without error
299- // The actual insert would hit the real DB, so we verify the flow instead
300-
301- // For a pure unit test, we'd need to inject the db dependency too
302- // This is a limitation of the current DI pattern
303- expect ( true ) . toBe ( true ) // Placeholder - function flow verified
300+ await grantCreditOperation ( {
301+ userId : 'user-123' ,
302+ amount : 500 ,
303+ type : 'free' ,
304+ description : 'Monthly free credits' ,
305+ expiresAt : futureDate ,
306+ operationId : 'new-grant-1' ,
307+ tx : mockTx as any ,
308+ logger,
309+ } )
310+
311+ // Should have created a new grant with full balance (no debt to deduct)
312+ expect ( insertedGrants . length ) . toBe ( 1 )
313+ expect ( insertedGrants [ 0 ] . principal ) . toBe ( 500 )
314+ expect ( insertedGrants [ 0 ] . balance ) . toBe ( 500 )
315+ expect ( insertedGrants [ 0 ] . description ) . toBe ( 'Monthly free credits' )
304316 } )
305317
306318 it ( 'should not create grant when debt exceeds amount' , async ( ) => {
@@ -368,165 +380,149 @@ describe('grant-credits', () => {
368380 describe ( 'revokeGrantByOperationId' , ( ) => {
369381 it ( 'should successfully revoke a grant with positive balance' , async ( ) => {
370382 const updatedValues : any [ ] = [ ]
371- let transactionCalled = false
372-
373- // Mock db.transaction
374- const mockDb = {
375- transaction : async < T > ( callback : ( tx : any ) => Promise < T > ) : Promise < T > => {
376- transactionCalled = true
377- const tx = {
378- query : {
379- creditLedger : {
380- findFirst : async ( ) => ( {
381- operation_id : 'grant-to-revoke' ,
382- user_id : 'user-123' ,
383- principal : 500 ,
384- balance : 300 , // 200 already consumed
385- type : 'purchase' ,
386- description : 'Purchased 500 credits' ,
387- } ) ,
388- } ,
389- } ,
390- update : ( ) => ( {
391- set : ( values : any ) => ( {
392- where : ( ) => {
393- updatedValues . push ( values )
394- return Promise . resolve ( )
395- } ,
383+
384+ const mockTransaction : BillingTransactionFn = async ( callback ) => {
385+ const tx = {
386+ query : {
387+ creditLedger : {
388+ findFirst : async ( ) => ( {
389+ operation_id : 'grant-to-revoke' ,
390+ user_id : 'user-123' ,
391+ principal : 500 ,
392+ balance : 300 , // 200 already consumed
393+ type : 'purchase' ,
394+ description : 'Purchased 500 credits' ,
396395 } ) ,
396+ } ,
397+ } ,
398+ update : ( ) => ( {
399+ set : ( values : any ) => ( {
400+ where : ( ) => {
401+ updatedValues . push ( values )
402+ return Promise . resolve ( )
403+ } ,
397404 } ) ,
398- }
399- return callback ( tx )
400- } ,
405+ } ) ,
406+ }
407+ return callback ( tx )
401408 }
402409
403- // Use the actual function with mocked db
404- const result = await mockDb . transaction ( async ( tx ) => {
405- const grant = await tx . query . creditLedger . findFirst ( { } )
406- if ( ! grant ) return false
407- if ( grant . balance < 0 ) return false
408-
409- await tx
410- . update ( )
411- . set ( {
412- principal : 0 ,
413- balance : 0 ,
414- description : `${ grant . description } (Revoked: Test refund)` ,
415- } )
416- . where ( )
417-
418- return true
410+ const result = await revokeGrantByOperationId ( {
411+ operationId : 'grant-to-revoke' ,
412+ reason : 'Test refund' ,
413+ logger,
414+ deps : { transaction : mockTransaction } ,
419415 } )
420416
421417 expect ( result ) . toBe ( true )
422- expect ( transactionCalled ) . toBe ( true )
423418 expect ( updatedValues . length ) . toBe ( 1 )
424419 expect ( updatedValues [ 0 ] . principal ) . toBe ( 0 )
425420 expect ( updatedValues [ 0 ] . balance ) . toBe ( 0 )
426421 expect ( updatedValues [ 0 ] . description ) . toContain ( 'Revoked: Test refund' )
427422 } )
428423
429424 it ( 'should return false when grant does not exist' , async ( ) => {
430- const mockDb = {
431- transaction : async < T > ( callback : ( tx : any ) => Promise < T > ) : Promise < T > => {
432- const tx = {
433- query : {
434- creditLedger : {
435- findFirst : async ( ) => null , // Grant not found
436- } ,
425+ const mockTransaction : BillingTransactionFn = async ( callback ) => {
426+ const tx = {
427+ query : {
428+ creditLedger : {
429+ findFirst : async ( ) => null , // Grant not found
437430 } ,
438- update : ( ) => ( {
439- set : ( ) => ( {
440- where : ( ) => Promise . resolve ( ) ,
441- } ) ,
431+ } ,
432+ update : ( ) => ( {
433+ set : ( ) => ( {
434+ where : ( ) => Promise . resolve ( ) ,
442435 } ) ,
443- }
444- return callback ( tx )
445- } ,
436+ } ) ,
437+ }
438+ return callback ( tx )
446439 }
447440
448- const result = await mockDb . transaction ( async ( tx ) => {
449- const grant = await tx . query . creditLedger . findFirst ( { } )
450- if ( ! grant ) return false
451- return true
441+ const result = await revokeGrantByOperationId ( {
442+ operationId : 'non-existent-grant' ,
443+ reason : 'Test refund' ,
444+ logger,
445+ deps : { transaction : mockTransaction } ,
452446 } )
453447
454448 expect ( result ) . toBe ( false )
455449 } )
456450
457451 it ( 'should return false when grant has negative balance' , async ( ) => {
458- const mockDb = {
459- transaction : async < T > ( callback : ( tx : any ) => Promise < T > ) : Promise < T > => {
460- const tx = {
461- query : {
462- creditLedger : {
463- findFirst : async ( ) => ( {
464- operation_id : 'debt-grant' ,
465- user_id : 'user-123' ,
466- principal : 500 ,
467- balance : - 100 , // User has overspent
468- type : 'free' ,
469- description : 'Monthly free credits' ,
470- } ) ,
471- } ,
472- } ,
473- update : ( ) => ( {
474- set : ( ) => ( {
475- where : ( ) => Promise . resolve ( ) ,
452+ const mockTransaction : BillingTransactionFn = async ( callback ) => {
453+ const tx = {
454+ query : {
455+ creditLedger : {
456+ findFirst : async ( ) => ( {
457+ operation_id : 'debt-grant' ,
458+ user_id : 'user-123' ,
459+ principal : 500 ,
460+ balance : - 100 , // User has overspent
461+ type : 'free' ,
462+ description : 'Monthly free credits' ,
476463 } ) ,
464+ } ,
465+ } ,
466+ update : ( ) => ( {
467+ set : ( ) => ( {
468+ where : ( ) => Promise . resolve ( ) ,
477469 } ) ,
478- }
479- return callback ( tx )
480- } ,
470+ } ) ,
471+ }
472+ return callback ( tx )
481473 }
482474
483- const result = await mockDb . transaction ( async ( tx ) => {
484- const grant = await tx . query . creditLedger . findFirst ( { } )
485- if ( ! grant ) return false
486- if ( grant . balance < 0 ) return false // Cannot revoke debt
487- return true
475+ const result = await revokeGrantByOperationId ( {
476+ operationId : 'debt- grant' ,
477+ reason : 'Test refund' ,
478+ logger ,
479+ deps : { transaction : mockTransaction } ,
488480 } )
489481
490482 expect ( result ) . toBe ( false )
491483 } )
492484
493- it ( 'should return false when grant balance is exactly zero' , async ( ) => {
494- const mockDb = {
495- transaction : async < T > ( callback : ( tx : any ) => Promise < T > ) : Promise < T > => {
496- const tx = {
497- query : {
498- creditLedger : {
499- findFirst : async ( ) => ( {
500- operation_id : 'depleted-grant' ,
501- user_id : 'user-123' ,
502- principal : 500 ,
503- balance : 0 , // Fully consumed
504- type : 'free' ,
505- description : 'Monthly free credits' ,
506- } ) ,
507- } ,
508- } ,
509- update : ( ) => ( {
510- set : ( values : any ) => ( {
511- where : ( ) => Promise . resolve ( ) ,
485+ it ( 'should successfully revoke a grant with zero balance' , async ( ) => {
486+ const updatedValues : any [ ] = [ ]
487+
488+ const mockTransaction : BillingTransactionFn = async ( callback ) => {
489+ const tx = {
490+ query : {
491+ creditLedger : {
492+ findFirst : async ( ) => ( {
493+ operation_id : 'depleted-grant' ,
494+ user_id : 'user-123' ,
495+ principal : 500 ,
496+ balance : 0 , // Fully consumed
497+ type : 'free' ,
498+ description : 'Monthly free credits' ,
512499 } ) ,
500+ } ,
501+ } ,
502+ update : ( ) => ( {
503+ set : ( values : any ) => ( {
504+ where : ( ) => {
505+ updatedValues . push ( values )
506+ return Promise . resolve ( )
507+ } ,
513508 } ) ,
514- }
515- return callback ( tx )
516- } ,
509+ } ) ,
510+ }
511+ return callback ( tx )
517512 }
518513
519- // Note: The actual revokeGrantByOperationId checks for balance < 0,
520- // so a balance of 0 would still be revoked (nothing to revoke though)
521- const result = await mockDb . transaction ( async ( tx ) => {
522- const grant = await tx . query . creditLedger . findFirst ( { } )
523- if ( ! grant ) return false
524- if ( grant . balance < 0 ) return false
525- // Balance of 0 is technically revocable but there's nothing to revoke
526- return true
514+ // Balance of 0 is not negative, so it can be revoked (nothing to actually revoke though)
515+ const result = await revokeGrantByOperationId ( {
516+ operationId : 'depleted-grant' ,
517+ reason : 'Test refund' ,
518+ logger,
519+ deps : { transaction : mockTransaction } ,
527520 } )
528521
529522 expect ( result ) . toBe ( true )
523+ expect ( updatedValues . length ) . toBe ( 1 )
524+ expect ( updatedValues [ 0 ] . principal ) . toBe ( 0 )
525+ expect ( updatedValues [ 0 ] . balance ) . toBe ( 0 )
530526 } )
531527 } )
532528} )
0 commit comments