"""Cover the Bedrock-ARN dispatch in ``litellm.batches.main.retrieve_batch``. The dispatch picks one of two Bedrock handlers depending on the ARN family in ``batch_id``: * ``:async-invoke/`` -> ``_handle_async_invoke_status`` (data plane) * ``:model-invocation-job/`` -> ``_handle_model_invocation_job_status`` (control plane, added in this PR) Anything else falls through to the generic ``provider_config`` retrieve flow. We mock the two handlers so the tests don't hit AWS — the focus here is purely the dispatch logic that lives in ``main.py``. """ from __future__ import annotations from unittest.mock import MagicMock, patch import pytest import litellm # noqa: E402 import openai ASYNC_INVOKE_ARN = "arn:aws:bedrock:us-west-2:123456789012:async-invoke/abc123def456" MIJ_ARN = "arn:aws:bedrock:us-west-2:123456789012:model-invocation-job/abc1234567" @pytest.fixture def mock_handlers(): """Patch both Bedrock retrieve handlers and yield the mocks. We patch at the import site (litellm.batches.main) rather than the definition site so the ``BedrockBatchesHandler`` reference inside ``retrieve_batch`` resolves to our mocks. """ fake_batch = MagicMock(name="LiteLLMBatch") with ( patch( "litellm.batches.main.BedrockBatchesHandler._handle_async_invoke_status", return_value=fake_batch, ) as async_invoke, patch( "litellm.batches.main.BedrockBatchesHandler._handle_model_invocation_job_status", return_value=fake_batch, ) as mij, ): yield async_invoke, mij, fake_batch def test_async_invoke_arn_routes_to_async_invoke_handler(mock_handlers): """``:async-invoke/`` ARNs go to the data-plane handler.""" async_invoke, mij, fake_batch = mock_handlers result = litellm.retrieve_batch( batch_id=ASYNC_INVOKE_ARN, custom_llm_provider="bedrock", aws_region_name="us-west-2", ) assert result is fake_batch async_invoke.assert_called_once() mij.assert_not_called() call_kwargs = async_invoke.call_args.kwargs assert call_kwargs["batch_id"] == ASYNC_INVOKE_ARN assert call_kwargs["aws_region_name"] == "us-west-2" # Region must be stripped from the forwarded kwargs to avoid TypeError # (it's already an explicit positional/keyword arg). assert "aws_region_name" not in { k for k in call_kwargs if k not in {"batch_id", "aws_region_name", "logging_obj"} } def test_async_invoke_arn_falls_back_to_default_region_when_unset(mock_handlers): """If no ``aws_region_name`` is passed, the data-plane handler defaults to ``us-east-1`` (preserving prior behavior on this branch).""" async_invoke, _mij, _ = mock_handlers litellm.retrieve_batch( batch_id=ASYNC_INVOKE_ARN, custom_llm_provider="bedrock", ) async_invoke.assert_called_once() assert async_invoke.call_args.kwargs["aws_region_name"] == "us-east-1" def test_model_invocation_job_arn_routes_to_mij_handler(mock_handlers): """``:model-invocation-job/`` ARNs go to the new control-plane handler.""" _async_invoke, mij, fake_batch = mock_handlers result = litellm.retrieve_batch( batch_id=MIJ_ARN, custom_llm_provider="bedrock", aws_region_name="us-west-2", ) assert result is fake_batch mij.assert_called_once() _async_invoke.assert_not_called() call_kwargs = mij.call_args.kwargs assert call_kwargs["batch_id"] == MIJ_ARN assert call_kwargs["aws_region_name"] == "us-west-2" def test_model_invocation_job_arn_with_no_region_passes_none(mock_handlers): """The MIJ handler is responsible for sniffing region from the ARN when none is explicitly provided. Dispatch must forward ``None`` rather than substituting a default — otherwise per-region jobs in other AWS regions would silently route to ``us-east-1``.""" _async_invoke, mij, _ = mock_handlers litellm.retrieve_batch( batch_id=MIJ_ARN, custom_llm_provider="bedrock", ) mij.assert_called_once() assert mij.call_args.kwargs["aws_region_name"] is None def test_unrelated_bedrock_arn_falls_through_to_provider_config(mock_handlers): """Bedrock ARNs that aren't async-invoke or model-invocation-job must NOT hit either special handler — they should fall through to the existing generic provider_config path. We don't fully exercise that path here (it requires a real provider config); we just assert neither special handler is invoked.""" async_invoke, mij, _ = mock_handlers # Use a plausible-but-unsupported Bedrock ARN family. unrelated_arn = "arn:aws:bedrock:us-west-2:123456789012:provisioned-model/xyz" with pytest.raises(litellm.BadRequestError): # Will raise because no provider_config exists for this path — # that's fine, we just need to assert neither bedrock handler ran # before the failure. litellm.retrieve_batch( batch_id=unrelated_arn, custom_llm_provider="bedrock", ) async_invoke.assert_not_called() mij.assert_not_called() def test_non_bedrock_id_skips_bedrock_dispatch_entirely(mock_handlers): """Plain (non-ARN) batch ids must not even enter the Bedrock dispatch block — they belong to other providers' retrieve flows.""" async_invoke, mij, _ = mock_handlers with pytest.raises(openai.OpenAIError): litellm.retrieve_batch( batch_id="batch_abc123", custom_llm_provider="openai", ) async_invoke.assert_not_called() mij.assert_not_called()