diff options
Diffstat (limited to 'tests/client_core.rs')
| -rw-r--r-- | tests/client_core.rs | 151 |
1 files changed, 107 insertions, 44 deletions
diff --git a/tests/client_core.rs b/tests/client_core.rs index 2f43d47..3a2fb27 100644 --- a/tests/client_core.rs +++ b/tests/client_core.rs | |||
| @@ -7,17 +7,17 @@ | |||
| 7 | use std::time::Duration; | 7 | use std::time::Duration; |
| 8 | use zagan_bet::blackjack::BlackjackState; | 8 | use zagan_bet::blackjack::BlackjackState; |
| 9 | use zagan_bet::client::core::{ClientCore, Effect}; | 9 | use zagan_bet::client::core::{ClientCore, Effect}; |
| 10 | use zagan_bet::client::input::{game_input, Editor, GameInput, Input, Key}; | ||
| 11 | use zagan_bet::client::shell::{self, Shell}; | ||
| 12 | use zagan_bet::client::ui::help_lines; | ||
| 13 | use zagan_bet::client::games::response::{ | 10 | use zagan_bet::client::games::response::{ |
| 14 | parse_blackjack_response, parse_crash_response, parse_spin_response, | 11 | parse_blackjack_response, parse_crash_response, parse_spin_response, |
| 15 | }; | 12 | }; |
| 16 | use zagan_bet::client::games::roulette::{Cursor, Direction}; | 13 | use zagan_bet::client::games::roulette::{Cursor, Direction}; |
| 14 | use zagan_bet::client::input::{Editor, GameInput, Input, Key, game_input}; | ||
| 15 | use zagan_bet::client::shell::{self, Shell}; | ||
| 17 | use zagan_bet::client::ui::command_names; | 16 | use zagan_bet::client::ui::command_names; |
| 17 | use zagan_bet::client::ui::help_lines; | ||
| 18 | use zagan_bet::client::view::{Command, RoulettePhase, SlotsPhase, View}; | 18 | use zagan_bet::client::view::{Command, RoulettePhase, SlotsPhase, View}; |
| 19 | use zagan_bet::roulette::BetTarget; | ||
| 20 | use zagan_bet::crash::{CrashState, multiplier_at}; | 19 | use zagan_bet::crash::{CrashState, multiplier_at}; |
| 20 | use zagan_bet::roulette::BetTarget; | ||
| 21 | 21 | ||
| 22 | const WIN3: &str = "CCCCLLCLODGSDGS"; | 22 | const WIN3: &str = "CCCCLLCLODGSDGS"; |
| 23 | const LOSS: &str = "CLOOGDSLCDCOGLS"; | 23 | const LOSS: &str = "CLOOGDSLCDCOGLS"; |
| @@ -54,7 +54,7 @@ impl Screen { | |||
| 54 | self.region_start = None; | 54 | self.region_start = None; |
| 55 | self.region_len = 0; | 55 | self.region_len = 0; |
| 56 | } | 56 | } |
| 57 | Effect::SendLine(line) => sent.push(line.clone()), | 57 | Effect::SendRequest { payload, .. } => sent.push(payload.clone()), |
| 58 | Effect::Quit | Effect::Disconnect => {} | 58 | Effect::Quit | Effect::Disconnect => {} |
| 59 | } | 59 | } |
| 60 | } | 60 | } |
| @@ -601,7 +601,7 @@ fn ping_prints_pong() { | |||
| 601 | h.line("ping"); | 601 | h.line("ping"); |
| 602 | assert_eq!(h.sent.last().map(String::as_str), Some("PING")); | 602 | assert_eq!(h.sent.last().map(String::as_str), Some("PING")); |
| 603 | 603 | ||
| 604 | h.reply("PONG"); | 604 | h.reply("PONG 1700000000000"); |
| 605 | 605 | ||
| 606 | assert!(h.screen.lines.iter().any(|line| line == "Pong")); | 606 | assert!(h.screen.lines.iter().any(|line| line == "Pong")); |
| 607 | } | 607 | } |
| @@ -968,6 +968,13 @@ fn crash_state( | |||
| 968 | crash_point, | 968 | crash_point, |
| 969 | payout, | 969 | payout, |
| 970 | result: phase.to_string(), | 970 | result: phase.to_string(), |
| 971 | started_at: 1_700_000_000_000, | ||
| 972 | commit: Some("00".repeat(32)), | ||
| 973 | seed: if phase == "running" { | ||
| 974 | None | ||
| 975 | } else { | ||
| 976 | Some("00".repeat(32)) | ||
| 977 | }, | ||
| 971 | } | 978 | } |
| 972 | } | 979 | } |
| 973 | 980 | ||
| @@ -1221,11 +1228,12 @@ fn history_prints_recent_plays() { | |||
| 1221 | let json = r#"[{"game":"roulette","bet":10,"winnings":20,"balance_after":1010,"at":"2026-10-30 01:20:11","details":{"number":"17","color":"black"}}]"#; | 1228 | let json = r#"[{"game":"roulette","bet":10,"winnings":20,"balance_after":1010,"at":"2026-10-30 01:20:11","details":{"number":"17","color":"black"}}]"#; |
| 1222 | h.reply(&format!("HISTORY {json}")); | 1229 | h.reply(&format!("HISTORY {json}")); |
| 1223 | 1230 | ||
| 1224 | assert!(h | 1231 | assert!( |
| 1225 | .screen | 1232 | h.screen |
| 1226 | .lines | 1233 | .lines |
| 1227 | .iter() | 1234 | .iter() |
| 1228 | .any(|line| line.starts_with("Recent plays (1)"))); | 1235 | .any(|line| line.starts_with("Recent plays (1)")) |
| 1236 | ); | ||
| 1229 | let row = h | 1237 | let row = h |
| 1230 | .screen | 1238 | .screen |
| 1231 | .lines | 1239 | .lines |
| @@ -1242,11 +1250,12 @@ fn history_without_plays_says_so() { | |||
| 1242 | let mut h = Harness::new(); | 1250 | let mut h = Harness::new(); |
| 1243 | h.line("history"); | 1251 | h.line("history"); |
| 1244 | h.reply("HISTORY []"); | 1252 | h.reply("HISTORY []"); |
| 1245 | assert!(h | 1253 | assert!( |
| 1246 | .screen | 1254 | h.screen |
| 1247 | .lines | 1255 | .lines |
| 1248 | .iter() | 1256 | .iter() |
| 1249 | .any(|line| line == "No plays recorded yet.")); | 1257 | .any(|line| line == "No plays recorded yet.") |
| 1258 | ); | ||
| 1250 | } | 1259 | } |
| 1251 | 1260 | ||
| 1252 | #[test] | 1261 | #[test] |
| @@ -1254,11 +1263,12 @@ fn history_rejects_a_bad_count_without_sending() { | |||
| 1254 | let mut h = Harness::new(); | 1263 | let mut h = Harness::new(); |
| 1255 | h.line("history 0"); | 1264 | h.line("history 0"); |
| 1256 | assert!(!h.sent.iter().any(|line| line == "HISTORY 0")); | 1265 | assert!(!h.sent.iter().any(|line| line == "HISTORY 0")); |
| 1257 | assert!(h | 1266 | assert!( |
| 1258 | .screen | 1267 | h.screen |
| 1259 | .lines | 1268 | .lines |
| 1260 | .iter() | 1269 | .iter() |
| 1261 | .any(|line| line.contains("Usage: history [1-50]"))); | 1270 | .any(|line| line.contains("Usage: history [1-50]")) |
| 1271 | ); | ||
| 1262 | } | 1272 | } |
| 1263 | 1273 | ||
| 1264 | #[test] | 1274 | #[test] |
| @@ -1291,10 +1301,7 @@ fn the_roulette_view_reflects_the_cursor_and_bets() { | |||
| 1291 | Some(BetTarget::Straight { number: 2 }) | 1301 | Some(BetTarget::Straight { number: 2 }) |
| 1292 | ); | 1302 | ); |
| 1293 | assert_eq!(view.bets.total(), 10); | 1303 | assert_eq!(view.bets.total(), 10); |
| 1294 | assert_eq!( | 1304 | assert_eq!(view.bets.amount_on(&BetTarget::Straight { number: 2 }), 10); |
| 1295 | view.bets.amount_on(&BetTarget::Straight { number: 2 }), | ||
| 1296 | 10 | ||
| 1297 | ); | ||
| 1298 | } | 1305 | } |
| 1299 | 1306 | ||
| 1300 | #[test] | 1307 | #[test] |
| @@ -1302,7 +1309,9 @@ fn clicking_a_bet_target_does_not_move_the_cursor() { | |||
| 1302 | let mut h = Harness::new(); | 1309 | let mut h = Harness::new(); |
| 1303 | h.enter_roulette(); | 1310 | h.enter_roulette(); |
| 1304 | 1311 | ||
| 1305 | let effects = h.core.command(Command::PlaceBetAt(BetTarget::Split { a: 1, b: 4 })); | 1312 | let effects = h |
| 1313 | .core | ||
| 1314 | .command(Command::PlaceBetAt(BetTarget::Split { a: 1, b: 4 })); | ||
| 1306 | h.apply(effects); | 1315 | h.apply(effects); |
| 1307 | 1316 | ||
| 1308 | let Some(View::Roulette(view)) = h.core.view() else { | 1317 | let Some(View::Roulette(view)) = h.core.view() else { |
| @@ -1387,9 +1396,7 @@ fn the_slots_view_carries_the_result() { | |||
| 1387 | let mut h = Harness::new(); | 1396 | let mut h = Harness::new(); |
| 1388 | h.enter_slots("10"); | 1397 | h.enter_slots("10"); |
| 1389 | h.key(Key::Char(' ')); | 1398 | h.key(Key::Char(' ')); |
| 1390 | h.reply(&format!( | 1399 | h.reply(&format!("GAME_RESULT s1 20 10 1010 {WIN3} win 1 0,3,6")); |
| 1391 | "GAME_RESULT s1 20 10 1010 {WIN3} win 1 0,3,6" | ||
| 1392 | )); | ||
| 1393 | 1400 | ||
| 1394 | let Some(View::Slots(view)) = h.core.view() else { | 1401 | let Some(View::Slots(view)) = h.core.view() else { |
| 1395 | panic!("expected a slots view"); | 1402 | panic!("expected a slots view"); |
| @@ -1518,11 +1525,12 @@ fn history_is_a_screen_and_keeps_its_records() { | |||
| 1518 | assert_eq!(view.records[0].summary(), "cashed 1.13x"); | 1525 | assert_eq!(view.records[0].summary(), "cashed 1.13x"); |
| 1519 | 1526 | ||
| 1520 | // The terminal adapter still prints the table. | 1527 | // The terminal adapter still prints the table. |
| 1521 | assert!(h | 1528 | assert!( |
| 1522 | .screen | 1529 | h.screen |
| 1523 | .lines | 1530 | .lines |
| 1524 | .iter() | 1531 | .iter() |
| 1525 | .any(|line| line.starts_with("Recent plays (1)"))); | 1532 | .any(|line| line.starts_with("Recent plays (1)")) |
| 1533 | ); | ||
| 1526 | } | 1534 | } |
| 1527 | 1535 | ||
| 1528 | #[test] | 1536 | #[test] |
| @@ -1563,11 +1571,12 @@ fn a_login_command_sends_auth_and_sets_the_user() { | |||
| 1563 | }; | 1571 | }; |
| 1564 | assert_eq!(lobby.user.as_deref(), Some("alice")); | 1572 | assert_eq!(lobby.user.as_deref(), Some("alice")); |
| 1565 | assert!(lobby.login_error.is_none()); | 1573 | assert!(lobby.login_error.is_none()); |
| 1566 | assert!(h | 1574 | assert!( |
| 1567 | .screen | 1575 | h.screen |
| 1568 | .lines | 1576 | .lines |
| 1569 | .iter() | 1577 | .iter() |
| 1570 | .any(|line| line == "Logged in as alice")); | 1578 | .any(|line| line == "Logged in as alice") |
| 1579 | ); | ||
| 1571 | } | 1580 | } |
| 1572 | 1581 | ||
| 1573 | #[test] | 1582 | #[test] |
| @@ -1600,11 +1609,12 @@ fn a_register_command_sends_register_and_reports_the_result() { | |||
| 1600 | assert_eq!(h.sent.last().map(String::as_str), Some("REGISTER bob hash")); | 1609 | assert_eq!(h.sent.last().map(String::as_str), Some("REGISTER bob hash")); |
| 1601 | 1610 | ||
| 1602 | h.reply("REGISTERED bob"); | 1611 | h.reply("REGISTERED bob"); |
| 1603 | assert!(h | 1612 | assert!( |
| 1604 | .screen | 1613 | h.screen |
| 1605 | .lines | 1614 | .lines |
| 1606 | .iter() | 1615 | .iter() |
| 1607 | .any(|line| line == "Account created. Log in to play.")); | 1616 | .any(|line| line == "Account created. Log in to play.") |
| 1617 | ); | ||
| 1608 | } | 1618 | } |
| 1609 | 1619 | ||
| 1610 | #[test] | 1620 | #[test] |
| @@ -1646,3 +1656,56 @@ fn the_crash_multiplier_is_not_rounded_for_the_chart() { | |||
| 1646 | "a two-decimal store would flatten the curve into steps" | 1656 | "a two-decimal store would flatten the curve into steps" |
| 1647 | ); | 1657 | ); |
| 1648 | } | 1658 | } |
| 1659 | |||
| 1660 | #[test] | ||
| 1661 | fn a_reply_is_routed_by_request_id_and_late_replies_are_ignored() { | ||
| 1662 | let mut h = Harness::new(); | ||
| 1663 | let first = h.core.command(Command::Ping); | ||
| 1664 | let first_id = first | ||
| 1665 | .iter() | ||
| 1666 | .find_map(|effect| match effect { | ||
| 1667 | Effect::SendRequest { id, .. } => Some(*id), | ||
| 1668 | _ => None, | ||
| 1669 | }) | ||
| 1670 | .unwrap(); | ||
| 1671 | |||
| 1672 | let second = h.core.command(Command::Info); | ||
| 1673 | let second_id = second | ||
| 1674 | .iter() | ||
| 1675 | .find_map(|effect| match effect { | ||
| 1676 | Effect::SendRequest { id, .. } => Some(*id), | ||
| 1677 | _ => None, | ||
| 1678 | }) | ||
| 1679 | .unwrap(); | ||
| 1680 | assert_ne!(first_id, second_id, "ids must be unique on the connection"); | ||
| 1681 | |||
| 1682 | // The ping reply arrives after INFO was sent. It must not complete INFO. | ||
| 1683 | assert!( | ||
| 1684 | h.core | ||
| 1685 | .handle_reply_id(first_id, "PONG 1700000000000") | ||
| 1686 | .is_empty() | ||
| 1687 | ); | ||
| 1688 | let effects = h.core.handle_reply_id(second_id, "INFO alice 1 1000"); | ||
| 1689 | assert!( | ||
| 1690 | effects.iter().any(|effect| matches!(effect, Effect::Print(lines) if lines.iter().any(|line| line.contains("Balance: 1000")))), | ||
| 1691 | "the matching reply is applied" | ||
| 1692 | ); | ||
| 1693 | } | ||
| 1694 | |||
| 1695 | #[test] | ||
| 1696 | fn a_crash_tick_updates_the_board_for_its_own_session() { | ||
| 1697 | let mut h = Harness::new(); | ||
| 1698 | h.enter_crash(); | ||
| 1699 | h.key(Key::Char(' ')); | ||
| 1700 | let running = crash_state("running", 1.0, None, 0, 990); | ||
| 1701 | h.reply(&crash_line("CRASH_START", &running)); | ||
| 1702 | assert!(h.board()[2].contains("Multiplier: 1.00x")); | ||
| 1703 | |||
| 1704 | h.push("crash.tick session=cr1 t=1700000000100 m=1.5"); | ||
| 1705 | assert!(h.board()[2].contains("Multiplier: 1.50x")); | ||
| 1706 | assert!(h.board()[3].contains("Potential: 15 Kr")); | ||
| 1707 | |||
| 1708 | // A tick for another round cannot move this one. | ||
| 1709 | h.push("crash.tick session=other t=1700000000200 m=9.0"); | ||
| 1710 | assert!(h.board()[2].contains("Multiplier: 1.50x")); | ||
| 1711 | } | ||
