Skip to content

Commit 679694b

Browse files
committed
Merge commit 'refs/pull/1050/head'
# Conflicts: # app/stores/__tests__/creditStore.test.ts # app/stores/creditStore.ts # contracts/credit/src/test.rs # jest.config.js # pnpm-workspace.yaml # src/screens/CancellationFlowScreen.tsx # src/types/fraud.ts
2 parents 7f47d97 + cb9610f commit 679694b

18 files changed

Lines changed: 860 additions & 334 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useCreditStore } from '../creditStore';
2+
3+
describe('credit store performance', () => {
4+
it('processes 100 credit applications within the local budget', () => {
5+
useCreditStore.setState({
6+
accounts: {},
7+
nextId: 0,
8+
wallets: {},
9+
nextWalletId: 0,
10+
walletTransactionIds: {},
11+
now: () => 1_000,
12+
});
13+
const store = useCreditStore.getState();
14+
const startedAt = performance.now();
15+
16+
for (let index = 0; index < 100; index += 1) {
17+
store.issueCredit(`subscriber-${index}`, 100, 'benchmark');
18+
store.applyCredit(`subscriber-${index}`, `subscription-${index}`, 50);
19+
}
20+
21+
expect(performance.now() - startedAt).toBeLessThan(1_500);
22+
});
23+
});

app/stores/__tests__/creditStore.test.ts

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { useCreditStore } from '../creditStore';
22

33
let clock = 1000;
44
const 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

714
beforeEach(() => {
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

Comments
 (0)