@@ -2,7 +2,14 @@ import { useCreditStore } from '../creditStore';
22
33let clock = 1000 ;
44const reset = ( ) =>
5- useCreditStore . setState ( { accounts : { } , wallets : { } , nextId : 0 , now : ( ) => clock } ) ;
5+ useCreditStore . setState ( {
6+ accounts : { } ,
7+ nextId : 0 ,
8+ wallets : { } ,
9+ nextWalletId : 0 ,
10+ walletTransactionIds : { } ,
11+ now : ( ) => clock ,
12+ } ) ;
613
714beforeEach ( ( ) => {
815 clock = 1000 ;
@@ -67,53 +74,60 @@ describe('useCreditStore', () => {
6774 expect ( s ( ) . getBalance ( 'alice' ) ) . toBe ( 0 ) ;
6875 } ) ;
6976
70- it ( 'deposits credit into an account balance' , ( ) => {
71- s ( ) . depositCredit ( 'alice' , 200 , 'prepaid top-up' ) ;
72- expect ( s ( ) . getBalance ( 'alice' ) ) . toBe ( 200 ) ;
73- expect (
74- s ( )
75- . getAccount ( 'alice' )
76- . transactions . some ( ( t ) => t . kind === 'deposit' && t . amount === 200 )
77- ) . toBe ( true ) ;
77+ it ( 'manages wallet deposits, withdrawals, and charge drawdowns' , ( ) => {
78+ const walletId = s ( ) . createWallet ( 'alice' , 'sub_1' , 'USD' ) ;
79+ expect ( s ( ) . deposit ( 'alice' , walletId , 1000 ) ) . toMatchObject ( {
80+ walletId,
81+ balance : 1000 ,
82+ transactionId : 0 ,
83+ } ) ;
84+ expect ( s ( ) . drawdown ( 'alice' , walletId , 250 ) ) . toMatchObject ( {
85+ balance : 750 ,
86+ transactionId : 1 ,
87+ } ) ;
88+ expect ( s ( ) . withdraw ( 'alice' , walletId , 100 ) ) . toMatchObject ( {
89+ balance : 650 ,
90+ transactionId : 2 ,
91+ } ) ;
92+ expect ( s ( ) . getWallet ( walletId ) ) . toMatchObject ( {
93+ balance : 650 ,
94+ totalDeposited : 1000 ,
95+ totalDrawn : 250 ,
96+ totalWithdrawn : 100 ,
97+ transactions : [
98+ { id : 0 , kind : 'deposit' , amount : 1000 , balanceAfter : 1000 } ,
99+ { id : 1 , kind : 'drawdown' , amount : 250 , balanceAfter : 750 } ,
100+ { id : 2 , kind : 'withdraw' , amount : 100 , balanceAfter : 650 } ,
101+ ] ,
102+ } ) ;
78103 } ) ;
79104
80- it ( 'withdraws available credit and rejects overdrafts' , ( ) => {
81- s ( ) . issueCredit ( 'alice' , 300 , 'promo' ) ;
82- expect ( s ( ) . withdrawCredit ( 'alice' , 100 , 'cash-out' ) ) . toBe ( true ) ;
83- expect ( s ( ) . getBalance ( 'alice' ) ) . toBe ( 200 ) ;
84- expect ( s ( ) . withdrawCredit ( 'alice' , 500 , 'cash-out' ) ) . toBe ( false ) ;
85- expect ( s ( ) . getBalance ( 'alice' ) ) . toBe ( 200 ) ;
105+ it ( 'rejects unauthorized and overdrawn wallet operations' , ( ) => {
106+ const walletId = s ( ) . createWallet ( 'alice' , 'sub_1' , 'USD' ) ;
107+ s ( ) . deposit ( 'alice' , walletId , 100 ) ;
108+ expect ( s ( ) . drawdown ( 'alice' , walletId , 101 ) ) . toBeUndefined ( ) ;
109+ expect ( s ( ) . withdraw ( 'bob' , walletId , 1 ) ) . toBeUndefined ( ) ;
86110 } ) ;
87111
88- it ( 'computes a consolidated account balance summary' , ( ) => {
89- useCreditStore . getState ( ) . wallets = {
90- 'w-1' : {
91- id : 'w-1' ,
92- subscriber : 'alice' ,
93- currency : 'USD' ,
94- balance : 75 ,
95- totalDeposited : 100 ,
96- totalWithdrawn : 25 ,
97- } ,
98- } ;
99-
100- s ( ) . issueCredit ( 'alice' , 250 , 'refund' ) ;
101- s ( ) . applyCredit ( 'alice' , 'sub_1' , 50 ) ;
102-
103- const balance = s ( ) . getAccountBalance ( 'alice' ) ;
104- expect ( balance . subscriber ) . toBe ( 'alice' ) ;
105- expect ( balance . availableCredit ) . toBe ( 200 ) ;
106- expect ( balance . totalIssued ) . toBe ( 250 ) ;
107- expect ( balance . totalApplied ) . toBe ( 50 ) ;
108- expect ( balance . prepaymentBalance ) . toBe ( 75 ) ;
109- expect ( balance . netBalance ) . toBe ( 275 ) ;
112+ it ( 'does not expose mutable account or wallet state' , ( ) => {
113+ s ( ) . issueCredit ( 'alice' , 100 , 'promo' ) ;
114+ const account = s ( ) . getAccount ( 'alice' ) ;
115+ account . lots [ 0 ] . remaining = 0 ;
116+ expect ( s ( ) . getBalance ( 'alice' ) ) . toBe ( 100 ) ;
117+
118+ const walletId = s ( ) . createWallet ( 'alice' , 'sub_1' , 'USD' ) ;
119+ const wallet = s ( ) . getWallet ( walletId ) ;
120+ if ( wallet ) wallet . balance = 99 ;
121+ expect ( s ( ) . getWallet ( walletId ) ?. balance ) . toBe ( 0 ) ;
110122 } ) ;
111123
112- it ( 'returns account balances for all known subscribers' , ( ) => {
113- s ( ) . issueCredit ( 'alice' , 100 , 'promo' ) ;
114- s ( ) . issueCredit ( 'bob' , 200 , 'promo' ) ;
115- const balances = s ( ) . getAccountBalances ( ) ;
116- expect ( balances ) . toHaveLength ( 2 ) ;
117- expect ( balances . map ( ( b ) => b . subscriber ) . sort ( ) ) . toEqual ( [ 'alice' , 'bob' ] ) ;
124+ it ( 'retains only the bounded recent transaction history' , ( ) => {
125+ for ( let index = 0 ; index < 130 ; index += 1 ) {
126+ s ( ) . issueCredit ( 'alice' , 1 , `grant-${ index } ` ) ;
127+ }
128+ const history = s ( ) . getAccount ( 'alice' ) . transactions ;
129+ expect ( history ) . toHaveLength ( 128 ) ;
130+ expect ( history [ 0 ] . reason ) . toBe ( 'grant-2' ) ;
131+ expect ( history [ 127 ] . reason ) . toBe ( 'grant-129' ) ;
118132 } ) ;
119133} ) ;
0 commit comments