litellm/tests/unit/proxy/video_endpoints/test_endpoints.py
ekpog200 ba61ecdf57
fix(videos)!: keep model_id in proxy video ids so per-model keys resolve on status/content (#44614)
* fix(videos): keep model_id in proxy video ids so per-model keys resolve on status/content

Provider transforms encode returned video ids with an empty model_id, so
GET /v1/videos/{id} and /content cannot resolve the deployment (and its
per-model api_key) and fail with invalid_api_key.

Re-stamp the returned VideoObject id with the resolved model_id in
video_generation, video_status, video_remix, video_edit and
video_extension.

Fixes #33423

* fix(videos): return a copy instead of mutating the response, read hidden params via helper

* test(videos): fully type the new video id re-stamping tests

Annotate every parameter and return added by this PR (hidden_params,
harness, response, -> None), drop Any from the test helper, mark locals
Final, and narrow the endpoint/helper results with isinstance(VideoObject)
instead of reading .id off an untyped value. Route the mocked processor
response through a typed Harness.respond_with, read NotRequired
DecodedVideoId keys with .get, and name the rule and reason on the
_hidden_params suppressions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(videos): fill only an empty model in returned video ids

Keep an id that already names a model as is, so the asked-for name,
routing group and served group in it still drive access checks and
routing. Fill an empty model from the model the caller asked for
before the routed deployment id, and read the request model through a
typed view so the type gate does not grow

* fix(videos): route video calls to the deployment that created the video

Video ids now carry the id of the deployment that created them. Status,
content, remix, edit and extension calls go to that deployment when it
belongs to the requested group, so a group spread over two provider
accounts no longer polls the wrong one. A provider model shared by
several groups resolves to the creating deployment's group, and the
asked-for model is written into an id only when the router can route it.

* fix(videos): pin video calls only to deployments the caller may use

Pin a video id's creating deployment at deployment selection, and only when the caller may use that
deployment in the requested group, so aliases, routing groups and access groups still apply and the
spend label and fallbacks keep the requested group.

A status poll keeps the deployment its id names and takes the deployment that answered when the id
names none. An alias that routes to the creating deployment's group is kept on status and content.

* test(router): reset module fallback globals between unit tests

* fix(videos): check and route polls by the name the router serves

A poll on a video created through a provider wildcard is checked against the name the wildcard serves, a team model is checked by its public name, a poll stamps the deployment that answered, and the sync Router pins video calls the same way the async path does

* fix(videos): pin polls only among the deployments routing allows

Video calls route by group again and the creating deployment is picked only from the deployments left after access, team, health, cooldown, budget and tag filters, so a pinned poll no longer skips them. Wildcard groups now pin too. Adds tests for cooldown, wildcard pinning and character lookups through a provider wildcard, and return types on the auth tests

* fix(videos): scope the video pin to video calls, keep fallback-served videos reachable, skip cooldown on video 404s

* fix(videos): stamp the requested group only when one of its router fallbacks served the create

* fix(videos): follow chained router fallbacks for fallback-served videos, test with a real Router

* test(router): list the video pin helper with its sibling in the router coverage exemptions

* refactor(auth): walk chained router fallbacks without recursion

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-20 05:42:59 +00:00

885 lines
32 KiB
Python

"""
Routing-contract tests for litellm/proxy/video_endpoints/endpoints.py
Unlike the batches layer, every video endpoint funnels into a single downstream
seam - ProxyBaseLLMRequestProcessing.base_process_llm_request - so there is no
provider-dispatch to assert. All of the video-specific, regression-worthy logic
runs *before* that call, while the endpoint assembles the `data` dict. Each test
therefore locks four things:
1. ROUTE_TYPE - the exact route_type each endpoint forwards
(avideo_generation/status/content/edit). Swapping two would
silently route requests to the wrong handler.
2. DATA SHAPE - the entire `data` dict the processor is constructed with:
provider-precedence resolution, video_id passthrough/extraction,
model resolution from the decoded model_id, and file attachment.
3. RESULT - base_process_llm_request's return value is propagated untouched
(except where the endpoint transforms it).
4. OUTPUT SHAPE - video_content wraps raw bytes in a Response (video/mp4 +
Content-Disposition).
Only true I/O boundaries are mocked (the downstream processor call, request body
parsing, file->bytes conversion, the provider-from-request readers, and the
router's model-id resolver). The id decode helpers and get_custom_provider_from_data
run for real, so the data assertions reflect production exactly. base_process is
patched with autospec so the real __init__ still stores self.data (captured via the
mock's call args), and a brand-new kwarg added to this layer surfaces as a failure.
"""
from contextlib import ExitStack
from dataclasses import dataclass
from collections.abc import Awaitable, Callable
from typing import Any, Dict, Final, Optional
from unittest.mock import AsyncMock, MagicMock, patch
import orjson
import pytest
import litellm.proxy.proxy_server as proxy_server
import litellm.proxy.video_endpoints.endpoints as endpoints
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing
from litellm.proxy.utils import ProxyLogging
from litellm.router import Router
from litellm.types.videos.main import VideoObject
from litellm.types.videos.utils import (
decode_video_id_with_provider,
encode_character_id_with_provider,
encode_video_id_with_provider,
)
from fastapi import Response
from starlette.datastructures import UploadFile as StarletteUploadFile
# --------------------------------------------------------------------------- #
# A real model-encoded video id: decodes (for real) to provider "azure",
# model_id VIDEO_MODEL_ID, original video id "video_orig123". The router's
# resolver maps that model_id to a model name; an unknown id resolves to None,
# so a wrong/hardcoded model_id cannot produce a plausible-looking result.
# --------------------------------------------------------------------------- #
VIDEO_MODEL_ID = "deployment-123"
AZURE_VIDEO_ID = encode_video_id_with_provider("video_orig123", "azure", VIDEO_MODEL_ID)
# A real model-encoded character id: decodes to provider "azure", VIDEO_MODEL_ID,
# original character id "char_orig". Distinct from the video id so a test cannot
# pass by reusing the wrong constant.
AZURE_CHARACTER_ID = encode_character_id_with_provider(
"char_orig", "azure", VIDEO_MODEL_ID
)
RESOLVED_MODELS: Dict[str, str] = {VIDEO_MODEL_ID: "azure-sora"}
# Sentinel propagated by base_process for the passthrough endpoints.
SENTINEL = object()
def _video_with_empty_model_id(
hidden_params: dict[str, object] | None = None,
) -> VideoObject:
video: Final[VideoObject] = VideoObject(
id=encode_video_id_with_provider("video_new456", "azure", ""),
object="video",
status="queued",
)
video.hidden_params = dict(hidden_params or {})
return video
def _assert_id_resolves_to_deployment(response: object, *, deployment_id: str | None = None) -> None:
assert isinstance(response, VideoObject)
expected_deployment: Final = {} if deployment_id is None else {"deployment_id": deployment_id}
assert decode_video_id_with_provider(response.id) == {
"custom_llm_provider": "azure",
"model_id": VIDEO_MODEL_ID,
"video_id": "video_new456",
**expected_deployment,
}
class FakeRequest:
"""Minimal stand-in. headers/query_params are read by the provider readers
(mocked) and on the edit path the raw body is parsed for real via orjson."""
def __init__(
self,
headers: Optional[Dict[str, str]] = None,
query: Optional[Dict[str, str]] = None,
raw_body: bytes = b"{}",
):
self.headers = headers or {}
self.query_params = query or {}
self._raw_body = raw_body
async def body(self) -> bytes:
return self._raw_body
@dataclass
class Harness:
read_body: AsyncMock
batch_to_bytesio: AsyncMock
base_process: MagicMock
handle_exc: AsyncMock
provider_from_headers: MagicMock
provider_from_query: MagicMock
provider_from_body: AsyncMock
router: MagicMock
resolve_model: MagicMock
def processor_data(self) -> Dict[str, Any]:
"""The exact `data` dict the processor was constructed with."""
assert self.base_process.call_count == 1
return dict(self.base_process.call_args.args[0].data)
def respond_with(self, response: object) -> None:
self.base_process.return_value = response # pyright: ignore[reportAny] # MagicMock subclasses Any in typeshed
def route_type(self) -> str:
return self.base_process.call_args.kwargs["route_type"]
@pytest.fixture
def harness():
logging = MagicMock(spec=ProxyLogging)
router = MagicMock(spec=Router)
resolve_model = MagicMock(
side_effect=lambda model_id, custom_llm_provider=None: RESOLVED_MODELS.get(model_id)
)
router.resolve_model_name_from_model_id = resolve_model
router.get_deployment.return_value = None
read_body = AsyncMock(return_value={})
batch_to_bytesio = AsyncMock(return_value=[b"filebytes"])
handle_exc = AsyncMock(return_value=RuntimeError("handled"))
provider_from_headers = MagicMock(return_value=None)
provider_from_query = MagicMock(return_value=None)
provider_from_body = AsyncMock(return_value=None)
with ExitStack() as stack:
base_process = stack.enter_context(
patch.object(
ProxyBaseLLMRequestProcessing,
"base_process_llm_request",
autospec=True,
)
)
base_process.return_value = SENTINEL
stack.enter_context(
patch.object(
ProxyBaseLLMRequestProcessing,
"handle_llm_api_exception",
handle_exc,
)
)
stack.enter_context(patch.object(endpoints, "read_request_body", read_body))
stack.enter_context(
patch.object(endpoints, "batch_to_bytesio", batch_to_bytesio)
)
stack.enter_context(
patch.object(
endpoints,
"get_custom_llm_provider_from_request_headers",
provider_from_headers,
)
)
stack.enter_context(
patch.object(
endpoints,
"get_custom_llm_provider_from_request_query",
provider_from_query,
)
)
stack.enter_context(
patch.object(
endpoints,
"get_custom_llm_provider_from_request_body",
provider_from_body,
)
)
stack.enter_context(patch.object(proxy_server, "llm_router", router))
stack.enter_context(patch.object(proxy_server, "proxy_logging_obj", logging))
stack.enter_context(patch.object(proxy_server, "general_settings", {}))
stack.enter_context(patch.object(proxy_server, "proxy_config", MagicMock()))
stack.enter_context(
patch.object(proxy_server, "select_data_generator", MagicMock())
)
stack.enter_context(patch.object(proxy_server, "user_model", None))
stack.enter_context(patch.object(proxy_server, "user_temperature", None))
stack.enter_context(patch.object(proxy_server, "user_request_timeout", None))
stack.enter_context(patch.object(proxy_server, "user_max_tokens", None))
stack.enter_context(patch.object(proxy_server, "user_api_base", None))
stack.enter_context(patch.object(proxy_server, "version", "test-version"))
yield Harness(
read_body=read_body,
batch_to_bytesio=batch_to_bytesio,
base_process=base_process,
handle_exc=handle_exc,
provider_from_headers=provider_from_headers,
provider_from_query=provider_from_query,
provider_from_body=provider_from_body,
router=router,
resolve_model=resolve_model,
)
def _user() -> UserAPIKeyAuth:
return UserAPIKeyAuth(api_key="sk-test")
# =========================================================================== #
# POST /v1/videos - video_generation #
# =========================================================================== #
async def call_generation(
harness: Harness, *, body: Dict[str, Any], input_reference=None
):
harness.read_body.return_value = body
return await endpoints.video_generation(
request=FakeRequest(),
fastapi_response=Response(),
input_reference=input_reference,
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_generation__route_type_data_and_no_provider_default(harness):
body = {"model": "sora-2", "prompt": "a sunset"}
resp = await call_generation(harness, body=body)
assert resp is SENTINEL
assert harness.route_type() == "avideo_generation"
# generation does NOT resolve a provider; data is the body, untouched. A
# future default custom_llm_provider injection would break this row.
assert harness.processor_data() == {"model": "sora-2", "prompt": "a sunset"}
harness.batch_to_bytesio.assert_not_called()
@pytest.mark.asyncio
async def test_generation__input_reference_attached(harness):
body = {"model": "sora-2", "prompt": "a sunset"}
upload = MagicMock(name="upload_file")
await call_generation(harness, body=body, input_reference=upload)
harness.batch_to_bytesio.assert_called_once_with([upload])
assert harness.processor_data() == {
"model": "sora-2",
"prompt": "a sunset",
"input_reference": b"filebytes",
}
@pytest.mark.asyncio
async def test_generation__restamps_id_with_asked_for_model_over_router_deployment_id(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id(hidden_params={"model_id": "deployment-uuid"}))
resp: Final = await call_generation(harness, body={"model": VIDEO_MODEL_ID, "prompt": "x"})
_assert_id_resolves_to_deployment(resp, deployment_id="deployment-uuid")
@pytest.mark.asyncio
async def test_generation__restamps_id_with_request_model_when_no_hidden_model_id(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id())
resp: Final = await call_generation(harness, body={"model": VIDEO_MODEL_ID, "prompt": "x"})
_assert_id_resolves_to_deployment(resp)
@pytest.mark.asyncio
async def test_generation__unroutable_request_model_falls_back_to_router_deployment_id(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id(hidden_params={"model_id": "deployment-uuid"}))
resp: Final = await call_generation(harness, body={"model": "team-public-sora", "prompt": "x"})
assert isinstance(resp, VideoObject)
decoded: Final = decode_video_id_with_provider(resp.id)
assert decoded.get("model_id") == "deployment-uuid"
assert decoded.get("deployment_id") == "deployment-uuid"
@pytest.mark.asyncio
async def test_generation__exception_routed_through_handler(harness):
harness.base_process.side_effect = ValueError("provider boom")
with pytest.raises(RuntimeError, match="handled"):
await call_generation(harness, body={"model": "sora-2"})
harness.handle_exc.assert_called_once()
assert harness.handle_exc.call_args.kwargs["e"].args[0] == "provider boom"
# =========================================================================== #
# GET /v1/videos/{video_id} - video_status #
# =========================================================================== #
async def call_status(harness: Harness, video_id: str, *, headers=None, query=None):
return await endpoints.video_status(
video_id=video_id,
request=FakeRequest(headers=headers, query=query),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_status__model_encoded_id_full_contract(harness):
resp = await call_status(harness, AZURE_VIDEO_ID)
assert resp is SENTINEL
assert harness.route_type() == "avideo_status"
# provider comes from the decoded id; model_id resolved to a model name.
harness.resolve_model.assert_called_once_with(VIDEO_MODEL_ID, "azure")
assert harness.processor_data() == {
"video_id": AZURE_VIDEO_ID,
"custom_llm_provider": "azure",
"model": "azure-sora",
}
@pytest.mark.asyncio
async def test_status__restamps_response_id_with_decoded_model_id(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id())
resp: Final = await call_status(harness, AZURE_VIDEO_ID)
_assert_id_resolves_to_deployment(resp)
@pytest.mark.asyncio
async def test_status__id_without_a_deployment_takes_the_deployment_that_answered_the_poll(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id(hidden_params={"model_id": "deployment-uuid"}))
resp: Final = await call_status(harness, AZURE_VIDEO_ID)
_assert_id_resolves_to_deployment(resp, deployment_id="deployment-uuid")
@pytest.mark.asyncio
async def test_status__plain_id_defaults_to_openai(harness):
await call_status(harness, "video_plain")
# plain id -> nothing decoded, no header/query/body provider -> "openai".
harness.resolve_model.assert_not_called()
assert harness.processor_data() == {
"video_id": "video_plain",
"custom_llm_provider": "openai",
}
@pytest.mark.asyncio
async def test_status__header_provider_beats_decoded_id(harness):
harness.provider_from_headers.return_value = "bedrock"
await call_status(harness, AZURE_VIDEO_ID)
data = harness.processor_data()
# header wins over the provider decoded from the id ...
assert data["custom_llm_provider"] == "bedrock"
# ... but the model is still resolved from the decoded model_id.
assert data["model"] == "azure-sora"
# =========================================================================== #
# GET /v1/videos/{video_id}/content - video_content #
# =========================================================================== #
async def call_content(harness: Harness, video_id: str, *, headers=None, query=None):
return await endpoints.video_content(
video_id=video_id,
request=FakeRequest(headers=headers, query=query),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_content__wraps_raw_bytes_in_response(harness):
harness.base_process.return_value = b"VIDEOBYTES"
resp = await call_content(harness, "video_plain")
assert harness.route_type() == "avideo_content"
assert isinstance(resp, Response)
assert resp.body == b"VIDEOBYTES"
assert resp.media_type == "video/mp4"
assert (
resp.headers["content-disposition"]
== "attachment; filename=video_video_plain.mp4"
)
@pytest.mark.asyncio
async def test_content__plain_id_has_no_openai_default(harness):
"""The high-value asymmetry vs video_status: content stops at the decoded
provider and never injects an 'openai' default, so a plain id leaves
custom_llm_provider unset. A copy-paste of status' fallback breaks this."""
harness.base_process.return_value = b"x"
await call_content(harness, "video_plain")
assert harness.processor_data() == {"video_id": "video_plain"}
@pytest.mark.asyncio
async def test_content__model_encoded_id(harness):
harness.base_process.return_value = b"x"
await call_content(harness, AZURE_VIDEO_ID)
harness.resolve_model.assert_called_once_with(VIDEO_MODEL_ID, "azure")
assert harness.processor_data() == {
"video_id": AZURE_VIDEO_ID,
"custom_llm_provider": "azure",
"model": "azure-sora",
}
@pytest.mark.asyncio
@pytest.mark.parametrize("call", [call_status, call_content])
async def test_status_and_content__hand_the_served_member_to_the_request_processor(harness, call):
"""The id names the routing group a key may be scoped to, and the member that served the task, which is
the only one holding it. The processor pins the member once key and team aliases have been applied."""
router = Router(
model_list=[
{"model_name": name, "litellm_params": {"model": "dashscope/wan3.0-video", "api_key": "k"}}
for name in ("wan3-a", "wan3-b")
],
routing_groups=[{"group_name": "wan3", "models": ["wan3-a", "wan3-b"], "routing_strategy": "simple-shuffle"}],
)
harness.base_process.return_value = b"x"
video_id = encode_video_id_with_provider("task1", "dashscope", "wan3", served_model="wan3-b")
with patch.object(proxy_server, "llm_router", router):
await call(harness, video_id)
assert harness.processor_data()["model"] == "wan3"
assert harness.base_process.call_args.args[0].served_model == "wan3-b"
@pytest.mark.asyncio
@pytest.mark.parametrize("call", [call_status, call_content])
async def test_status_and_content__shared_provider_model_routes_to_the_creating_deployments_group(
harness: Harness, call: Callable[[Harness, str], Awaitable[object]]
) -> None:
router: Final = Router(
model_list=[
{
"model_name": name,
"litellm_params": {"model": "azure/sora-2", "api_base": f"https://{name}.example.com", "api_key": name},
"model_info": {"id": f"dep-{name}"},
}
for name in ("sora-team", "sora-shared")
]
)
harness.base_process.return_value = b"x"
video_id: Final = encode_video_id_with_provider("video_raw", "azure", "sora-2", deployment_id="dep-sora-shared")
with patch.object(proxy_server, "llm_router", router):
await call(harness, video_id)
assert harness.processor_data()["model"] == "sora-shared"
# =========================================================================== #
# POST /v1/videos/edits - video_edit #
# =========================================================================== #
async def call_edit(
harness: Harness, *, body: Dict[str, Any], headers=None, query=None
):
harness.read_body.return_value = dict(body)
return await endpoints.video_edit(
request=FakeRequest(headers=headers, query=query, raw_body=orjson.dumps(body)),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_edit__extracts_nested_video_id_full_contract(harness):
resp = await call_edit(
harness, body={"prompt": "brighter", "video": {"id": AZURE_VIDEO_ID}}
)
assert resp is SENTINEL
assert harness.route_type() == "avideo_edit"
harness.resolve_model.assert_called_once_with(VIDEO_MODEL_ID, "azure")
# nested video object is popped; its id becomes video_id; provider/model
# derived from the encoded id.
assert harness.processor_data() == {
"prompt": "brighter",
"video_id": AZURE_VIDEO_ID,
"custom_llm_provider": "azure",
"model": "azure-sora",
}
@pytest.mark.asyncio
async def test_edit__restamps_new_video_id_with_source_model_id(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id())
resp: Final = await call_edit(harness, body={"prompt": "brighter", "video": {"id": AZURE_VIDEO_ID}})
_assert_id_resolves_to_deployment(resp)
@pytest.mark.asyncio
async def test_edit__provider_from_body_data_for_plain_id(harness):
"""For a plain id, get_custom_provider_from_data (run for real) pulls the
provider out of the request body before the 'openai' default."""
await call_edit(
harness,
body={
"prompt": "x",
"video": {"id": "video_plain"},
"custom_llm_provider": "vertex_ai",
},
)
data = harness.processor_data()
assert data["video_id"] == "video_plain"
assert data["custom_llm_provider"] == "vertex_ai"
harness.resolve_model.assert_not_called()
@pytest.mark.asyncio
async def test_edit__missing_video_object_defaults_to_openai(harness):
await call_edit(harness, body={"prompt": "x"})
data = harness.processor_data()
# no video object -> empty video_id; plain -> default provider.
assert data["video_id"] == ""
assert data["custom_llm_provider"] == "openai"
assert "video" not in data
@pytest.mark.asyncio
async def test_edit__bare_string_video_id_from_form_field(harness):
await call_edit(harness, body={"prompt": "brighter", "video": "video_plain"})
assert harness.processor_data() == {
"prompt": "brighter",
"video_id": "video_plain",
"custom_llm_provider": "openai",
}
@pytest.mark.asyncio
async def test_edit__json_string_video_reference_from_form_field(harness):
await call_edit(
harness,
body={"prompt": "brighter", "video": orjson.dumps({"id": "video_plain"}).decode()},
)
assert harness.processor_data()["video_id"] == "video_plain"
@pytest.mark.asyncio
async def test_edit__uploaded_video_restamps_new_id_with_requested_model(harness: Harness) -> None:
import io
upload: Final = StarletteUploadFile(file=io.BytesIO(b"rawmp4"), filename="clip.mp4")
harness.read_body.return_value = {"model": VIDEO_MODEL_ID, "prompt": "make it nighttime", "video": upload}
harness.respond_with(_video_with_empty_model_id())
resp: Final = await endpoints.video_edit(
request=FakeRequest(raw_body=b"multipart"),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
_assert_id_resolves_to_deployment(resp)
@pytest.mark.asyncio
async def test_edit__uploaded_video_file_is_forwarded_not_dropped(harness):
"""A multipart-uploaded source video must be converted to bytes and attached
under ``video`` so the provider receives the file. Before the fix the upload
was popped, coerced to an empty ``video_id``, and silently dropped."""
import io
upload = StarletteUploadFile(file=io.BytesIO(b"rawmp4"), filename="clip.mp4")
harness.read_body.return_value = {"prompt": "make it nighttime", "video": upload}
await endpoints.video_edit(
request=FakeRequest(raw_body=b"multipart"),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
harness.batch_to_bytesio.assert_called_once_with((upload,))
assert harness.processor_data() == {
"prompt": "make it nighttime",
"video": b"filebytes",
"video_id": "",
"custom_llm_provider": "openai",
}
# =========================================================================== #
# GET /v1/videos - video_list #
# =========================================================================== #
async def call_list(harness: Harness, *, headers=None, query=None):
return await endpoints.video_list(
request=FakeRequest(headers=headers, query=query),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_list__query_params_and_no_provider(harness):
resp = await call_list(harness, query={"limit": "5"})
assert resp is SENTINEL
assert harness.route_type() == "avideo_list"
# no provider anywhere -> custom_llm_provider stays absent (only set if truthy).
assert harness.processor_data() == {"query_params": {"limit": "5"}}
@pytest.mark.asyncio
async def test_list__provider_from_header(harness):
harness.provider_from_headers.return_value = "bedrock"
await call_list(harness)
assert harness.processor_data() == {
"query_params": {},
"custom_llm_provider": "bedrock",
}
# =========================================================================== #
# POST /v1/videos/{video_id}/remix - video_remix #
# =========================================================================== #
async def call_remix(
harness: Harness, video_id: str, *, body, headers=None, query=None
):
harness.read_body.return_value = dict(body)
return await endpoints.video_remix(
video_id=video_id,
request=FakeRequest(headers=headers, query=query, raw_body=orjson.dumps(body)),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_remix__model_encoded_id_full_contract(harness):
resp = await call_remix(harness, AZURE_VIDEO_ID, body={"prompt": "new colors"})
assert resp is SENTINEL
assert harness.route_type() == "avideo_remix"
harness.resolve_model.assert_called_once_with(VIDEO_MODEL_ID, "azure")
assert harness.processor_data() == {
"prompt": "new colors",
"video_id": AZURE_VIDEO_ID,
"custom_llm_provider": "azure",
"model": "azure-sora",
}
@pytest.mark.asyncio
async def test_remix__restamps_new_video_id_with_source_model_id(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id())
resp: Final = await call_remix(harness, AZURE_VIDEO_ID, body={"prompt": "new colors"})
_assert_id_resolves_to_deployment(resp)
@pytest.mark.asyncio
async def test_remix__provider_from_body_data_not_request_body_reader(harness):
"""remix resolves the provider from data.get('custom_llm_provider'), never
from the async request-body reader (unlike status/get_character). Setting
that reader to a sentinel and asserting it is untouched locks the difference."""
harness.provider_from_body.return_value = "must-not-win"
await call_remix(
harness,
"video_plain",
body={"prompt": "x", "custom_llm_provider": "vertex_ai"},
)
harness.provider_from_body.assert_not_called()
data = harness.processor_data()
assert data["video_id"] == "video_plain"
assert data["custom_llm_provider"] == "vertex_ai"
@pytest.mark.asyncio
async def test_remix__plain_id_has_no_openai_default(harness):
await call_remix(harness, "video_plain", body={"prompt": "x"})
# like video_content, remix stops at provider_from_id with no 'openai' default.
assert harness.processor_data() == {"prompt": "x", "video_id": "video_plain"}
# =========================================================================== #
# POST /v1/videos/characters - video_create_character #
# =========================================================================== #
async def call_create_character(harness: Harness, *, body, video=None, name="my_char"):
harness.read_body.return_value = body
return await endpoints.video_create_character(
request=FakeRequest(),
fastapi_response=Response(),
video=video if video is not None else MagicMock(name="video_upload"),
name=name,
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_create_character__video_attached_default_provider_no_encode(harness):
upload = MagicMock(name="video_upload")
resp = await call_create_character(harness, body={"prompt": "x"}, video=upload)
assert resp is SENTINEL
assert harness.route_type() == "avideo_create_character"
harness.batch_to_bytesio.assert_called_once_with([upload])
# no target_model_names -> no model injected, no id re-encoding.
assert harness.processor_data() == {
"prompt": "x",
"video": b"filebytes",
"custom_llm_provider": "openai",
}
@pytest.mark.asyncio
async def test_create_character__target_model_sets_model_and_encodes_id(harness):
harness.base_process.return_value = {"id": "char_raw"}
resp = await call_create_character(
harness,
body={"target_model_names": "azure-sora-model", "custom_llm_provider": "azure"},
)
data = harness.processor_data()
assert data["model"] == "azure-sora-model"
assert data["custom_llm_provider"] == "azure"
# response id re-encoded with the resolved provider + model for the round-trip.
assert resp["id"] == encode_character_id_with_provider(
"char_raw", "azure", "azure-sora-model"
)
# =========================================================================== #
# GET /v1/videos/characters/{character_id} - video_get_character #
# =========================================================================== #
async def call_get_character(
harness: Harness, character_id: str, *, headers=None, query=None
):
return await endpoints.video_get_character(
character_id=character_id,
request=FakeRequest(headers=headers, query=query),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_get_character__encoded_id_full_contract(harness):
harness.base_process.return_value = {"id": "char_raw2"}
resp = await call_get_character(harness, AZURE_CHARACTER_ID)
assert harness.route_type() == "avideo_get_character"
harness.resolve_model.assert_called_once_with(VIDEO_MODEL_ID, "azure")
# character_id decoded to its inner value; provider/model from the encoded id.
assert harness.processor_data() == {
"character_id": "char_orig",
"custom_llm_provider": "azure",
"model": "azure-sora",
}
# response id re-encoded for the client round-trip.
assert resp["id"] == encode_character_id_with_provider(
"char_raw2", "azure", VIDEO_MODEL_ID
)
@pytest.mark.asyncio
async def test_get_character__plain_id_defaults_openai_no_encode(harness):
harness.base_process.return_value = {"id": "char_raw3"}
resp = await call_get_character(harness, "char_plain")
harness.resolve_model.assert_not_called()
assert harness.processor_data() == {
"character_id": "char_plain",
"custom_llm_provider": "openai",
}
# id does not start with 'character_' -> returned untouched.
assert resp["id"] == "char_raw3"
@pytest.mark.asyncio
async def test_get_character__model_served_only_by_a_provider_wildcard_routes_through_it(harness: Harness) -> None:
router: Final = Router(
model_list=[{"model_name": "openai/*", "litellm_params": {"model": "openai/*", "api_key": "key-w"}}]
)
harness.base_process.return_value = {"id": "char_raw4"}
character_id: Final = encode_character_id_with_provider("char_orig", "openai", "sora-2")
with patch.object(proxy_server, "llm_router", router):
await call_get_character(harness, character_id)
assert harness.processor_data()["model"] == "sora-2"
# =========================================================================== #
# POST /v1/videos/extensions - video_extension #
# =========================================================================== #
async def call_extension(harness: Harness, *, body, headers=None, query=None):
harness.read_body.return_value = dict(body)
return await endpoints.video_extension(
request=FakeRequest(headers=headers, query=query, raw_body=orjson.dumps(body)),
fastapi_response=Response(),
user_api_key_dict=_user(),
)
@pytest.mark.asyncio
async def test_extension__extracts_nested_video_id_full_contract(harness):
resp = await call_extension(
harness, body={"prompt": "continue", "video": {"id": AZURE_VIDEO_ID}}
)
assert resp is SENTINEL
assert harness.route_type() == "avideo_extension"
harness.resolve_model.assert_called_once_with(VIDEO_MODEL_ID, "azure")
assert harness.processor_data() == {
"prompt": "continue",
"video_id": AZURE_VIDEO_ID,
"custom_llm_provider": "azure",
"model": "azure-sora",
}
@pytest.mark.asyncio
async def test_extension__restamps_new_video_id_with_source_model_id(harness: Harness) -> None:
harness.respond_with(_video_with_empty_model_id())
resp: Final = await call_extension(harness, body={"prompt": "continue", "video": {"id": AZURE_VIDEO_ID}})
_assert_id_resolves_to_deployment(resp)