mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-01 07:03:02 +00:00
test(responses): reproduce comparison response ID ownership and wire failures
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
e6b4774ac6
commit
4eefe343b1
3 changed files with 449 additions and 4 deletions
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import json
|
||||
import os
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Mapping
|
||||
from pathlib import Path
|
||||
from typing import Final, Literal
|
||||
from urllib.parse import urlsplit
|
||||
|
|
@ -16,7 +16,7 @@ from integration._support.process import owned_proxy
|
|||
from integration._support.responses_vendor import same_response
|
||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||
from integration._support.worker_readiness import wait_for_worker_readiness
|
||||
from openai.types.responses import ResponseInputParam
|
||||
from openai.types.responses import ResponseCompletedEvent, ResponseInputParam
|
||||
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
|
||||
|
||||
pytestmark: Final = pytest.mark.shares_worker_state
|
||||
|
|
@ -138,6 +138,213 @@ def _every_id_route(client: openai.OpenAI, model: str, response_id: str) -> tupl
|
|||
)
|
||||
|
||||
|
||||
def _comparison_body(response_id: str, nested: bool) -> Mapping[str, object]:
|
||||
options: Final = {"comparison_response_id": response_id}
|
||||
if nested:
|
||||
return {
|
||||
"prompt_cache_options": {"mode": "explicit", "ttl": "12h"},
|
||||
"extra_body": {"prompt_cache_options": options},
|
||||
}
|
||||
return {"prompt_cache_options": options}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("prefix", "nested", "stream"),
|
||||
(("/v1", False, False), ("", True, False), ("/openai/v1", False, True), ("/v1", True, True)),
|
||||
)
|
||||
def test_regression_comparison_response_id_owner_repeated_requests_reach_raw_provider_id(
|
||||
gateway: Gateway, prefix: str, nested: bool, stream: bool
|
||||
) -> None:
|
||||
raw_id: Final = f"resp_comparison_{uuid.uuid4().hex}"
|
||||
|
||||
def respond(request: Request) -> Reply:
|
||||
if request.target == "/v1/models":
|
||||
return _model_discovery_reply()
|
||||
assert (request.method, request.target) == ("POST", "/v1/responses")
|
||||
body: Final = _JSON_OBJECT.validate_json(request.body)
|
||||
if body["input"] == "create comparison response":
|
||||
assert body == {"model": _MODEL, "input": "create comparison response"}, request.body
|
||||
return Reply(body=_response(raw_id))
|
||||
assert body == {
|
||||
"model": _MODEL,
|
||||
"input": "compare owned response",
|
||||
"stream": stream,
|
||||
"prompt_cache_options": {"comparison_response_id": raw_id},
|
||||
}, request.body
|
||||
if stream:
|
||||
return Reply(
|
||||
content_type="text/event-stream",
|
||||
chunks=(
|
||||
b"data: "
|
||||
+ json.dumps({"type": "response.completed", "response": json.loads(_response(raw_id))}).encode()
|
||||
+ b"\n\n",
|
||||
b"data: [DONE]\n\n",
|
||||
),
|
||||
)
|
||||
return Reply(body=_response(raw_id))
|
||||
|
||||
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire)
|
||||
team: Final = scenario.team(models=[model])
|
||||
user: Final = scenario.member(team)
|
||||
with _client(gateway, _key(scenario, team, user, model), prefix) as client:
|
||||
created: Final = client.responses.create(model=model, input="create comparison response")
|
||||
assert created.id != raw_id, created.model_dump_json()
|
||||
assert len(_contract_requests(wire)) == 1
|
||||
|
||||
def compare_again() -> None:
|
||||
result: Final = client.responses.create(
|
||||
model=model,
|
||||
input="compare owned response",
|
||||
stream=stream,
|
||||
extra_body=_comparison_body(created.id, nested),
|
||||
)
|
||||
if isinstance(result, openai.Stream):
|
||||
events: Final = tuple(result)
|
||||
assert isinstance(events[-1], ResponseCompletedEvent), events
|
||||
assert events[-1].response.status == "completed", events
|
||||
else:
|
||||
assert result.status == "completed", result.model_dump_json()
|
||||
|
||||
for _ in range(2):
|
||||
compare_again()
|
||||
assert len(_contract_requests(wire)) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nested", (False, True))
|
||||
@pytest.mark.parametrize("stranger", ("user", "team"))
|
||||
@pytest.mark.parametrize("retained", (False, True), ids=("ordinary", "client-internal-field"))
|
||||
def test_regression_comparison_response_id_other_identity_is_denied_before_dispatch(
|
||||
gateway: Gateway, nested: bool, stranger: str, retained: bool
|
||||
) -> None:
|
||||
raw_id: Final = f"resp_comparison_{uuid.uuid4().hex}"
|
||||
|
||||
def respond(request: Request) -> Reply:
|
||||
if request.target == "/v1/models":
|
||||
return _model_discovery_reply()
|
||||
return Reply(body=_response(raw_id))
|
||||
|
||||
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire)
|
||||
team_a: Final = scenario.team(models=[model])
|
||||
team_b: Final = scenario.team(models=[model])
|
||||
user_a: Final = scenario.member(team_a)
|
||||
user_b: Final = scenario.member(team_a)
|
||||
gateway.post("/team/member_add", {"team_id": team_b, "member": {"role": "user", "user_id": user_a}})
|
||||
with (
|
||||
_client(gateway, _key(scenario, team_a, user_a, model), "/v1") as owner,
|
||||
_client(
|
||||
gateway,
|
||||
_key(
|
||||
scenario, team_a if stranger == "user" else team_b, user_b if stranger == "user" else user_a, model
|
||||
),
|
||||
"/v1",
|
||||
) as other,
|
||||
):
|
||||
created: Final = owner.responses.create(model=model, input="create private comparison")
|
||||
assert created.id != raw_id
|
||||
assert len(_contract_requests(wire)) == 1
|
||||
_refused(
|
||||
lambda: other.responses.create(
|
||||
model=model,
|
||||
input="compare another identity",
|
||||
extra_body={
|
||||
**_comparison_body(created.id, nested),
|
||||
**(
|
||||
{"_litellm_addressed_comparison_response_id": "resp_unmanaged_comparison"}
|
||||
if retained
|
||||
else {}
|
||||
),
|
||||
},
|
||||
),
|
||||
_OTHER_USER_DETAIL if stranger == "user" else _OTHER_TEAM_DETAIL,
|
||||
)
|
||||
assert _contract_requests(wire) == (), "A cross-identity comparison reached the provider"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nested", (False, True))
|
||||
@pytest.mark.parametrize("comparison_id", ("resp_unmanaged_comparison", "resp_!!!not-base64", "cmF3X3ZlbmRvcl9pZA=="))
|
||||
def test_guard_unmanaged_comparison_response_ids_pass_through_without_opt_in(
|
||||
gateway: Gateway, nested: bool, comparison_id: str
|
||||
) -> None:
|
||||
def respond(request: Request) -> Reply:
|
||||
if request.target == "/v1/models":
|
||||
return _model_discovery_reply()
|
||||
assert (request.method, request.target) == ("POST", "/v1/responses")
|
||||
assert _JSON_OBJECT.validate_json(request.body) == {
|
||||
"model": _MODEL,
|
||||
"input": "unmanaged comparison",
|
||||
"prompt_cache_options": {"comparison_response_id": comparison_id},
|
||||
}, request.body
|
||||
return Reply(body=_response(f"resp_result_{uuid.uuid4().hex}"))
|
||||
|
||||
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire)
|
||||
team: Final = scenario.team(models=[model])
|
||||
user: Final = scenario.member(team)
|
||||
with _client(gateway, _key(scenario, team, user, model), "/v1") as client:
|
||||
compared: Final = client.responses.create(
|
||||
model=model, input="unmanaged comparison", extra_body=_comparison_body(comparison_id, nested)
|
||||
)
|
||||
assert compared.status == "completed", compared.model_dump_json()
|
||||
assert len(_contract_requests(wire)) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("effective_owner", (False, True), ids=("guard-raw", "regression-owned"))
|
||||
def test_comparison_extra_body_ignores_foreign_top_level(gateway: Gateway, effective_owner: bool) -> None:
|
||||
foreign_raw_id: Final = f"resp_ignored_{uuid.uuid4().hex}"
|
||||
owner_raw_id: Final = f"resp_effective_{uuid.uuid4().hex}"
|
||||
unmanaged_id: Final = "resp_raw_effective_comparison"
|
||||
|
||||
def respond(request: Request) -> Reply:
|
||||
if request.target == "/v1/models":
|
||||
return _model_discovery_reply()
|
||||
assert (request.method, request.target) == ("POST", "/v1/responses")
|
||||
body: Final = _JSON_OBJECT.validate_json(request.body)
|
||||
if body["input"] in ("seed ignored comparison", "seed effective comparison"):
|
||||
assert body == {"model": _MODEL, "input": body["input"]}, request.body
|
||||
return Reply(body=_response(foreign_raw_id if body["input"] == "seed ignored comparison" else owner_raw_id))
|
||||
assert body == {
|
||||
"model": _MODEL,
|
||||
"input": "compare effective options",
|
||||
"prompt_cache_options": {"comparison_response_id": owner_raw_id if effective_owner else unmanaged_id},
|
||||
}, request.body
|
||||
return Reply(body=_response(owner_raw_id))
|
||||
|
||||
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire)
|
||||
team: Final = scenario.team(models=[model])
|
||||
owner_user: Final = scenario.member(team)
|
||||
foreign_user: Final = scenario.member(team)
|
||||
with (
|
||||
_client(gateway, _key(scenario, team, owner_user, model), "/v1") as owner,
|
||||
_client(gateway, _key(scenario, team, foreign_user, model), "/v1") as foreign,
|
||||
):
|
||||
foreign_response: Final = foreign.responses.create(model=model, input="seed ignored comparison")
|
||||
assert foreign_response.id != foreign_raw_id
|
||||
owner_response: Final = owner.responses.create(model=model, input="seed effective comparison")
|
||||
assert owner_response.id != owner_raw_id
|
||||
assert len(_contract_requests(wire)) == 2
|
||||
compared: Final = owner.responses.create(
|
||||
model=model,
|
||||
input="compare effective options",
|
||||
extra_body={
|
||||
"prompt_cache_options": {
|
||||
"comparison_response_id": foreign_response.id,
|
||||
"mode": "explicit",
|
||||
"ttl": "12h",
|
||||
},
|
||||
"extra_body": {
|
||||
"prompt_cache_options": {
|
||||
"comparison_response_id": owner_response.id if effective_owner else unmanaged_id
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
assert compared.status == "completed", compared.model_dump_json()
|
||||
assert len(_contract_requests(wire)) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("prefix", _ALIASES)
|
||||
def test_response_ids_are_scoped_to_the_issuing_user_and_team(gateway: Gateway, prefix: str) -> None:
|
||||
raw_id: Final = f"resp_owned_{uuid.uuid4().hex}"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from typing import Final
|
||||
from collections.abc import AsyncIterator, Callable, Iterator
|
||||
from copy import deepcopy
|
||||
from typing import Final, Protocol, runtime_checkable
|
||||
|
||||
import openai
|
||||
import pytest
|
||||
|
|
@ -14,6 +16,10 @@ from integration._support.worker_readiness import wait_for_worker_readiness
|
|||
from openai.types.responses import ResponseCompletedEvent
|
||||
from pydantic import JsonValue, TypeAdapter
|
||||
|
||||
import litellm
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
from litellm.types.llms.openai import ResponseCompletedEvent as LiteLLMResponseCompletedEvent
|
||||
|
||||
_MODEL: Final = "gpt-5"
|
||||
_API_KEY: Final = "synthetic-responses-key"
|
||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||
|
|
@ -21,6 +27,16 @@ _PROXY_WORKERS: Final = max(1, int(os.environ.get("INTEGRATION_PROXY_WORKERS", "
|
|||
_ALIASES: Final = ("/v1", "", "/openai/v1")
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class _AsyncResponseEvents(Protocol):
|
||||
def __aiter__(self) -> AsyncIterator[object]: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class _ResponseEvents(Protocol):
|
||||
def __iter__(self) -> Iterator[object]: ...
|
||||
|
||||
|
||||
def _deployments_in_group(gateway: Gateway, group: str) -> int:
|
||||
entries: Final = gateway.get("/model/info")["data"]
|
||||
assert isinstance(entries, list), entries
|
||||
|
|
@ -69,6 +85,172 @@ def _stream(identity: str) -> tuple[bytes, ...]:
|
|||
) + (b"data: [DONE]\n\n",)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("asynchronous", (False, True), ids=("sync", "async"))
|
||||
@pytest.mark.parametrize("stream", (False, True), ids=("nonstream", "stream"))
|
||||
@pytest.mark.parametrize("extra", (False, True), ids=("top-level", "extra-body-precedence"))
|
||||
async def test_regression_sdk_comparison_response_id_is_raw_on_the_wire(
|
||||
asynchronous: bool, stream: bool, extra: bool
|
||||
) -> None:
|
||||
raw_id: Final = f"resp_sdk_comparison_{uuid.uuid4().hex}"
|
||||
|
||||
def respond(request: Request) -> Reply:
|
||||
assert (request.method, request.target) == ("POST", "/v1/responses")
|
||||
body: Final = _JSON_OBJECT.validate_json(request.body)
|
||||
if body["input"] == "seed comparison":
|
||||
assert body == {"model": _MODEL, "input": "seed comparison"}, request.body
|
||||
return Reply(body=_response(raw_id))
|
||||
assert body == {
|
||||
"model": _MODEL,
|
||||
"input": "compare response",
|
||||
"stream": stream,
|
||||
"previous_response_id": raw_id,
|
||||
"prompt_cache_options": {"comparison_response_id": raw_id},
|
||||
}, request.body
|
||||
return (
|
||||
Reply(content_type="text/event-stream", chunks=_stream(raw_id)) if stream else Reply(body=_response(raw_id))
|
||||
)
|
||||
|
||||
with wire_server(respond) as wire:
|
||||
seeded: Final = litellm.responses(
|
||||
model=f"openai/{_MODEL}", input="seed comparison", api_base=f"{wire.url}/v1", api_key=_API_KEY
|
||||
)
|
||||
assert isinstance(seeded, ResponsesAPIResponse), seeded
|
||||
assert seeded.id != raw_id, seeded
|
||||
params: Final = {
|
||||
"model": f"openai/{_MODEL}",
|
||||
"input": "compare response",
|
||||
"stream": stream,
|
||||
"previous_response_id": seeded.id,
|
||||
"api_base": f"{wire.url}/v1",
|
||||
"api_key": _API_KEY,
|
||||
"num_retries": 0,
|
||||
**(
|
||||
{
|
||||
"prompt_cache_options": {"mode": "explicit", "ttl": "12h"},
|
||||
"extra_body": {"prompt_cache_options": {"comparison_response_id": seeded.id}},
|
||||
}
|
||||
if extra
|
||||
else {"prompt_cache_options": {"comparison_response_id": seeded.id}}
|
||||
),
|
||||
}
|
||||
original: Final = deepcopy(params)
|
||||
result: Final = await litellm.aresponses(**params) if asynchronous else litellm.responses(**params)
|
||||
if stream:
|
||||
if asynchronous:
|
||||
assert isinstance(result, _AsyncResponseEvents), result
|
||||
async_events: Final = tuple([event async for event in result])
|
||||
async_completed: Final = async_events[-1]
|
||||
assert isinstance(async_completed, LiteLLMResponseCompletedEvent), async_events
|
||||
assert async_completed.response.status == "completed", async_events
|
||||
else:
|
||||
assert isinstance(result, _ResponseEvents), result
|
||||
sync_events: Final = tuple(result)
|
||||
sync_completed: Final = sync_events[-1]
|
||||
assert isinstance(sync_completed, LiteLLMResponseCompletedEvent), sync_events
|
||||
assert sync_completed.response.status == "completed", sync_events
|
||||
else:
|
||||
assert isinstance(result, ResponsesAPIResponse), result
|
||||
assert result.status == "completed", result
|
||||
assert params == original, "The caller's comparison options were mutated"
|
||||
assert len(wire.drain()) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("extra", (False, True))
|
||||
@pytest.mark.parametrize(
|
||||
"comparison_id",
|
||||
(
|
||||
"resp_custom_vendor_id",
|
||||
"resp_!!!not-base64",
|
||||
"cmF3X3ZlbmRvcl9pZA==",
|
||||
pytest.param(
|
||||
"resp_"
|
||||
+ base64.b64encode(b"litellm:custom_llm_provider:xai;model_id:foreign;response_id:resp_foreign").decode(),
|
||||
id="foreign-provider-wrapper",
|
||||
),
|
||||
pytest.param("resp_" + base64.b64encode(b"custom;id;response").decode(), id="unmanaged-semicolons"),
|
||||
),
|
||||
)
|
||||
def test_guard_sdk_custom_host_preserves_unmanaged_comparison_ids(extra: bool, comparison_id: str) -> None:
|
||||
def respond(request: Request) -> Reply:
|
||||
assert (request.method, request.target) == ("POST", "/v1/responses")
|
||||
assert _JSON_OBJECT.validate_json(request.body) == {
|
||||
"model": _MODEL,
|
||||
"input": "custom comparison",
|
||||
"prompt_cache_options": {"comparison_response_id": comparison_id, "mode": "implicit", "ttl": "30m"},
|
||||
}, request.body
|
||||
return Reply(body=_response("resp_custom_result"))
|
||||
|
||||
with wire_server(respond) as wire:
|
||||
options: Final = {
|
||||
"comparison_response_id": comparison_id,
|
||||
"mode": "implicit",
|
||||
"ttl": "30m",
|
||||
}
|
||||
params: Final = (
|
||||
{"extra_body": {"prompt_cache_options": options}} if extra else {"prompt_cache_options": options}
|
||||
)
|
||||
original: Final = deepcopy(params)
|
||||
result: Final = litellm.responses(
|
||||
model=f"openai/{_MODEL}", input="custom comparison", api_base=f"{wire.url}/v1", api_key=_API_KEY, **params
|
||||
)
|
||||
assert isinstance(result, ResponsesAPIResponse), result
|
||||
assert result.status == "completed", result
|
||||
assert params == original
|
||||
assert len(wire.drain()) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider", ("azure", "xai"))
|
||||
@pytest.mark.parametrize("extra", (False, True))
|
||||
def test_guard_inherited_responses_providers_preserve_comparison_wrappers(provider: str, extra: bool) -> None:
|
||||
raw_id: Final = f"resp_inherited_{uuid.uuid4().hex}"
|
||||
|
||||
def seed(request: Request) -> Reply:
|
||||
assert _JSON_OBJECT.validate_json(request.body) == {"model": _MODEL, "input": "seed inherited comparison"}
|
||||
return Reply(body=_response(raw_id))
|
||||
|
||||
with wire_server(seed) as seed_wire:
|
||||
seeded: Final = litellm.responses(
|
||||
model=f"openai/{_MODEL}",
|
||||
input="seed inherited comparison",
|
||||
api_base=f"{seed_wire.url}/v1",
|
||||
api_key=_API_KEY,
|
||||
)
|
||||
assert isinstance(seeded, ResponsesAPIResponse), seeded
|
||||
assert seeded.id != raw_id
|
||||
assert len(seed_wire.drain()) == 1
|
||||
|
||||
def respond(request: Request) -> Reply:
|
||||
assert request.method == "POST", request
|
||||
expected_target: Final = (
|
||||
"/openai/responses?api-version=2025-03-01-preview" if provider == "azure" else "/v1/responses"
|
||||
)
|
||||
assert request.target == expected_target, request.target
|
||||
assert _JSON_OBJECT.validate_json(request.body) == {
|
||||
"model": _MODEL,
|
||||
"input": "inherited comparison",
|
||||
"prompt_cache_options": {"comparison_response_id": seeded.id},
|
||||
}, request.body
|
||||
return Reply(body=_response(raw_id))
|
||||
|
||||
with wire_server(respond) as wire:
|
||||
options: Final = {"comparison_response_id": seeded.id}
|
||||
params: Final = (
|
||||
{"extra_body": {"prompt_cache_options": options}} if extra else {"prompt_cache_options": options}
|
||||
)
|
||||
result: Final = litellm.responses(
|
||||
model=f"{provider}/{_MODEL}",
|
||||
input="inherited comparison",
|
||||
api_base=wire.url if provider == "azure" else f"{wire.url}/v1",
|
||||
api_key=_API_KEY,
|
||||
api_version="2025-03-01-preview" if provider == "azure" else None,
|
||||
**params,
|
||||
)
|
||||
assert isinstance(result, ResponsesAPIResponse), result
|
||||
assert result.status == "completed", result
|
||||
assert len(wire.drain()) == 1
|
||||
|
||||
|
||||
def _register_in_group(scenario: Scenario, wire: Wire, group: str, order: int | None = None) -> str:
|
||||
existing: Final = _deployments_in_group(scenario.gateway, group)
|
||||
created: Final = scenario.gateway.post(
|
||||
|
|
|
|||
|
|
@ -4,11 +4,16 @@ Tests for ResponsesIDSecurity hook.
|
|||
Tests the security hook that prevents user B from seeing response from user A.
|
||||
"""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Final
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from litellm.caching.caching import DualCache
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.hooks.responses_id_security import (
|
||||
ResponsesIDSecurity,
|
||||
_streams_response_ids,
|
||||
|
|
@ -854,6 +859,57 @@ def _issue_managed_id(hook, owner, provider_response_id=_FABRICATED_PROVIDER_RES
|
|||
return issued.id
|
||||
|
||||
|
||||
def _comparison_options(data: object, nested: bool) -> Mapping[str, str]:
|
||||
request: Final = TypeAdapter(dict[str, object]).validate_python(data)
|
||||
container: Final = TypeAdapter(dict[str, object]).validate_python(request["extra_body"]) if nested else request
|
||||
return TypeAdapter(dict[str, str]).validate_python(container["prompt_cache_options"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("nested", (False, True))
|
||||
@pytest.mark.parametrize("stranger", ("user", "team"))
|
||||
async def test_regression_comparison_id_reentry_keeps_ownership_after_owner_retry(
|
||||
salt_key_env: str, nested: bool, stranger: str
|
||||
) -> None:
|
||||
hook: Final = ResponsesIDSecurity(general_settings_reader=lambda: {}, signing_key_reader=lambda: salt_key_env)
|
||||
owner: Final = UserAPIKeyAuth(user_id="owner-user", team_id="owner-team")
|
||||
caller: Final = UserAPIKeyAuth(
|
||||
user_id="stranger-user" if stranger == "user" else owner.user_id,
|
||||
team_id="stranger-team" if stranger == "team" else owner.team_id,
|
||||
)
|
||||
cache: Final = DualCache()
|
||||
issued: Final = ResponsesAPIResponse.model_validate(
|
||||
await hook.async_post_call_success_hook( # pyright: ignore[reportUnknownMemberType] # Legacy dict boundary
|
||||
data={},
|
||||
user_api_key_dict=owner,
|
||||
response=ResponsesAPIResponse(
|
||||
id=_FABRICATED_PROVIDER_RESPONSE_ID, created_at=1234567890, output=[], status="completed"
|
||||
),
|
||||
)
|
||||
)
|
||||
assert issued.id != _FABRICATED_PROVIDER_RESPONSE_ID
|
||||
options: Final = {"comparison_response_id": issued.id}
|
||||
request: Final = {"extra_body": {"prompt_cache_options": options}} if nested else {"prompt_cache_options": options}
|
||||
first: Final = TypeAdapter(dict[str, object]).validate_python(
|
||||
await hook.async_pre_call_hook( # pyright: ignore[reportUnknownMemberType] # Legacy dict boundary
|
||||
user_api_key_dict=owner, cache=cache, data=request, call_type="aresponses"
|
||||
)
|
||||
)
|
||||
retried: Final = TypeAdapter(dict[str, object]).validate_python(
|
||||
await hook.async_pre_call_hook( # pyright: ignore[reportUnknownMemberType] # Legacy dict boundary
|
||||
user_api_key_dict=owner, cache=cache, data=request, call_type="aresponses"
|
||||
)
|
||||
)
|
||||
with pytest.raises(HTTPException) as refusal:
|
||||
await hook.async_pre_call_hook( # pyright: ignore[reportUnknownMemberType] # Legacy dict boundary
|
||||
user_api_key_dict=caller, cache=cache, data=request, call_type="aresponses"
|
||||
)
|
||||
assert refusal.value.status_code == 403
|
||||
assert f"not associated with the {stranger}" in refusal.value.detail
|
||||
assert _comparison_options(first, nested) == {"comparison_response_id": _FABRICATED_PROVIDER_RESPONSE_ID}
|
||||
assert _comparison_options(retried, nested) == {"comparison_response_id": _FABRICATED_PROVIDER_RESPONSE_ID}
|
||||
|
||||
|
||||
class TestUnrecognizedResponseIdIsRejected:
|
||||
"""An id this proxy never issued carries no owner, so it must not reach the provider."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue