diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-24 17:36:19 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-24 17:36:19 +0200 |
| commit | 828eb472701af16b7bd913111273b62d22ea3627 (patch) | |
| tree | 48ab3876cc1e2b7420609c7c1b016c939c9b84d1 /tests | |
| parent | TOFU host trust: SPKI fingerprint pinning, tests, forget-host (diff) | |
| download | zagan-bet-828eb472701af16b7bd913111273b62d22ea3627.tar.gz zagan-bet-828eb472701af16b7bd913111273b62d22ea3627.zip | |
Fix critical game bugs: payout, sessions, balance type, render_lines; add AGENTS.md and regression tests
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/slots.rs | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/tests/slots.rs b/tests/slots.rs new file mode 100644 index 0000000..c50f425 --- /dev/null +++ b/tests/slots.rs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | //! Regression tests for the server-side slots game logic. | ||
| 2 | |||
| 3 | use zagan_bet::server_game_traits::{Game, GamePhase}; | ||
| 4 | use zagan_bet::server_slots::SlotsGame; | ||
| 5 | |||
| 6 | fn make_game() -> SlotsGame { | ||
| 7 | SlotsGame::new() | ||
| 8 | } | ||
| 9 | |||
| 10 | /// Three of a kind pays the three-of-a-kind multiplier. | ||
| 11 | #[test] | ||
| 12 | fn three_of_a_kind_pays() { | ||
| 13 | let game = make_game(); | ||
| 14 | // CCC = 5x | ||
| 15 | assert_eq!(game.calculate_payout(&['C', 'C', 'C'], 100), 500); | ||
| 16 | // SSS = 100x | ||
| 17 | assert_eq!(game.calculate_payout(&['S', 'S', 'S'], 10), 1000); | ||
| 18 | } | ||
| 19 | |||
| 20 | /// Two adjacent matching symbols pays the two-of-a-kind multiplier. | ||
| 21 | #[test] | ||
| 22 | fn two_adjacent_pays() { | ||
| 23 | let game = make_game(); | ||
| 24 | // CC? = 2x | ||
| 25 | assert_eq!(game.calculate_payout(&['C', 'C', 'D'], 100), 200); | ||
| 26 | // ?LL = 3x | ||
| 27 | assert_eq!(game.calculate_payout(&['D', 'L', 'L'], 50), 150); | ||
| 28 | } | ||
| 29 | |||
| 30 | /// Non-adjacent matching symbols (e.g. [C,D,C]) must also pay. | ||
| 31 | /// This was a bug: the old code only checked pairs (0,1) and (1,2). | ||
| 32 | #[test] | ||
| 33 | fn two_non_adjacent_pays() { | ||
| 34 | let game = make_game(); | ||
| 35 | // C,D,C — two cherries at positions 0 and 2 | ||
| 36 | assert_eq!(game.calculate_payout(&['C', 'D', 'C'], 100), 200); | ||
| 37 | // S,X,S — two sevens at positions 0 and 2 | ||
| 38 | assert_eq!(game.calculate_payout(&['S', 'L', 'S'], 10), 200); | ||
| 39 | // L,O,L | ||
| 40 | assert_eq!(game.calculate_payout(&['L', 'O', 'L'], 50), 150); | ||
| 41 | } | ||
| 42 | |||
| 43 | /// No matching symbols returns 0. | ||
| 44 | #[test] | ||
| 45 | fn no_match_returns_zero() { | ||
| 46 | let game = make_game(); | ||
| 47 | assert_eq!(game.calculate_payout(&['C', 'L', 'O'], 100), 0); | ||
| 48 | assert_eq!(game.calculate_payout(&['G', 'D', 'S'], 50), 0); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// process_action produces a finished state with correct reels and win_type. | ||
| 52 | #[test] | ||
| 53 | fn process_action_spin_sets_finished() { | ||
| 54 | let game = make_game(); | ||
| 55 | let state = game.prepare_state("alice", 100); | ||
| 56 | assert_eq!(state.phase, GamePhase::WaitingToStart); | ||
| 57 | |||
| 58 | // Manually set reels by going through process_action. | ||
| 59 | // Since spin is random, we test the state machine rather than the outcome. | ||
| 60 | let result = game.process_action(&state, "spin", &[]); | ||
| 61 | assert!(result.is_ok()); | ||
| 62 | let new_state = result.unwrap(); | ||
| 63 | assert_eq!(new_state.phase, GamePhase::Finished); | ||
| 64 | assert!(new_state.data.contains_key("reels")); | ||
| 65 | assert!(new_state.data.contains_key("win_type")); | ||
| 66 | } | ||
| 67 | |||
| 68 | /// calculate_result on a finished state returns valid GameResult. | ||
| 69 | #[test] | ||
| 70 | fn calculate_result_on_finished_state() { | ||
| 71 | let game = make_game(); | ||
| 72 | let mut state = game.prepare_state("alice", 100); | ||
| 73 | state.phase = GamePhase::Finished; | ||
| 74 | state | ||
| 75 | .data | ||
| 76 | .insert("reels".to_string(), "CCD".to_string()); | ||
| 77 | state | ||
| 78 | .data | ||
| 79 | .insert("win_type".to_string(), "two".to_string()); | ||
| 80 | |||
| 81 | let result = game.calculate_result(&state).unwrap(); | ||
| 82 | assert_eq!(result.bet_amount, 100); | ||
| 83 | assert_eq!(result.winnings, 200); // CC = 2x | ||
| 84 | } | ||
| 85 | |||
| 86 | /// calculate_result on a non-finished state returns an error. | ||
| 87 | #[test] | ||
| 88 | fn calculate_result_on_unfinished_state() { | ||
| 89 | let game = make_game(); | ||
| 90 | let state = game.prepare_state("alice", 100); | ||
| 91 | assert!(game.calculate_result(&state).is_err()); | ||
| 92 | } | ||
