diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-29 21:36:26 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-29 21:36:26 +0100 |
| commit | 772727b0ef7d63144bf629d774f8b6c1c996bf78 (patch) | |
| tree | 1680dbbd61fce2aaf383ee81ad04ff0705ffe985 /tests | |
| parent | v2.3.0: add crash with local multiplier and pushed crash event (diff) | |
| download | zagan-bet-772727b0ef7d63144bf629d774f8b6c1c996bf78.tar.gz zagan-bet-772727b0ef7d63144bf629d774f8b6c1c996bf78.zip | |
v2.3.1: settle orphaned reservations on server restart
Diffstat (limited to '')
| -rw-r--r-- | tests/server_core.rs | 130 |
1 files changed, 129 insertions, 1 deletions
diff --git a/tests/server_core.rs b/tests/server_core.rs index 695cfe4..0350fb6 100644 --- a/tests/server_core.rs +++ b/tests/server_core.rs | |||
| @@ -442,7 +442,7 @@ fn blackjack_reserves_the_bet_and_settles_a_hit_hand() { | |||
| 442 | 442 | ||
| 443 | #[test] | 443 | #[test] |
| 444 | fn blackjack_double_reserves_an_extra_bet() { | 444 | fn blackjack_double_reserves_an_extra_bet() { |
| 445 | let (mut core, _path) = setup_blackjack( | 445 | let (mut core, path) = setup_blackjack( |
| 446 | "blackjack-double", | 446 | "blackjack-double", |
| 447 | &["5S", "9D", "5H", "8C", "10D"], | 447 | &["5S", "9D", "5H", "8C", "10D"], |
| 448 | ); | 448 | ); |
| @@ -464,6 +464,21 @@ fn blackjack_double_reserves_an_extra_bet() { | |||
| 464 | assert_eq!(done.balance, 1020); | 464 | assert_eq!(done.balance, 1020); |
| 465 | assert_eq!(core.balance("alice"), Some(1020)); | 465 | assert_eq!(core.balance("alice"), Some(1020)); |
| 466 | assert_eq!(core.play_count("alice"), 1); | 466 | assert_eq!(core.play_count("alice"), 1); |
| 467 | |||
| 468 | // A double extends the reservation, and settling clears it. | ||
| 469 | let conn = Connection::open(&path).unwrap(); | ||
| 470 | let pending: i64 = conn | ||
| 471 | .query_row("SELECT COUNT(*) FROM pending_plays", [], |row| row.get(0)) | ||
| 472 | .unwrap(); | ||
| 473 | assert_eq!(pending, 0); | ||
| 474 | let bet: i64 = conn | ||
| 475 | .query_row( | ||
| 476 | "SELECT bet_amount FROM plays WHERE username = 'alice'", | ||
| 477 | [], | ||
| 478 | |row| row.get(0), | ||
| 479 | ) | ||
| 480 | .unwrap(); | ||
| 481 | assert_eq!(bet, 20); | ||
| 467 | } | 482 | } |
| 468 | 483 | ||
| 469 | #[test] | 484 | #[test] |
| @@ -741,3 +756,116 @@ fn crash_rounds_are_owned_by_the_player_who_started_them() { | |||
| 741 | assert_eq!(core.balance("bob"), Some(1000)); | 756 | assert_eq!(core.balance("bob"), Some(1000)); |
| 742 | assert_eq!(core.session_count(), 1, "alice's round must survive"); | 757 | assert_eq!(core.session_count(), 1, "alice's round must survive"); |
| 743 | } | 758 | } |
| 759 | |||
| 760 | #[test] | ||
| 761 | fn a_crash_reservation_is_durable_until_the_round_settles() { | ||
| 762 | let (mut core, path, clock) = setup_crash("pending-crash", CRASH_AT_3); | ||
| 763 | let mut state = ConnectionState::default(); | ||
| 764 | register(&mut core, &mut state, "alice"); | ||
| 765 | login(&mut core, &mut state, "alice"); | ||
| 766 | |||
| 767 | let started = crash_state(&core.handle_message(&mut state, "PLAY_START crash 10")); | ||
| 768 | assert_eq!(started.balance, 990); | ||
| 769 | |||
| 770 | let conn = Connection::open(&path).unwrap(); | ||
| 771 | let pending: i64 = conn | ||
| 772 | .query_row("SELECT COUNT(*) FROM pending_plays", [], |row| row.get(0)) | ||
| 773 | .unwrap(); | ||
| 774 | assert_eq!(pending, 1, "the reserve must be written with the debit"); | ||
| 775 | drop(conn); | ||
| 776 | |||
| 777 | clock.advance(5.0); | ||
| 778 | let cashed = crash_state(&core.handle_message( | ||
| 779 | &mut state, | ||
| 780 | &format!("CRASH_CASHOUT {}", started.session_id), | ||
| 781 | )); | ||
| 782 | assert_eq!(cashed.phase, "cashed"); | ||
| 783 | |||
| 784 | let conn = Connection::open(&path).unwrap(); | ||
| 785 | let pending: i64 = conn | ||
| 786 | .query_row("SELECT COUNT(*) FROM pending_plays", [], |row| row.get(0)) | ||
| 787 | .unwrap(); | ||
| 788 | assert_eq!(pending, 0, "settlement must clear the reservation"); | ||
| 789 | } | ||
| 790 | |||
| 791 | #[test] | ||
| 792 | fn a_restart_settles_an_orphaned_reservation_as_abandoned() { | ||
| 793 | let (mut core, path) = setup_blackjack("pending-restart", &["10S", "9D", "6H", "8C"]); | ||
| 794 | let mut state = ConnectionState::default(); | ||
| 795 | register(&mut core, &mut state, "alice"); | ||
| 796 | login(&mut core, &mut state, "alice"); | ||
| 797 | |||
| 798 | let current = blackjack_state(&core.handle_message(&mut state, "PLAY_START blackjack 10")); | ||
| 799 | assert_eq!(current.phase, "player"); | ||
| 800 | assert_eq!(current.balance, 990); | ||
| 801 | |||
| 802 | // The process dies with the hand still running. | ||
| 803 | drop(core); | ||
| 804 | |||
| 805 | let conn = Connection::open(&path).unwrap(); | ||
| 806 | let pending: i64 = conn | ||
| 807 | .query_row("SELECT COUNT(*) FROM pending_plays", [], |row| row.get(0)) | ||
| 808 | .unwrap(); | ||
| 809 | assert_eq!(pending, 1, "the reservation must survive the process"); | ||
| 810 | drop(conn); | ||
| 811 | |||
| 812 | // Boot does this once, before accepting connections. | ||
| 813 | let recovered = zagan_bet::server::core::recover_orphaned_reservations(&path).unwrap(); | ||
| 814 | assert_eq!(recovered, 1); | ||
| 815 | |||
| 816 | let core = ServerCore::open(&path).unwrap(); | ||
| 817 | assert_eq!( | ||
| 818 | core.balance("alice"), | ||
| 819 | Some(990), | ||
| 820 | "an abandoned hand is a forfeit, not a refund" | ||
| 821 | ); | ||
| 822 | assert_eq!(core.play_count("alice"), 1); | ||
| 823 | |||
| 824 | let conn = Connection::open(&path).unwrap(); | ||
| 825 | let (game_type, bet, winnings, details): (String, i64, i64, String) = conn | ||
| 826 | .query_row( | ||
| 827 | "SELECT game_type, bet_amount, winnings, details | ||
| 828 | FROM plays WHERE username = 'alice'", | ||
| 829 | [], | ||
| 830 | |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)), | ||
| 831 | ) | ||
| 832 | .unwrap(); | ||
| 833 | assert_eq!(game_type, "blackjack"); | ||
| 834 | assert_eq!(bet, 10); | ||
| 835 | assert_eq!(winnings, 0); | ||
| 836 | let details: serde_json::Value = serde_json::from_str(&details).unwrap(); | ||
| 837 | assert_eq!(details["result"], "abandoned"); | ||
| 838 | assert_eq!(details["reason"], "server_restart"); | ||
| 839 | assert_eq!(details["player"][0], "10S", "the hand payload is preserved"); | ||
| 840 | |||
| 841 | let pending: i64 = conn | ||
| 842 | .query_row("SELECT COUNT(*) FROM pending_plays", [], |row| row.get(0)) | ||
| 843 | .unwrap(); | ||
| 844 | assert_eq!(pending, 0, "the sweep must clear the reservation"); | ||
| 845 | } | ||
| 846 | |||
| 847 | #[test] | ||
| 848 | fn an_insufficient_bet_leaves_no_reservation() { | ||
| 849 | let (mut core, path) = setup_blackjack("pending-broke", &["10S", "9D", "6H", "8C"]); | ||
| 850 | let mut state = ConnectionState::default(); | ||
| 851 | register(&mut core, &mut state, "alice"); | ||
| 852 | login(&mut core, &mut state, "alice"); | ||
| 853 | |||
| 854 | let conn = Connection::open(&path).unwrap(); | ||
| 855 | conn.execute("UPDATE users SET balance = 5 WHERE username = 'alice'", []) | ||
| 856 | .unwrap(); | ||
| 857 | drop(conn); | ||
| 858 | |||
| 859 | assert_eq!( | ||
| 860 | core.handle_message(&mut state, "PLAY_START blackjack 10"), | ||
| 861 | "ERROR Insufficient balance" | ||
| 862 | ); | ||
| 863 | assert_eq!(core.session_count(), 0); | ||
| 864 | assert_eq!(core.balance("alice"), Some(5)); | ||
| 865 | |||
| 866 | let conn = Connection::open(&path).unwrap(); | ||
| 867 | let pending: i64 = conn | ||
| 868 | .query_row("SELECT COUNT(*) FROM pending_plays", [], |row| row.get(0)) | ||
| 869 | .unwrap(); | ||
| 870 | assert_eq!(pending, 0); | ||
| 871 | } | ||
