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_interact_with_game(backend: ConnectionBackend, depth: int, failure_at: Optional[int]):
    # Setup
    backend.message_cooldown = 2
    backend.connector = AsyncMock()

    backend.connector.current_game_status.return_value = (
        depth <= 1,  # has pending op
        MagicMock() if depth > 0 else None,  # world
    )

    expectation = contextlib.nullcontext()
    if failure_at == 1:
        backend.connector.get_inventory.side_effect = MemoryOperationException("error at _get_inventory")
        expectation = pytest.raises(MemoryOperationException, match="error at _get_inventory")

    backend._multiworld_interaction = AsyncMock()
    if failure_at == 2:
        backend._multiworld_interaction.side_effect = MemoryOperationException("error at _check_for_collected_index")
        expectation = pytest.raises(MemoryOperationException, match="error at _check_for_collected_index")

    expected_depth = min(depth, failure_at) if failure_at is not None else depth
    if (failure_at or 999) > depth:
        expectation = contextlib.nullcontext()

    # Run
    with expectation:
        await backend._interact_with_game(1)

    # Assert
    assert backend.message_cooldown == (2 if expected_depth < 2 else 1)
    backend.connector.current_game_status.assert_awaited_once_with(backend.executor)

    if expected_depth > 0:
        backend.connector.get_inventory.assert_awaited_once_with(backend.executor)
    else:
        backend.connector.get_inventory.assert_not_awaited()

    if expected_depth > 1:
        backend._multiworld_interaction.assert_awaited_once_with()
    else:
        backend._multiworld_interaction.assert_not_awaited()

    if 0 < depth:
        assert backend._world is not None
    else:
        assert backend._world is None