diff --git a/poker-texas-hold-em/contract/src/models/player.cairo b/poker-texas-hold-em/contract/src/models/player.cairo index 7ac5a86..533fadb 100644 --- a/poker-texas-hold-em/contract/src/models/player.cairo +++ b/poker-texas-hold-em/contract/src/models/player.cairo @@ -53,4 +53,272 @@ impl ContractAddressDefault of Default { /// TESTS ON PLAYER MODEL #[cfg(test)] -mod tests {} +mod tests { + use dojo::event::EventStorageTest; + use dojo_cairo_test::WorldStorageTestTrait; + use dojo::model::{ModelStorage, ModelValueStorage, ModelStorageTest}; + use dojo::world::{WorldStorage, WorldStorageTrait}; + use dojo_cairo_test::{ + spawn_test_world, NamespaceDef, TestResource, ContractDefTrait, ContractDef, + }; + use poker::models::game::{Game, GameTrait, ShowdownType}; + use poker::models::player::{Player, PlayerTrait}; + use poker::tests::setup::setup::{CoreContract, deploy_contracts}; + use poker::models::base::GameErrors; + use poker::traits::game::get_default_game_params; + use starknet::ContractAddress; + use starknet::testing::{set_account_contract_address, set_contract_address}; + + use crate::tests::test_actions::tests::{ + PLAYER_1, PLAYER_2, PLAYER_3, PLAYER_4, mock_poker_game, mock_player, mock_poker_game_flex, + }; + + fn mock_allowable_game(ref world: WorldStorage) { + let player_1 = mock_player( + PLAYER_1(), 'dub_zn', 2000, 0, 1, (true, 1), false, true, (0, 0), + ); + let player_2 = mock_player( + PLAYER_2(), 'Birdmannn', 5000, 0, 1, (true, 2), false, true, (0, 0), + ); + let player_3 = mock_player( + PLAYER_3(), 'chiscookeke11', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + mock_poker_game_flex( + ref world, + true, + false, + 1, + false, + 2, + array![PLAYER_1(), PLAYER_2(), PLAYER_3()], + Option::Some(PLAYER_1()), + array![], + 0, + array![player_1, player_2, player_3], + ); + } + + + #[test] + fn test_exit_with_out_true_succeeds() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game(ref world); + let mut game = world.read_model(1); + let mut player = world.read_model(PLAYER_1()); + player.exit(ref game, true); + assert_eq!( + player.out, + (game.id, game.reshuffled), + "Player out should be set to game id and reshuffled", + ); + assert_eq!(player.current_bet, 0, "Player current bet should be reset to 0"); + assert_eq!(player.is_dealer, false, "Player should not be dealer after exit"); + assert_eq!(player.in_round, false, "Player should not be in round after exit"); + assert_eq!(player.locked, (false, 0), "Player should be unlocked after exit"); + assert_eq!(game.current_player_count, 1, "Game player count should decrease by 1"); + } + + #[test] + fn test_exit_with_out_false_succeeds_and_updates_out() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game(ref world); + let mut game = world.read_model(1); + let mut player = world.read_model(PLAYER_1()); + player.exit(ref game, false); + assert_eq!(player.out, (0, 0), "Player out should be set to game id and reshuffled"); + assert_eq!(player.current_bet, 0, "Player current bet should be reset to 0"); + assert_eq!(player.is_dealer, false, "Player should not be dealer after exit"); + assert_eq!(player.in_round, false, "Player should not be in round after exit"); + assert_eq!(player.locked, (false, 0), "Player should be unlocked after exit"); + assert_eq!(game.current_player_count, 1, "Game player count should decrease by 1"); + } + + #[test] + #[should_panic(expected: 'CANNOT EXIT, PLAYER NOT LOCKED')] + fn test_exit_without_lock_fails() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game(ref world); + let mut game = world.read_model(1); + let mut player: Player = world.read_model(PLAYER_3()); + player.locked = (false, 0); + player.exit(ref game, true); + } + + #[test] + fn test_exit_with_splitted_showdown_returns_locked_chips() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game(ref world); + let mut game: Game = world.read_model(1); + let mut player: Player = world.read_model(PLAYER_1()); + let stake = 1000; + game.params.showdown_type = ShowdownType::Splitted(stake); + player.chips = 2000; + player.locked_chips = stake; + player.exit(ref game, true); + assert_eq!(player.chips, 3000, "Player should get locked chips back"); + assert_eq!(player.locked_chips, 0, "Locked chips should be reset to 0"); + } + + #[test] + #[should_panic(expected: 'GAME PLAYER COUNT SUB')] + fn test_exit_with_zero_player_count_fails() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game(ref world); + let mut game: Game = world.read_model(1); + let mut player: Player = world.read_model(PLAYER_1()); + game.current_player_count = 0; + player.exit(ref game, true); + } + + #[test] + #[should_panic(expected: 'BAD REQUEST')] + fn test_exit_with_invalid_player_fails() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game(ref world); + let mut game: Game = world.read_model(1); + let mut player: Player = world.read_model(PLAYER_2()); + player.locked = (true, 999); + player.exit(ref game, true); + } + + #[test] + fn test_enter_succeeds_new_player() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_allowable_game(ref world); + let mut game: Game = world.read_model(1); + let mut player: Player = mock_player( + PLAYER_4(), 'Nobody', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + let is_full = player.enter(ref game); + assert_eq!(player.locked, (true, game.id), "Player should be locked to game"); + assert_eq!(player.in_round, true, "Player should be in round"); + assert_eq!(game.current_player_count, 3, "Game player count should increase"); + assert_eq!(is_full, false, "Game should not be full"); + assert_eq!(game.players.len(), 4, "Player should be added to game players"); + assert_eq!(*game.players.at(3), PLAYER_4(), "Last player should be PLAYER_4"); + } + + #[test] + fn test_is_full_returns_true_when_game_full() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_allowable_game(ref world); + let mut game: Game = world.read_model(1); + game.current_player_count = 2; + game.params.max_no_of_players = 3; + let mut player: Player = mock_player( + PLAYER_4(), 'Nobody', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + let is_full = player.enter(ref game); + assert_eq!(is_full, true, "Game should be full after player enters"); + assert_eq!(game.current_player_count, 3, "Game should have max players"); + } + + #[test] + #[should_panic(expected: 'PLAYER ALREADY LOCKED')] + fn test_enter_fails_when_player_already_locked() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_allowable_game(ref world); + let mut game: Game = world.read_model(1); + let mut player: Player = world.read_model(PLAYER_1()); + player.locked = (true, game.id); + player.enter(ref game); + } + + #[test] + #[should_panic(expected: 'GAME NOT INITIALIZED')] + fn test_enter_fails_when_game_not_initialized() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game_flex( + ref world, false, false, 0, false, 0, array![], Option::None, array![], 0, array![], + ); + let mut game: Game = world.read_model(1); + let mut player: Player = mock_player( + PLAYER_4(), 'Nobody', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + player.enter(ref game); + } + + #[test] + #[should_panic(expected: 'GAME ALREADY ENDED')] + fn test_enter_fails_when_game_ended() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game_flex( + ref world, + false, + true, + 0, + false, + 0, + array![PLAYER_1()], + Option::None, + array![], + 0, + array![], + ); + let mut game: Game = world.read_model(1); + let mut player: Player = mock_player( + PLAYER_4(), 'Nobody', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + player.enter(ref game); + } + + #[test] + #[should_panic(expected: 'INSUFFICIENT CHIPS')] + fn test_enter_fails_when_insufficient_chips() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_allowable_game(ref world); + let mut game: Game = world.read_model(1); + let stake = 1000; + game.params.showdown_type = ShowdownType::Splitted(stake); + let mut player: Player = mock_player( + PLAYER_4(), 'Nobody', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + player.chips = 0; + player.enter(ref game); + } + + #[test] + fn test_refresh_stake_succeeds_with_splitted_showdown() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_allowable_game(ref world); + let mut game: Game = world.read_model(1); + let stake = 1000; + game.params.showdown_type = ShowdownType::Splitted(stake); + let mut player: Player = mock_player( + PLAYER_4(), 'Nobody', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + player.chips = 2000; + let result = player.refresh_stake(ref game); + assert_eq!(result, true, "Refresh stake should succeed"); + assert_eq!(player.chips, 1000, "Player chips should be reduced by stake"); + assert_eq!(player.locked_chips, 1000, "Locked chips should equal stake"); + } + + #[test] + fn test_refresh_stake_fails_with_insufficient_chips() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_allowable_game(ref world); + let mut game: Game = world.read_model(1); + let stake = 1000; + game.params.showdown_type = ShowdownType::Splitted(stake); + let mut player: Player = mock_player( + PLAYER_4(), 'Nobody', 5000, 0, 1, (false, 1), false, true, (0, 0), + ); + player.chips = 500; + let result = player.refresh_stake(ref game); + assert_eq!(result, false, "Refresh stake should fail"); + } + + #[test] + fn test_is_maxed_returns_true_when_player_maxed() { + let (mut world, _) = deploy_contracts(array![CoreContract::Actions]); + mock_poker_game(ref world); + let mut game: Game = world.read_model(1); + let mut player: Player = world.read_model(PLAYER_1()); + player.chips = 0; + player.current_bet = 0; + player.eligible_pots = 0; + let result = player.is_maxed(@game); + assert_eq!(result, true, "Player should be maxed"); + } +} diff --git a/poker-texas-hold-em/contract/src/tests/test_actions.cairo b/poker-texas-hold-em/contract/src/tests/test_actions.cairo index e2a6a3a..e5ef414 100644 --- a/poker-texas-hold-em/contract/src/tests/test_actions.cairo +++ b/poker-texas-hold-em/contract/src/tests/test_actions.cairo @@ -1,5 +1,5 @@ #[cfg(test)] -mod tests { +pub mod tests { use dojo::event::EventStorageTest; use dojo_cairo_test::WorldStorageTestTrait; use dojo::model::{ModelStorage, ModelValueStorage, ModelStorageTest}; @@ -7,7 +7,8 @@ mod tests { use dojo_cairo_test::{ spawn_test_world, NamespaceDef, TestResource, ContractDefTrait, ContractDef, }; - use poker::models::game::{Game, GameTrait}; + use poker::models::game::{Game, GameTrait, GameParams, ShowdownType}; + use poker::models::card::{Card}; use poker::models::player::{Player, PlayerTrait}; use poker::traits::game::get_default_game_params; use poker::systems::interface::{IActionsDispatcher, IActionsDispatcherTrait}; @@ -15,18 +16,86 @@ mod tests { use starknet::ContractAddress; use starknet::testing::{set_account_contract_address, set_contract_address}; - fn PLAYER_1() -> ContractAddress { + pub fn PLAYER_1() -> ContractAddress { starknet::contract_address_const::<'PLAYER_1'>() } - fn PLAYER_2() -> ContractAddress { + pub fn PLAYER_2() -> ContractAddress { starknet::contract_address_const::<'PLAYER_2'>() } - fn PLAYER_3() -> ContractAddress { + pub fn PLAYER_3() -> ContractAddress { starknet::contract_address_const::<'PLAYER_3'>() } + pub fn PLAYER_4() -> ContractAddress { + starknet::contract_address_const::<'PLAYER_4'>() + } + + // Flexible player mock + pub fn mock_player( + id: ContractAddress, + alias: felt252, + chips: u256, + current_bet: u256, + total_rounds: u64, + locked: (bool, u64), + is_dealer: bool, + in_round: bool, + out: (u64, u64), + ) -> Player { + let mut player: Player = Default::default(); + player.id = id; + player.alias = alias; + player.chips = chips; + player.current_bet = current_bet; + player.total_rounds = total_rounds; + player.locked = locked; + player.is_dealer = is_dealer; + player.in_round = in_round; + player.out = out; + player.locked_chips = 0; + player.eligible_pots = 1; + + player + } + + // Flexible game mock + pub fn mock_poker_game_flex( + ref world: WorldStorage, + in_progress: bool, + has_ended: bool, + current_round: u8, + round_in_progress: bool, + current_player_count: u32, + players: Array, + next_player: Option, + community_cards: Array, + current_bet: u256, + player_states: Array, + ) { + let temp_player_states = player_states.span(); + let mut player_states = array![]; + for player in temp_player_states { + player_states.append(player); + }; + + let mut game: Game = Default::default(); + game.id = 1; + game.in_progress = in_progress; + game.has_ended = has_ended; + game.current_round = current_round; + game.round_in_progress = round_in_progress; + game.current_player_count = current_player_count; + game.players = players; + game.next_player = next_player; + game.pots = array![0]; + game.current_bet = current_bet; + game.params = get_default_game_params(); + world.write_model(@game); + world.write_models(player_states.span()); + } + // [Actions] - check() tests #[test] fn test_check_succeeds_when_player_current_bet_equals_game() { @@ -343,82 +412,29 @@ mod tests { } // [Mocks] - fn mock_poker_game(ref world: WorldStorage) { - let game = Game { - id: 1, - in_progress: true, - has_ended: false, - current_round: 1, - round_in_progress: true, - current_player_count: 2, - players: array![PLAYER_1(), PLAYER_2(), PLAYER_3()], - deck: array![], - next_player: Option::Some(PLAYER_1()), - community_cards: array![], - pots: array![0], - current_bet: 0, - params: get_default_game_params(), - reshuffled: 0, - should_end: false, - deck_root: 0, - dealt_cards_root: 0, - nonce: 0, - community_dealing: false, - showdown: false, - round_count: 0, - highest_staker: Option::None, - previous_offset: 0, - }; - - let player_1 = Player { - id: PLAYER_1(), - alias: 'dub_zn', - chips: 2000, - current_bet: 0, - total_rounds: 1, - locked: (true, 1), - is_dealer: false, - in_round: true, - out: (0, 0), - pub_key: 0x1, - locked_chips: 0, - is_blacklisted: false, - eligible_pots: 1, - }; - - let player_2 = Player { - id: PLAYER_2(), - alias: 'Birdmannn', - chips: 5000, - current_bet: 0, - total_rounds: 1, - locked: (true, 1), - is_dealer: false, - in_round: true, - out: (0, 0), - pub_key: 0x2, - locked_chips: 0, - is_blacklisted: false, - eligible_pots: 1, - }; - - let player_3 = Player { - id: PLAYER_3(), - alias: 'chiscookeke11', - chips: 5000, - current_bet: 0, - total_rounds: 1, - locked: (true, 1), - is_dealer: false, - in_round: true, - out: (0, 0), - pub_key: 0x3, - locked_chips: 0, - is_blacklisted: false, - eligible_pots: 1, - }; - - world.write_model(@game); - world.write_models(array![@player_1, @player_2, @player_3].span()); + // Default mock usage for legacy tests + pub fn mock_poker_game(ref world: WorldStorage) { + let player_1 = mock_player( + PLAYER_1(), 'dub_zn', 2000, 0, 1, (true, 1), false, true, (0, 0), + ); + let player_2 = mock_player( + PLAYER_2(), 'Birdmannn', 5000, 0, 1, (true, 1), false, true, (0, 0), + ); + let player_3 = mock_player( + PLAYER_3(), 'chiscookeke11', 5000, 0, 1, (true, 1), false, true, (0, 0), + ); + mock_poker_game_flex( + ref world, + true, // in_progress + false, // has_ended + 1, // current_round + true, // round_in_progress + 2, // current_player_count + array![PLAYER_1(), PLAYER_2(), PLAYER_3()], + Option::Some(PLAYER_1()), + array![], + 0, + array![player_1, player_2, player_3], + ); } }