diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-01 16:17:19 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-01 16:17:19 +0100 |
| commit | f07221b4e1d8aa464a44fe566884ccc0565406e9 (patch) | |
| tree | 48020a68cee1c95521d594dc6803c1f7ec01c801 /tests | |
| parent | add nginx + php-fpm deployment example (diff) | |
| download | zagan-bet-f07221b4e1d8aa464a44fe566884ccc0565406e9.tar.gz zagan-bet-f07221b4e1d8aa464a44fe566884ccc0565406e9.zip | |
v3.2.0: straight and split bets anywhere on the felt; add history command
Diffstat (limited to '')
| -rw-r--r-- | tests/client_core.rs | 90 | ||||
| -rw-r--r-- | tests/crash.rs | 2 | ||||
| -rw-r--r-- | tests/roulette.rs | 97 | ||||
| -rw-r--r-- | tests/server_core.rs | 57 |
4 files changed, 201 insertions, 45 deletions
diff --git a/tests/client_core.rs b/tests/client_core.rs index bb8772e..b0bba6f 100644 --- a/tests/client_core.rs +++ b/tests/client_core.rs | |||
| @@ -608,24 +608,29 @@ fn roulette_cursor_places_bets_and_spins() { | |||
| 608 | assert!(h.board()[1].contains("Balance: 1000 Kr")); | 608 | assert!(h.board()[1].contains("Balance: 1000 Kr")); |
| 609 | assert!(h.board()[1].contains("Chip: 10 Kr")); | 609 | assert!(h.board()[1].contains("Chip: 10 Kr")); |
| 610 | assert!(h.board()[9].contains("Bets: (none)")); | 610 | assert!(h.board()[9].contains("Bets: (none)")); |
| 611 | // The cursor starts on number 1, the bottom-left corner. | 611 | // The cursor starts on number 1, the bottom-left cell. |
| 612 | assert!(h.board()[5].contains("[ 1 ]")); | 612 | assert!(h.board()[5].contains("[ 1 ]")); |
| 613 | assert!(h.board()[10].contains("Bet: 1 · pays 36x")); | ||
| 613 | 614 | ||
| 614 | // Up twice: straight 2, then the split between 2 and 3. | 615 | // One step up is the split between 1 and 2, two steps is number 2. |
| 615 | h.key(Key::Up); | 616 | h.key(Key::Up); |
| 617 | assert!(h.board()[5].contains("[ 1 ]")); | ||
| 618 | assert!(h.board()[4].contains("[ 2 ]")); | ||
| 619 | assert!(h.board()[10].contains("Bet: 1/2 · pays 18x")); | ||
| 620 | |||
| 616 | h.key(Key::Up); | 621 | h.key(Key::Up); |
| 622 | assert!(!h.board()[5].contains("[ 1 ]")); | ||
| 617 | assert!(h.board()[4].contains("[ 2 ]")); | 623 | assert!(h.board()[4].contains("[ 2 ]")); |
| 618 | assert!(h.board()[3].contains("[ 3 ]")); | 624 | assert!(h.board()[10].contains("Bet: 2 · pays 36x")); |
| 619 | 625 | ||
| 620 | h.key(Key::Enter); | 626 | h.key(Key::Enter); |
| 621 | assert!(h.board()[9].contains("2/3 10")); | 627 | assert!(h.board()[9].contains("2 10")); |
| 622 | assert!(h.board()[9].contains("Total: 10 Kr")); | ||
| 623 | 628 | ||
| 624 | // Back down to number 1 and place a straight. | 629 | // Right from 2 is the split with 5. |
| 625 | h.key(Key::Down); | 630 | h.key(Key::Right); |
| 626 | h.key(Key::Down); | 631 | assert!(h.board()[10].contains("Bet: 2/5 · pays 18x")); |
| 627 | h.key(Key::Enter); | 632 | h.key(Key::Enter); |
| 628 | assert!(h.board()[9].contains("1 10")); | 633 | assert!(h.board()[9].contains("2/5 10")); |
| 629 | assert!(h.board()[9].contains("Total: 20 Kr")); | 634 | assert!(h.board()[9].contains("Total: 20 Kr")); |
| 630 | 635 | ||
| 631 | h.key(Key::Char(' ')); | 636 | h.key(Key::Char(' ')); |
| @@ -636,18 +641,17 @@ fn roulette_cursor_places_bets_and_spins() { | |||
| 636 | .expect("roulette request"); | 641 | .expect("roulette request"); |
| 637 | assert_eq!( | 642 | assert_eq!( |
| 638 | request, | 643 | request, |
| 639 | "PLAY_START roulette 20 {\"chips\":[{\"target\":{\"bet\":\"split\",\"a\":2,\"b\":3},\"amount\":10},{\"target\":{\"bet\":\"straight\",\"number\":1},\"amount\":10}]}" | 644 | "PLAY_START roulette 20 {\"chips\":[{\"target\":{\"bet\":\"straight\",\"number\":2},\"amount\":10},{\"target\":{\"bet\":\"split\",\"a\":2,\"b\":5},\"amount\":10}]}" |
| 640 | ); | 645 | ); |
| 641 | 646 | ||
| 642 | let bets = r#"{"chips":[{"target":{"bet":"split","a":2,"b":3},"amount":10},{"target":{"bet":"straight","number":1},"amount":10}]}"#; | 647 | let bets = r#"{"chips":[{"target":{"bet":"straight","number":2},"amount":10},{"target":{"bet":"split","a":2,"b":5},"amount":10}]}"#; |
| 643 | h.reply(&format!( | 648 | h.reply(&format!( |
| 644 | "ROULETTE_RESULT session-1 180 20 1160 3 red {bets}" | 649 | "ROULETTE_RESULT session-1 540 20 1520 2 black {bets}" |
| 645 | )); | 650 | )); |
| 646 | 651 | ||
| 647 | assert_eq!(h.board().len(), 12); | 652 | assert!(h.board()[1].contains("Balance: 1520 Kr")); |
| 648 | assert!(h.board()[1].contains("Balance: 1160 Kr")); | 653 | assert!(h.board()[10].contains("Won 540 Kr at 2 BLACK EVEN")); |
| 649 | assert!(h.board()[10].contains("Won 180 Kr at 3 RED ODD")); | 654 | assert!(h.board()[10].contains("+520")); |
| 650 | assert!(h.board()[10].contains("+160")); | ||
| 651 | } | 655 | } |
| 652 | 656 | ||
| 653 | #[test] | 657 | #[test] |
| @@ -658,19 +662,18 @@ fn roulette_cursor_reaches_the_outside_rows() { | |||
| 658 | h.key(Key::Down); | 662 | h.key(Key::Down); |
| 659 | assert!(h.board()[6].starts_with("[") && h.board()[6].contains("1st 12")); | 663 | assert!(h.board()[6].starts_with("[") && h.board()[6].contains("1st 12")); |
| 660 | h.key(Key::Down); | 664 | h.key(Key::Down); |
| 661 | assert!(h.board()[7].starts_with("[") && h.board()[7].contains("2:1")); | 665 | assert!(h.board()[7].starts_with("[") && h.board()[7].contains("col 1 2:1")); |
| 662 | h.key(Key::Down); | 666 | h.key(Key::Down); |
| 663 | assert!(h.board()[8].starts_with("[") && h.board()[8].contains("1-18")); | 667 | assert!(h.board()[8].starts_with("[") && h.board()[8].contains("1-18")); |
| 664 | 668 | ||
| 665 | h.key(Key::Enter); | 669 | h.key(Key::Enter); |
| 666 | assert!(h.board()[9].contains("1-18 10")); | 670 | assert!(h.board()[9].contains("1-18 10")); |
| 667 | 671 | ||
| 668 | // Back up to the felt: the bottom edge of the first dozen is the 1/4 split. | 672 | // Back up to the felt: the dozens return to number 1. |
| 669 | h.key(Key::Up); | 673 | h.key(Key::Up); |
| 670 | h.key(Key::Up); | 674 | h.key(Key::Up); |
| 671 | h.key(Key::Up); | 675 | h.key(Key::Up); |
| 672 | assert!(h.board()[5].contains("[ 1 ]")); | 676 | assert!(h.board()[5].contains("[ 1 ]")); |
| 673 | assert!(h.board()[5].contains("[ 4 ]")); | ||
| 674 | } | 677 | } |
| 675 | 678 | ||
| 676 | #[test] | 679 | #[test] |
| @@ -1146,3 +1149,52 @@ fn a_round_is_live_only_while_money_is_reserved() { | |||
| 1146 | h.push(&crash_line("CRASH_BUSTED", &busted)); | 1149 | h.push(&crash_line("CRASH_BUSTED", &busted)); |
| 1147 | assert!(!h.core.is_live_game()); | 1150 | assert!(!h.core.is_live_game()); |
| 1148 | } | 1151 | } |
| 1152 | |||
| 1153 | #[test] | ||
| 1154 | fn history_prints_recent_plays() { | ||
| 1155 | let mut h = Harness::new(); | ||
| 1156 | h.line("history"); | ||
| 1157 | assert_eq!(h.sent.last().map(String::as_str), Some("HISTORY")); | ||
| 1158 | |||
| 1159 | let json = r#"[{"game":"roulette","bet":10,"winnings":20,"balance_after":1010,"at":"2026-10-30 01:20:11","details":{"number":"17","color":"black"}}]"#; | ||
| 1160 | h.reply(&format!("HISTORY {json}")); | ||
| 1161 | |||
| 1162 | assert!(h | ||
| 1163 | .screen | ||
| 1164 | .lines | ||
| 1165 | .iter() | ||
| 1166 | .any(|line| line.starts_with("Recent plays (1)"))); | ||
| 1167 | let row = h | ||
| 1168 | .screen | ||
| 1169 | .lines | ||
| 1170 | .iter() | ||
| 1171 | .find(|line| line.contains("roulette")) | ||
| 1172 | .expect("play row"); | ||
| 1173 | assert!(row.contains("2026-10-30 01:20"), "row: {row}"); | ||
| 1174 | assert!(row.contains("1010"), "row: {row}"); | ||
| 1175 | assert!(row.contains("17 black"), "row: {row}"); | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | #[test] | ||
| 1179 | fn history_without_plays_says_so() { | ||
| 1180 | let mut h = Harness::new(); | ||
| 1181 | h.line("history"); | ||
| 1182 | h.reply("HISTORY []"); | ||
| 1183 | assert!(h | ||
| 1184 | .screen | ||
| 1185 | .lines | ||
| 1186 | .iter() | ||
| 1187 | .any(|line| line == "No plays recorded yet.")); | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | #[test] | ||
| 1191 | fn history_rejects_a_bad_count_without_sending() { | ||
| 1192 | let mut h = Harness::new(); | ||
| 1193 | h.line("history 0"); | ||
| 1194 | assert!(!h.sent.iter().any(|line| line == "HISTORY 0")); | ||
| 1195 | assert!(h | ||
| 1196 | .screen | ||
| 1197 | .lines | ||
| 1198 | .iter() | ||
| 1199 | .any(|line| line.contains("Usage: history [1-50]"))); | ||
| 1200 | } | ||
diff --git a/tests/crash.rs b/tests/crash.rs index c985ebe..797874b 100644 --- a/tests/crash.rs +++ b/tests/crash.rs | |||
| @@ -38,7 +38,7 @@ fn the_time_for_a_multiplier_is_the_inverse_of_the_curve() { | |||
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | #[test] | 40 | #[test] |
| 41 | fn cashout_value_is_floored_to_whole_Kr() { | 41 | fn cashout_value_is_floored_to_whole_kr() { |
| 42 | assert_eq!(payout(10, 1.0), 10); | 42 | assert_eq!(payout(10, 1.0), 10); |
| 43 | assert_eq!(payout(10, 1.05), 10, "half a Kr is not paid out"); | 43 | assert_eq!(payout(10, 1.05), 10, "half a Kr is not paid out"); |
| 44 | assert_eq!(payout(10, 1.10), 11); | 44 | assert_eq!(payout(10, 1.10), 11); |
diff --git a/tests/roulette.rs b/tests/roulette.rs index 8f9155c..0c5e195 100644 --- a/tests/roulette.rs +++ b/tests/roulette.rs | |||
| @@ -234,28 +234,51 @@ fn target_at(x: usize, y: usize) -> BetTarget { | |||
| 234 | } | 234 | } |
| 235 | 235 | ||
| 236 | #[test] | 236 | #[test] |
| 237 | fn lattice_points_become_straights_splits_and_corners() { | 237 | fn cell_centres_are_straights() { |
| 238 | assert_eq!( | 238 | assert_eq!(target_at(1, 5), BetTarget::Straight { number: 1 }); |
| 239 | target_at(0, 3), | 239 | assert_eq!(target_at(1, 1), BetTarget::Straight { number: 3 }); |
| 240 | BetTarget::Straight { number: 1 }, | 240 | assert_eq!(target_at(23, 1), BetTarget::Straight { number: 36 }); |
| 241 | "bottom-left corner is a straight" | 241 | assert_eq!(target_at(23, 5), BetTarget::Straight { number: 34 }); |
| 242 | ); | 242 | assert_eq!(target_at(5, 3), BetTarget::Straight { number: 8 }); |
| 243 | assert_eq!(target_at(0, 0), BetTarget::Straight { number: 3 }); | 243 | |
| 244 | assert_eq!(target_at(12, 0), BetTarget::Straight { number: 36 }); | 244 | // Every one of the 36 numbers can be bet straight. |
| 245 | assert_eq!(target_at(12, 3), BetTarget::Straight { number: 34 }); | 245 | let mut reachable = Vec::new(); |
| 246 | 246 | for column in 0..12usize { | |
| 247 | assert_eq!(target_at(0, 1), BetTarget::Split { a: 2, b: 3 }); | 247 | for row in 0..3usize { |
| 248 | assert_eq!(target_at(12, 1), BetTarget::Split { a: 35, b: 36 }); | 248 | let cursor = Cursor::Lattice { |
| 249 | assert_eq!(target_at(1, 0), BetTarget::Split { a: 3, b: 6 }); | 249 | x: 2 * column + 1, |
| 250 | y: 2 * row + 1, | ||
| 251 | }; | ||
| 252 | if let Some(BetTarget::Straight { number }) = cursor.target() { | ||
| 253 | reachable.push(number); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | } | ||
| 257 | reachable.sort_unstable(); | ||
| 258 | assert_eq!(reachable, (1..=36).collect::<Vec<_>>()); | ||
| 259 | } | ||
| 250 | 260 | ||
| 261 | #[test] | ||
| 262 | fn lines_between_cells_are_splits_and_crossings_are_corners() { | ||
| 263 | // Vertical lines sit between two columns. | ||
| 264 | assert_eq!(target_at(2, 5), BetTarget::Split { a: 1, b: 4 }); | ||
| 265 | assert_eq!(target_at(2, 3), BetTarget::Split { a: 2, b: 5 }); | ||
| 266 | assert_eq!(target_at(22, 1), BetTarget::Split { a: 33, b: 36 }); | ||
| 267 | |||
| 268 | // Horizontal lines sit between two rows. | ||
| 269 | assert_eq!(target_at(1, 4), BetTarget::Split { a: 1, b: 2 }); | ||
| 270 | assert_eq!(target_at(3, 2), BetTarget::Split { a: 5, b: 6 }); | ||
| 271 | assert_eq!(target_at(23, 4), BetTarget::Split { a: 34, b: 35 }); | ||
| 272 | |||
| 273 | // Crossings cover four cells. | ||
| 251 | assert_eq!( | 274 | assert_eq!( |
| 252 | target_at(1, 1), | 275 | target_at(2, 4), |
| 253 | BetTarget::Corner { | 276 | BetTarget::Corner { |
| 254 | numbers: [2, 3, 5, 6] | 277 | numbers: [1, 2, 4, 5] |
| 255 | } | 278 | } |
| 256 | ); | 279 | ); |
| 257 | assert_eq!( | 280 | assert_eq!( |
| 258 | target_at(6, 2), | 281 | target_at(12, 4), |
| 259 | BetTarget::Corner { | 282 | BetTarget::Corner { |
| 260 | numbers: [16, 17, 19, 20] | 283 | numbers: [16, 17, 19, 20] |
| 261 | } | 284 | } |
| @@ -264,31 +287,54 @@ fn lattice_points_become_straights_splits_and_corners() { | |||
| 264 | 287 | ||
| 265 | #[test] | 288 | #[test] |
| 266 | fn the_cursor_walks_the_felt_and_the_outside_rows() { | 289 | fn the_cursor_walks_the_felt_and_the_outside_rows() { |
| 267 | let bottom = Cursor::Lattice { x: 0, y: 3 }; | 290 | let bottom = Cursor::Lattice { x: 1, y: 5 }; |
| 291 | // One step right is the split between 1 and 4, two steps is number 4. | ||
| 292 | assert_eq!( | ||
| 293 | bottom.moved(Direction::Right), | ||
| 294 | Cursor::Lattice { x: 2, y: 5 } | ||
| 295 | ); | ||
| 296 | assert_eq!( | ||
| 297 | bottom.moved(Direction::Right).moved(Direction::Right), | ||
| 298 | Cursor::Lattice { x: 3, y: 5 } | ||
| 299 | ); | ||
| 300 | // One step up is the split between 1 and 2. | ||
| 301 | assert_eq!(bottom.moved(Direction::Up), Cursor::Lattice { x: 1, y: 4 }); | ||
| 302 | |||
| 268 | assert_eq!(bottom.moved(Direction::Down), Cursor::Dozen(0)); | 303 | assert_eq!(bottom.moved(Direction::Down), Cursor::Dozen(0)); |
| 269 | assert_eq!( | 304 | assert_eq!( |
| 270 | Cursor::Lattice { x: 12, y: 3 }.moved(Direction::Down), | 305 | Cursor::Lattice { x: 23, y: 5 }.moved(Direction::Down), |
| 271 | Cursor::Dozen(2) | 306 | Cursor::Dozen(2) |
| 272 | ); | 307 | ); |
| 273 | assert_eq!( | 308 | assert_eq!( |
| 274 | Cursor::Lattice { x: 0, y: 0 }.moved(Direction::Up), | 309 | Cursor::Lattice { x: 1, y: 1 }.moved(Direction::Up), |
| 275 | Cursor::Zero | 310 | Cursor::Zero |
| 276 | ); | 311 | ); |
| 277 | assert_eq!(Cursor::Zero.moved(Direction::Down), Cursor::Lattice { x: 0, y: 0 }); | 312 | assert_eq!( |
| 313 | Cursor::Zero.moved(Direction::Down), | ||
| 314 | Cursor::Lattice { x: 1, y: 1 } | ||
| 315 | ); | ||
| 278 | 316 | ||
| 279 | assert_eq!(Cursor::Dozen(0).moved(Direction::Up), Cursor::Lattice { x: 1, y: 3 }); | 317 | assert_eq!( |
| 318 | Cursor::Dozen(0).moved(Direction::Up), | ||
| 319 | Cursor::Lattice { x: 1, y: 5 } | ||
| 320 | ); | ||
| 280 | assert_eq!(Cursor::Dozen(0).moved(Direction::Down), Cursor::Column(0)); | 321 | assert_eq!(Cursor::Dozen(0).moved(Direction::Down), Cursor::Column(0)); |
| 322 | assert_eq!(Cursor::Dozen(2).moved(Direction::Up), Cursor::Lattice { x: 17, y: 5 }); | ||
| 281 | assert_eq!(Cursor::Column(2).moved(Direction::Up), Cursor::Dozen(2)); | 323 | assert_eq!(Cursor::Column(2).moved(Direction::Up), Cursor::Dozen(2)); |
| 282 | assert_eq!(Cursor::Column(1).moved(Direction::Down), Cursor::Outside(2)); | 324 | assert_eq!(Cursor::Column(1).moved(Direction::Down), Cursor::Outside(2)); |
| 283 | assert_eq!(Cursor::Outside(3).moved(Direction::Up), Cursor::Column(1)); | 325 | assert_eq!(Cursor::Outside(3).moved(Direction::Up), Cursor::Column(1)); |
| 284 | 326 | ||
| 285 | // Edges stay put instead of wrapping. | 327 | // Edges stay put instead of wrapping. |
| 286 | assert_eq!(Cursor::Dozen(0).moved(Direction::Left), Cursor::Dozen(0)); | ||
| 287 | assert_eq!(Cursor::Outside(5).moved(Direction::Right), Cursor::Outside(5)); | ||
| 288 | assert_eq!( | 328 | assert_eq!( |
| 289 | Cursor::Lattice { x: 0, y: 2 }.moved(Direction::Left), | 329 | Cursor::Lattice { x: 1, y: 3 }.moved(Direction::Left), |
| 290 | Cursor::Lattice { x: 0, y: 2 } | 330 | Cursor::Lattice { x: 1, y: 3 } |
| 331 | ); | ||
| 332 | assert_eq!( | ||
| 333 | Cursor::Lattice { x: 23, y: 3 }.moved(Direction::Right), | ||
| 334 | Cursor::Lattice { x: 23, y: 3 } | ||
| 291 | ); | 335 | ); |
| 336 | assert_eq!(Cursor::Dozen(0).moved(Direction::Left), Cursor::Dozen(0)); | ||
| 337 | assert_eq!(Cursor::Outside(5).moved(Direction::Right), Cursor::Outside(5)); | ||
| 292 | } | 338 | } |
| 293 | 339 | ||
| 294 | #[test] | 340 | #[test] |
| @@ -305,6 +351,7 @@ fn every_cursor_round_trips_through_its_encoded_form() { | |||
| 305 | } | 351 | } |
| 306 | assert_eq!(Cursor::parse("nonsense"), Cursor::default()); | 352 | assert_eq!(Cursor::parse("nonsense"), Cursor::default()); |
| 307 | assert_eq!(Cursor::parse("lattice:99:0"), Cursor::default()); | 353 | assert_eq!(Cursor::parse("lattice:99:0"), Cursor::default()); |
| 354 | assert_eq!(Cursor::parse("lattice:0:1"), Cursor::default()); | ||
| 308 | assert_eq!(Cursor::parse("dozen:7"), Cursor::default()); | 355 | assert_eq!(Cursor::parse("dozen:7"), Cursor::default()); |
| 309 | } | 356 | } |
| 310 | 357 | ||
diff --git a/tests/server_core.rs b/tests/server_core.rs index 47e80e7..6d6cffd 100644 --- a/tests/server_core.rs +++ b/tests/server_core.rs | |||
| @@ -871,3 +871,60 @@ fn an_insufficient_bet_leaves_no_reservation() { | |||
| 871 | .unwrap(); | 871 | .unwrap(); |
| 872 | assert_eq!(pending, 0); | 872 | assert_eq!(pending, 0); |
| 873 | } | 873 | } |
| 874 | |||
| 875 | #[test] | ||
| 876 | fn history_requires_login_and_valid_counts() { | ||
| 877 | let (mut core, _path) = setup("history-guards", WIN3); | ||
| 878 | let mut state = ConnectionState::default(); | ||
| 879 | |||
| 880 | assert_eq!( | ||
| 881 | core.handle_message(&mut state, "HISTORY"), | ||
| 882 | "ERROR Not logged in" | ||
| 883 | ); | ||
| 884 | |||
| 885 | register(&mut core, &mut state, "alice"); | ||
| 886 | login(&mut core, &mut state, "alice"); | ||
| 887 | |||
| 888 | assert_eq!( | ||
| 889 | core.handle_message(&mut state, "HISTORY nope"), | ||
| 890 | "ERROR Invalid HISTORY count" | ||
| 891 | ); | ||
| 892 | assert_eq!( | ||
| 893 | core.handle_message(&mut state, "HISTORY 0"), | ||
| 894 | "ERROR Invalid HISTORY count" | ||
| 895 | ); | ||
| 896 | assert_eq!( | ||
| 897 | core.handle_message(&mut state, "HISTORY 1 2"), | ||
| 898 | "ERROR Invalid HISTORY format. Usage: HISTORY [count]" | ||
| 899 | ); | ||
| 900 | } | ||
| 901 | |||
| 902 | #[test] | ||
| 903 | fn history_lists_recent_plays_newest_first() { | ||
| 904 | let (mut core, _path) = setup("history-list", WIN3); | ||
| 905 | let mut state = ConnectionState::default(); | ||
| 906 | register(&mut core, &mut state, "alice"); | ||
| 907 | login(&mut core, &mut state, "alice"); | ||
| 908 | |||
| 909 | for _ in 0..3 { | ||
| 910 | assert!(core | ||
| 911 | .handle_message(&mut state, "PLAY_START slots 10") | ||
| 912 | .starts_with("GAME_RESULT")); | ||
| 913 | } | ||
| 914 | |||
| 915 | let response = core.handle_message(&mut state, "HISTORY 2"); | ||
| 916 | let json = response.strip_prefix("HISTORY ").expect("history response"); | ||
| 917 | let records: Vec<zagan_bet::history::PlayRecord> = serde_json::from_str(json).unwrap(); | ||
| 918 | |||
| 919 | assert_eq!(records.len(), 2, "the limit must be honoured"); | ||
| 920 | for record in &records { | ||
| 921 | assert_eq!(record.game, "slots"); | ||
| 922 | assert_eq!(record.bet, 10); | ||
| 923 | assert_eq!(record.winnings, 10, "three C pay the stake back"); | ||
| 924 | assert_eq!(record.summary(), "win"); | ||
| 925 | assert_eq!(record.details["win_type"], "win"); | ||
| 926 | } | ||
| 927 | |||
| 928 | // Newest first: the timestamps must not increase across the list. | ||
| 929 | assert!(records[0].at >= records[1].at); | ||
| 930 | } | ||
