BDD Test Results
Last Updated: April 2026
110 scenarios, all passing. Run with go test -v ./test/bdd/standalone/ in the service/ directory.
Feature: Full game simulation
Scenario: Two players compete, long wins when market rises
Given a game template with entry_fee 10 and reward 18 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then the match should be in "open" status
And player A wallet should have 40 USDT
And player B wallet should have 40 USDT
When BTCUSDT price is set to 95.0
And player A opens a "buy" position with margin 10.0 and leverage 2 at price 95.0
And player B opens a "sell" position with margin 10.0 and leverage 2 at price 95.0
Then player A should have an "open" position
And player B should have an "open" position
When BTCUSDT price changes to 110.0
And player A closes their position
And player B closes their position
Then player A position should be "closed" with positive profit
And player B position should be "closed" with negative profit
When the match is closed and rewards are calculated
Then player A should be the winner
And player A wallet should have 58 USDT
And player A should have gained XP and cups
Scenario: Match auto-starts when room is full
Given a game template with entry_fee 0 and reward 0 and duration "1m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
When player A joins the match
Then the match should be in "waiting" status
When player B joins the match
Then the match should be in "open" status
Scenario: Player cannot join a full match
Given a game template with entry_fee 0 and reward 0 and duration "1m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player C has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then player C should fail to join the match with error "not in waiting status"
Scenario: Player with insufficient balance cannot join
Given a game template with entry_fee 100 and reward 180 and duration "1m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
Then player A should fail to join the match with error "insufficient balance"Feature: Risk management
Scenario: Player is disqualified when total drawdown limit exceeded
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And the game has max_total_drawdown_pct of 30.0
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And player A opens a "buy" position with margin 50.0 and leverage 2 at price 100.0
And BTCUSDT price changes to 70.0
Then player A should be disqualified
Scenario: Disqualified player cannot open new positions
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And the game has max_total_drawdown_pct of 10.0
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And player A opens a "buy" position with margin 50.0 and leverage 2 at price 100.0
And BTCUSDT price changes to 85.0
Then player A should be disqualified
And player A should fail to open a position with error "disqualified"
Scenario: Trading is locked on a match
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When admin locks trading on the match
And BTCUSDT price is set to 100.0
Then player A should fail to open a position with error "trading is locked"
Scenario: Max loss per trade prevents oversized positions
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And the game has max_loss_per_trade_pct of 10.0
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
Then player A should fail to open a position with margin 20.0 and error "max loss per trade"Feature: Challenge rule engine
Scenario: Symbol not in allowed list is rejected
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And the game allows only symbols "BTCUSDT,ETHUSDT"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
Then player A should fail to open a "SOLUSDT" position with error "symbol not allowed"
Scenario: Leverage exceeds challenge max is rejected
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And the game has max_leverage of 5
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
Then player A should fail to open a position with leverage 10 and error "leverage exceeds"
Scenario: Short position rejected in long-only challenge
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And the game allows only "long_only" direction
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
Then player A should fail to open a "sell" direction position with error "direction not allowed"
And player A opens a "buy" position with margin 10.0 and leverage 1 at price 100.0
And player A should have an "open" positionFeature: Match lifecycle and refunds
Scenario: Admin cancels a waiting match and players get refunded
Given a game template with entry_fee 10 and reward 18 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player A joins the match
Then player A wallet should have 40 USDT
When admin cancels the match
Then the match should be in "canceled" status
And player A wallet should have 50 USDT
Scenario: Admin cancels match with two players, both refunded
Given a game template with entry_fee 20 and reward 36 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then player A wallet should have 30 USDT
And player B wallet should have 30 USDT
When admin cancels the match
Then the match should be in "canceled" status
And player A wallet should have 50 USDT
And player B wallet should have 50 USDT
Scenario: Cannot cancel a closed match
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And the match is closed and rewards are calculated
Then admin should fail to cancel match with error "cannot be canceled"Feature: Anti-cheat and account suspension
Scenario: Suspended user cannot join a match
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player A is suspended with reason "cheating detected"
Then player A should fail to join the match with error "suspended"
Scenario: User can join after suspension is lifted
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A is suspended with reason "under review"
And player A suspension is lifted
And player A joins the match
And player B joins the match
Then the match should be in "open" statusFeature: Friend system
Scenario: Send and accept friend request
Given player A exists
And player B exists
When player A sends a friend request to player B
Then player B should have 1 pending friend request
When player B accepts the friend request
Then player A should have 1 friend
And player B should have 1 friend
Scenario: Reject friend request
Given player A exists
And player B exists
When player A sends a friend request to player B
And player B rejects the friend request
Then player B should have 0 pending friend requests
And player A should have 0 friends
Scenario: Cannot send friend request to yourself
Given player A exists
Then player A should fail to send friend request to self with error "cannot send friend request to yourself"
Scenario: Cannot send duplicate friend request
Given player A exists
And player B exists
When player A sends a friend request to player B
Then player A should fail to send another request to player B with error "already friends"
Scenario: Remove friend
Given player A exists
And player B exists
When player A sends a friend request to player B
And player B accepts the friend request
Then player A should have 1 friend
When player A removes the friendship
Then player A should have 0 friendsFeature: Analytics and leaderboard
Scenario: Dashboard shows correct stats after a completed match
Given a game template with entry_fee 10 and reward 18 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And player A opens a "buy" position with margin 10.0 and leverage 1 at price 100.0
And BTCUSDT price changes to 110.0
And player A closes their position
And the match is closed and rewards are calculated
Then the dashboard should show 2 total users
And the dashboard should show 1 total matches
Scenario: Leaderboard ranks users by cup points
Given player A exists with 100 cup points
And player B exists with 200 cup points
And player C exists with 50 cup points
Then the leaderboard first place should be player B
And the leaderboard second place should be player A
And the leaderboard third place should be player C
Scenario: Match summary reports trade stats after match
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And player A opens a "buy" position with margin 10.0 and leverage 1 at price 100.0
And BTCUSDT price changes to 110.0
And player A closes their position
And the match is closed and rewards are calculated
Then the match summary should show 2 total trades
And the match summary should show 2 total participants
Scenario: Emergency stop locks all active matches
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then the match should be in "open" status
When admin triggers emergency stop
Then player A should fail to open a position with error "trading is locked"Feature: Admin match management
Scenario: Admin manually starts a waiting match with enough players
Given a game template with entry_fee 0 and reward 0 and duration "5m" and max_players 3
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then the match should be in "waiting" status
When admin manually starts the match
Then the match should be in "open" status
Scenario: Cannot start a match without enough players
Given a game template with entry_fee 0 and reward 0 and duration "5m" and max_players 3
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player A joins the match
Then admin should fail to start match with error "min players not met"
Scenario: Cannot start an already open match
Given a game template with entry_fee 0 and reward 0 and duration "1m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then the match should be in "open" status
And admin should fail to start match with error "not in waiting status"
Scenario: Admin force-closes an open match
Given a game template with entry_fee 0 and reward 0 and duration "1m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then the match should be in "open" status
When admin force-closes the match
Then the match should be in "closed" statusFeature: Position management
Scenario: Update stop loss and take profit on open position
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And player A opens a "buy" position with margin 10.0 and leverage 1 at price 100.0
Then player A should have an "open" position
When player A updates position with SL 90.0 and TP 120.0
Then player A position should have SL 90.0 and TP 120.0Feature: Automatic match closure
Scenario: Expired match gets auto-closed with rewards
Given a game template with entry_fee 0 and reward 10 and duration "1s"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then the match should be in "open" status
When we wait for the match to expire
And ProcessCloseMatches runs
Then the match should be in "closed" statusFeature: Rule enforcement
Scenario: Trading frequency limit blocks rapid trades
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And the game has max_trades_per_minute of 2
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And player A opens a "buy" position with margin 5.0 and leverage 1 at price 100.0
And player A opens a "buy" position with margin 5.0 and leverage 1 at price 100.0
Then player A should fail to open a position with error "trading frequency"
Scenario: Min trades excludes inactive player from winning
Given a game template with entry_fee 0 and reward 10 and duration "5m"
And the game has min_trades of 1
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
When BTCUSDT price is set to 100.0
And player B opens a "buy" position with margin 5.0 and leverage 1 at price 100.0
And BTCUSDT price changes to 110.0
And player B closes their position
And the match is closed and rewards are calculated
Then player B should be the winnerFeature: Authentication
Scenario: New user requests OTP and auto-registers
Given a fresh auth service
When user "alice@test.com" requests an OTP
Then the OTP request should succeed with expiry 120
And user "alice@test.com" should exist in the database
Scenario: User logs in with correct OTP
Given a fresh auth service
And user "bob@test.com" requests an OTP
When user "bob@test.com" logs in with the correct OTP
Then the login should return valid tokens
Scenario: User login fails with wrong OTP
Given a fresh auth service
And user "charlie@test.com" requests an OTP
When user "charlie@test.com" logs in with OTP "99999"
Then the login should fail with error "invalid verification code"
Scenario: Token refresh returns new access token
Given a fresh auth service
And user "dave@test.com" requests an OTP
And user "dave@test.com" logs in with the correct OTP
When the user refreshes their token
Then the refresh should return a new access token
Scenario: Inactive user cannot request OTP
Given a fresh auth service
And an inactive user "banned@test.com" exists
When user "banned@test.com" requests an OTP expecting error
Then the OTP request should fail with error "inactive user"Feature: User management
Scenario: Create and retrieve a user
Given a fresh user service
When a user is created with email "alice@test.com"
Then the user should exist with email "alice@test.com"
Scenario: Update user nickname
Given a fresh user service
And a user is created with email "bob@test.com"
When the user nickname is updated to "BobTrader"
Then the user nickname should be "BobTrader"
Scenario: Suspend and unsuspend a user
Given a fresh user service
And a user is created with email "charlie@test.com"
When the user is suspended with reason "cheating"
Then the user should be suspended
When the user suspension is lifted
Then the user should not be suspended
Scenario: Delete a user
Given a fresh user service
And a user is created with email "dave@test.com"
When the user is deleted
Then the user should not exist
Scenario: List users with pagination
Given a fresh user service
And a user is created with email "user1@test.com"
And a user is created with email "user2@test.com"
And a user is created with email "user3@test.com"
Then the users page should have 3 total usersFeature: Wallet management
Scenario: Create a wallet for a user
Given a fresh wallet service
And a registered user exists
When a USDT wallet is created for the user
Then the wallet should exist with 0 balance
Scenario: Deposit to a wallet
Given a fresh wallet service
And a registered user with a USDT wallet exists
When 100 USDT is deposited via the wallet mock
Then the wallet balance should be 100
Scenario: Withdraw from a wallet
Given a fresh wallet service
And a registered user with a USDT wallet exists
And 100 USDT is deposited via the wallet mock
When 30 USDT is withdrawn via the wallet mock
Then the wallet balance should be 70
Scenario: Withdraw more than balance fails
Given a fresh wallet service
And a registered user with a USDT wallet exists
When 100 USDT withdrawal is attempted via the wallet mock
Then the withdrawal should fail with error "insufficient balance"Feature: Transaction management
Scenario: Create a deposit transaction
Given a fresh transaction service
And a user with a wallet exists
When a deposit transaction of 100 is created
Then the transaction should exist with type "deposit" and amount 100
Scenario: Create a withdrawal transaction
Given a fresh transaction service
And a user with a wallet exists
When a withdrawal transaction of 50 is created
Then the transaction should exist with type "withdraw" and amount 50
Scenario: List transactions for a wallet
Given a fresh transaction service
And a user with a wallet exists
And a deposit transaction of 100 is created
And a withdrawal transaction of 30 is created
Then the transactions page should have 2 total transactionsFeature: Achievement management
Scenario: Create an achievement
Given a fresh achievement service
When an achievement is created with title "First Win"
Then the achievement should exist with title "First Win"
Scenario: Update an achievement
Given a fresh achievement service
And an achievement is created with title "First Win"
When the achievement title is updated to "Champion"
Then the achievement should have title "Champion"
Scenario: Delete an achievement
Given a fresh achievement service
And an achievement is created with title "To Delete"
When the achievement is deleted
Then the achievement should not exist
Scenario: List achievements
Given a fresh achievement service
And an achievement is created with title "Win 1"
And an achievement is created with title "Win 5"
Then the achievements page should have 2 total achievementsFeature: Consumable management
Scenario: Create a consumable
Given a fresh consumable service
When a consumable is created with title "XP Boost" and type "exp" and value 100
Then the consumable should exist with title "XP Boost"
Scenario: Apply EXP consumable to user
Given a fresh consumable service
And a registered user exists
And a consumable is created with title "XP Boost" and type "exp" and value 50
When the consumable is applied to the user
Then the user should have 50 exp
Scenario: Apply VBalance consumable to user
Given a fresh consumable service
And a registered user exists
And a consumable is created with title "Balance Boost" and type "v_balance" and value 25
When the consumable is applied to the user
Then the user v_balance should have increased
Scenario: Delete a consumable
Given a fresh consumable service
And a consumable is created with title "To Remove" and type "exp" and value 10
When the consumable is deleted
Then the consumable should not existFeature: Market data
Scenario: Get asset price after setting it
Given BTCUSDT price is set to 95000.0
Then the market should return BTCUSDT price of 95000.0
Scenario: Price changes propagate
Given BTCUSDT price is set to 50000.0
When BTCUSDT price changes to 55000.0
Then the market should return BTCUSDT price of 55000.0
Scenario: Multiple assets tracked independently
Given BTCUSDT price is set to 95000.0
And ETHUSDT price is set to 3500.0
Then the market should return BTCUSDT price of 95000.0
And the market should return ETHUSDT price of 3500.0Feature: Notification system
Scenario: OTP email is captured on auth request
Given a fresh auth service
When user "test@test.com" requests an OTP
Then an OTP email should have been sent to "test@test.com"
Scenario: In-app notifications are recorded
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
When player A joins the match
Then notifications should have been sent
Scenario: Notification count tracks correctly
Given a game template with entry_fee 0 and reward 0 and duration "5m"
And admin creates a match room from the game
And player A has a wallet with 50 USDT
And player B has a wallet with 50 USDT
And player A joins the match
And player B joins the match
Then the notification count should be greater than 0Test Harness
The BDD tests use a standalone harness (test/bdd/standalone/) with:
- In-memory SQLite (
cache=shared) for database - MockMarket — channel-based price feed simulation
- NotificationRecorder — captures notifications + OTP emails for assertions
- redismock — in-memory Redis mock for auth OTP flow
- gomock — mocks for Solana HD wallet, sequence counter, webshare, etc.
- No external services — no NATS, Redis, Postgres, Binance, Temporal required
Each scenario gets a fresh database and isolated state via context.WithValue.
Last updated on