Skip to main content

Sanity Testing V1 — Financial Calculation Verification

Scope

In Scope

  • Betfair exchange bets only (bookmaker = 'betfair-ex', decimal odds)
  • Back and lay bet placement, settlement, reversal
  • Agent hierarchy take propagation (Admin → Master Agent → Sub-Agent → Player)
  • Commission calculation on exchange markets
  • Exposure and available credit tracking
  • Multi-bet market settlement across users
  • System-wide point conservation invariants

Out of Scope

  • Bifrost / Indian odds bets
  • Pinnacle sportsbook bets
  • B-Book routing and split-venue orders
  • Frontend calculations (calcLayLiability, betslip display)
  • Real Betfair API calls (PAL adapter is mocked)
  • WebSocket / SSE real-time events
  • Concurrent bet placement / race conditions

Test Infrastructure

  • Framework: Vitest + Supertest (existing setup)
  • Database: Real Prisma queries against hannibal_test PostgreSQL
  • Mocked: Betfair PAL adapter (orders go straight to accepted), Redis (existing mock)
  • Not Mocked: OrderService, SettlementService, TakeService, CommissionService — all run real business logic
  • Location: backend/tests/integration/sanity/
  • Run command: vitest run --config vitest.integration.config.ts

Canonical Test Values

  • Odds: 3.50 (decimal), 1.50, 2.00, 10.00 (edge: high odds)
  • Stakes: 100, 500, 1000
  • Credit Limits: Player=1000, Sub-Agent=5000, Master Agent=10000
  • Commission Rate: 2% (platform default for exchange)

Invariants (checked after every scenario)

These are the "sanity" assertions — if any fail, something is fundamentally broken.

  1. Point Conservationsum(all user balances) + treasury = constant (no points created or destroyed except mint/commission)
  2. Take Consistencytake = balance - creditLimit for every entity at every level
  3. Exposure Consistencyexposure = sum(open order liabilities) matches what the system reports
  4. Settlement Completeness — Settled orders have non-null profitLoss, settlementOutcome, settledAt
  5. No Double Settlement — Settling the same market twice doesn't double-credit any user

Scenarios

1. Back Bet Lifecycle

Setup: Player with balance=1000, creditLimit=1000

1a. Back Bet — Win

StepActionExpected
1Place back bet: stake=100, odds=3.50Balance decremented by 100 → 900
2Settle market: player's selection winsBalance += stake × odds = 350 → 1250
3Verify P&LprofitLoss = +250 (= 350 - 100)
4Verify order statestatus=settled, settlementOutcome=win
5Check invariantsPoint conservation, take = 1250 - 1000 = +250

1b. Back Bet — Lose

StepActionExpected
1Place back bet: stake=100, odds=3.50Balance → 900
2Settle market: player's selection losesBalance unchanged → 900
3Verify P&LprofitLoss = -100
4Check invariantsTake = 900 - 1000 = -100

1c. Back Bet — Void

StepActionExpected
1Place back bet: stake=100, odds=3.50Balance → 900
2Settle as voidBalance += 100 (refund) → 1000
3Verify P&LprofitLoss = 0
4Check invariantsTake = 1000 - 1000 = 0

2. Lay Bet Lifecycle

Setup: Player with balance=1000, creditLimit=1000

Key formula: Lay liability = stake × (odds - 1)

2a. Lay Bet — Win (selection did NOT win)

StepActionExpected
1Place lay bet: stake=100, odds=3.50Liability = 100 × 2.50 = 250. Balance → 750
2Settle: player's selection loses (lay wins)Balance += liability + stake = 250 + 100 = 350 → 1100
3Verify P&LprofitLoss = +100 (won the stake)
4Check invariantsTake = 1100 - 1000 = +100

2b. Lay Bet — Lose (selection won)

StepActionExpected
1Place lay bet: stake=100, odds=3.50Liability = 250. Balance → 750
2Settle: player's selection wins (lay loses)Balance unchanged → 750
3Verify P&LprofitLoss = -250 (lost the liability)
4Check invariantsTake = 750 - 1000 = -250

2c. Lay Bet — Void

StepActionExpected
1Place lay bet: stake=100, odds=3.50Liability = 250. Balance → 750
2Settle as voidBalance += 250 (refund) → 1000
3Verify P&LprofitLoss = 0
4Check invariantsTake = 0

3. Agent Hierarchy & Take Propagation

Setup:

Admin (treasury=100000)
└── Master Agent (CL=10000, balance=10000)
└── Sub-Agent (CL=5000, balance=5000)
└── Player (CL=1000, balance=1000)

3a. Player Wins — Take Propagates Up

StepActionExpected
1Player places back bet: stake=100, odds=3.50Player balance → 900
2Settle: player winsPlayer balance → 1250
3Check player taketake = 1250 - 1000 = +250 (player profited)
4Check sub-agent takeSub-agent's downline AC increased by 250, take adjusts accordingly
5Check master agent takeMaster agent's downline AC reflects sub-agent change

3b. Player Loses — Take Propagates Up

StepActionExpected
1Player places back bet: stake=100, odds=3.50Player balance → 900
2Settle: player losesPlayer balance → 900
3Check player taketake = 900 - 1000 = -100 (player owes upline)
4Check sub-agent takeReflects player's loss in downline
5Check master agent takeReflects cascaded impact

3c. Multiple Players Under Same Agent

StepActionExpected
1Player A places back bet: stake=200, odds=2.00Player A balance → 800
2Player B places back bet: stake=100, odds=4.00Player B balance → 900
3Settle: Player A wins, Player B losesA: +200, B: -100
4Check agent takeNet = +200 - 100 = +100 reflected in agent downline

4. Commission on Exchange Markets

Setup: Player with balance=5000, creditLimit=1000. Commission rate=2%.

4a. Net Profit — Commission Charged

StepActionExpected
1Player places back bet: stake=500, odds=2.00Balance → 4500
2Settle: player winsBalance += 1000 → 5500. P&L = +500
3Commission calculationNet market P&L = +500. Commission = 500 × 0.02 = 10
4Verify balance after commissionBalance = 5500 - 10 = 5490
5Verify commission recordRecord exists with amount=10, marketId, userId

4b. Net Loss — No Commission

StepActionExpected
1Player places back bet: stake=500, odds=2.00Balance → 4500
2Settle: player losesBalance unchanged → 4500. P&L = -500
3Commission calculationNet market P&L = -500. Commission = 0
4Verify no commission recordNo record created

4c. Multiple Bets in Same Market — Net P&L Commission

StepActionExpected
1Player places back bet on Outcome A: stake=300, odds=2.00Balance → 4700
2Player places back bet on Outcome B: stake=200, odds=3.00Balance → 4500
3Settle: Outcome A winsBet 1 wins (+300), Bet 2 loses (-200). Net = +100
4Commission100 × 0.02 = 2
5Final balance4500 + 600 + 0 - 2 = 5098

5. Exposure & Available Credit

Setup: Player with balance=2000, creditLimit=2000

5a. Exposure Increases with Open Bets

StepActionExpected
1Place back bet: stake=100, odds=2.00Exposure += 100. Balance → 1900
2Place lay bet: stake=100, odds=3.00Exposure += 200 (liability). Balance → 1700
3Verify total exposure100 + 200 = 300

5b. Exposure Decreases on Settlement

StepActionExpected
1Start with 2 open bets from 5aExposure = 300
2Settle first market (back bet settles)Exposure drops by 100 → 200
3Settle second market (lay bet settles)Exposure drops by 200 → 0

5c. Exposure Decreases on Cancellation

StepActionExpected
1Place back bet: stake=200, odds=2.50Exposure = 200. Balance → 1800
2Cancel the betExposure → 0. Balance → 2000 (refund)

6. Settlement Reversal

Setup: Player with balance=1000, creditLimit=1000

6a. Reverse a Win, Re-Settle as Lose

StepActionExpected
1Place back bet: stake=100, odds=3.50Balance → 900
2Settle: winBalance → 1250. P&L = +250
3Reverse settlementBalance → 900 (back to pre-settlement). Order status → accepted
4Re-settle: loseBalance → 900. P&L = -100
5Check invariantsTake = 900 - 1000 = -100

6b. Reverse with Commission — Commission Also Reversed

StepActionExpected
1Place back bet: stake=500, odds=2.00. Settle: winBalance = 1500 - 10 (commission) = 1490
2Reverse settlementBalance → 500. Commission record deleted
3Re-settle: loseBalance → 500. No commission (net loss)

6c. Reverse a Lay Bet Settlement

StepActionExpected
1Place lay bet: stake=100, odds=3.50Liability=250. Balance → 750
2Settle: lay winsBalance → 1100. P&L = +100
3ReverseBalance → 750 (liability still locked). Order → accepted
4Re-settle: lay losesBalance → 750. P&L = -250

7. Multi-Bet Market Settlement

Setup: 3 players, same market, different bets

7a. Mixed Back and Lay Bets on Same Market

PlayerBet TypeSelectionStakeOdds
Player ABackTeam 12002.50
Player BBackTeam 21503.00
Player CLayTeam 11002.50

Settle: Team 1 wins

PlayerOutcomeP&LBalance Change
AWin+300+500 (payout)
BLose-1500
CLose (lay)-1500
StepActionExpected
1Settle market with Team 1 as winnerAll 3 orders settled in single pass
2Verify each player's balanceMatches table above
3Verify commission per playerA: 300 × 0.02 = 6. B: 0. C: 0
4System-wide point conservationTotal points before = total points after + commission deducted

8. Edge Cases

8a. Minimum Odds (1.01)

  • Back bet: stake=100, odds=1.01 → P&L on win = +1
  • Lay bet: stake=100, odds=1.01 → liability = 1, P&L on lose = -1

8b. High Odds (1000.00)

  • Back bet: stake=10, odds=1000.00 → P&L on win = +9990
  • Lay bet: stake=10, odds=1000.00 → liability = 9990

8c. Insufficient Balance — Bet Rejected

  • Player balance=50, attempts stake=100 → order rejected, balance unchanged

8d. Settle Already Settled Market — Idempotent

  • Settle market → verify state
  • Settle same market again → no changes, no errors

8e. Void After Win Reversal

  • Settle as win → reverse → settle as void → verify full refund