async def test_multiworld_interaction_missing_remote_pickups(backend: ConnectionBackend, has_message: bool,
                                                             has_cooldown: bool, has_patches: bool):
    # Setup
    if has_cooldown:
        initial_cooldown = 2.0
    else:
        initial_cooldown = 0.0
    backend.message_cooldown = initial_cooldown

    game_enum = RandovaniaGame.METROID_PRIME_CORRUPTION
    backend.set_expected_game(game_enum)
    patches = [MagicMock()]

    connector = AsyncMock()
    connector.game_enum = game_enum
    backend.connector = connector
    backend._inventory = MagicMock()
    backend._permanent_pickups = MagicMock()

    connector.known_collected_locations.return_value = ([], [])
    connector.find_missing_remote_pickups.return_value = (patches if has_patches else [], has_message)

    # Run
    await backend._multiworld_interaction()

    # Assert
    if has_patches and not (has_cooldown and has_message):
        connector.execute_remote_patches.assert_awaited_once_with(backend.executor, patches)
        if has_message:
            assert backend.message_cooldown == 4.0
        else:
            assert backend.message_cooldown == initial_cooldown
    else:
        connector.execute_remote_patches.assert_not_awaited()
        assert backend.message_cooldown == initial_cooldown
async def test_update_calls_interact_with_game(backend: ConnectionBackend, interact_fails,
                                               expected_game, connected_game):
    # Setup
    backend.connector = None
    backend._world = True
    backend.set_expected_game(expected_game)

    if connected_game is not None:
        connector = AsyncMock()
        connector.game_enum = connected_game
    else:
        connector = None

    backend._interact_with_game = AsyncMock(side_effect=MemoryOperationException("err") if interact_fails else None)
    backend._identify_game = AsyncMock(return_value=connector)

    should_call_interact = connected_game is not None
    if should_call_interact:
        should_call_interact = (expected_game == connected_game) or (expected_game is None)

    # Run
    await backend.update(1)

    # Assert
    backend._identify_game.assert_awaited_once_with()
    if should_call_interact:
        backend._interact_with_game.assert_awaited_once_with(1)
        if interact_fails:
            assert backend._world is None
        else:
            assert backend._world is not None
    else:
        backend._interact_with_game.assert_not_awaited()
async def test_multiworld_interaction(backend: ConnectionBackend, depth: int):
    # Setup
    # depth 0: wrong game
    # depth 1: non-empty known_collected_locations with patch
    # depth 2: empty known_collected_locations and empty find_missing_remote_pickups

    game_enum = RandovaniaGame.METROID_PRIME_CORRUPTION
    patches = [MagicMock()]

    location_collected = AsyncMock()
    backend.set_location_collected_listener(location_collected)
    if depth > 0:
        backend.set_expected_game(game_enum)

    connector = AsyncMock()
    connector.game_enum = game_enum
    backend.connector = connector
    backend._inventory = MagicMock()
    backend._permanent_pickups = MagicMock()

    connector.find_missing_remote_pickups.return_value = ([], False)
    if depth == 1:
        connector.known_collected_locations.return_value = ([PickupIndex(2), PickupIndex(5)], patches)
    else:
        connector.known_collected_locations.return_value = ([], [])

    # Run
    await backend._multiworld_interaction()

    # Assert
    connector.known_collected_locations.assert_has_awaits(
        [call(backend.executor)]
        if depth > 0 else []
    )
    if depth == 1:
        location_collected.assert_has_awaits([
            call(game_enum, PickupIndex(2)),
            call(game_enum, PickupIndex(5)),
        ])
        connector.execute_remote_patches.assert_awaited_once_with(backend.executor, patches)
    else:
        location_collected.assert_not_awaited()
        connector.execute_remote_patches.assert_not_awaited()

    if depth == 2:
        connector.find_missing_remote_pickups.assert_awaited_once_with(
            backend.executor, backend._inventory, backend._permanent_pickups, False
        )
    else:
        connector.find_missing_remote_pickups.assert_not_awaited()