mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
test(integration): give each owned proxy a private database and Redis by default (#47213)
* test(integration): give each owned proxy a private database and Redis by default owned_proxy and owned_proxy_process now boot on a scratch database and a per-proxy redis-server unless the caller opts into the worker's state with state=OwnedState.SHARED_WITH_WORKER, passes its own DATABASE_URL, or strips DATABASE_URL. Under the replica profile the private database gets the writer and reader roles so the reader path stays covered. Gateway carries the database_url its proxy writes to, and the Scenario cleanup checks and delete_key_if_present read from it, so they keep checking something on a private database. The 189 test files with owned proxy sites that depend on the worker's database or Redis opt in with a shares_worker_state module marker, which a module-scoped conftest fixture turns into the shared state. The 7 helper modules with such sites opt in per call, since conftest fixtures and other test files reach them. * test(integration): reach session fixtures with the shared marker and keep the worker retry default The module-scoped fixture that applied shares_worker_state ran after session-scoped fixtures, so proxies those booted went private in marked files. The marker is now applied around each marked item's setup, call and teardown, which covers fixtures of every scope. Owned proxies on the worker database inherited the worker gateway's router_settings.num_retries of 0 from it. A private proxy now gets the same default in its config copy unless the config sets num_retries itself. Five providers files read the owned proxy's spend rows or response cache through the worker's database or Redis and opt into sharing.
This commit is contained in:
parent
3feaf3ab1b
commit
fc27dfdc7d
210 changed files with 676 additions and 144 deletions
|
|
@ -15,7 +15,7 @@ from pydantic import JsonValue
|
||||||
from litellm.proxy.utils import PrismaClient, hash_token
|
from litellm.proxy.utils import PrismaClient, hash_token
|
||||||
from tests.integration._support.client import Gateway, Scenario, string_value
|
from tests.integration._support.client import Gateway, Scenario, string_value
|
||||||
from tests.integration._support.prisma_clients import connected_proxy_prisma
|
from tests.integration._support.prisma_clients import connected_proxy_prisma
|
||||||
from tests.integration._support.process import owned_proxy
|
from tests.integration._support.process import OwnedState, owned_proxy
|
||||||
|
|
||||||
TEAM_ALPHA: Final = "behavior-pin-team-alpha"
|
TEAM_ALPHA: Final = "behavior-pin-team-alpha"
|
||||||
TEAM_BETA: Final = "behavior-pin-team-beta"
|
TEAM_BETA: Final = "behavior-pin-team-beta"
|
||||||
|
|
@ -124,7 +124,7 @@ def owned_settings_client(
|
||||||
config: Final = {**base, "litellm_settings": {**base["litellm_settings"], **litellm_settings}}
|
config: Final = {**base, "litellm_settings": {**base["litellm_settings"], **litellm_settings}}
|
||||||
path: Final = directory / "owned-settings.yaml"
|
path: Final = directory / "owned-settings.yaml"
|
||||||
path.write_text(yaml.safe_dump(config))
|
path.write_text(yaml.safe_dump(config))
|
||||||
with owned_proxy(client.gateway, directory, {}, config=path) as candidate:
|
with owned_proxy(client.gateway, directory, {}, config=path, state=OwnedState.SHARED_WITH_WORKER) as candidate:
|
||||||
yield GatewayClient(candidate, client.replacements)
|
yield GatewayClient(candidate, client.replacements)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from integration._support.aws_control_plane import (
|
||||||
verified_scope,
|
verified_scope,
|
||||||
)
|
)
|
||||||
from integration._support.client import JSON_OBJECT, Gateway, Scenario, eventually, held, object_value, string_value
|
from integration._support.client import JSON_OBJECT, Gateway, Scenario, eventually, held, object_value, string_value
|
||||||
from integration._support.process import OwnedProxy, owned_proxy_process
|
from integration._support.process import OwnedProxy, OwnedState, owned_proxy_process
|
||||||
from litellm.litellm_core_utils.aws_partition import get_aws_dns_suffix
|
from litellm.litellm_core_utils.aws_partition import get_aws_dns_suffix
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
|
@ -115,6 +115,7 @@ def discovery_proxy(
|
||||||
config=discovery_config(parent, directory, check_provider_endpoint=check_provider_endpoint),
|
config=discovery_config(parent, directory, check_provider_endpoint=check_provider_endpoint),
|
||||||
remove_environment=aws_environment_names(),
|
remove_environment=aws_environment_names(),
|
||||||
workers=workers,
|
workers=workers,
|
||||||
|
state=OwnedState.SHARED_WITH_WORKER,
|
||||||
) as owned:
|
) as owned:
|
||||||
wait_for_every_worker(owned.log, workers)
|
wait_for_every_worker(owned.log, workers)
|
||||||
yield owned
|
yield owned
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,9 @@ def list_value(value: JsonValue) -> Sequence[JsonValue]:
|
||||||
|
|
||||||
def delete_key_if_present(candidate: Gateway, key: str) -> None:
|
def delete_key_if_present(candidate: Gateway, key: str) -> None:
|
||||||
digest: Final = sha256(key.encode()).hexdigest()
|
digest: Final = sha256(key.encode()).hexdigest()
|
||||||
if read_rows('SELECT token FROM "LiteLLM_VerificationToken" WHERE token=%s', (digest,)):
|
if candidate.rows('SELECT token FROM "LiteLLM_VerificationToken" WHERE token=%s', (digest,)):
|
||||||
candidate.post("/key/delete", {"keys": [key]})
|
candidate.post("/key/delete", {"keys": [key]})
|
||||||
assert read_rows('SELECT token FROM "LiteLLM_VerificationToken" WHERE token=%s', (digest,)) == []
|
assert candidate.rows('SELECT token FROM "LiteLLM_VerificationToken" WHERE token=%s', (digest,)) == []
|
||||||
|
|
||||||
|
|
||||||
def eventually(
|
def eventually(
|
||||||
|
|
@ -95,6 +95,10 @@ class Gateway:
|
||||||
client: httpx.Client
|
client: httpx.Client
|
||||||
key: str
|
key: str
|
||||||
upstream_url: str
|
upstream_url: str
|
||||||
|
database_url: str | None = None
|
||||||
|
|
||||||
|
def rows(self, query: str, parameters: tuple[str, ...]) -> list[dict[str, JsonValue]]:
|
||||||
|
return read_rows(query, parameters, database_url=self.database_url)
|
||||||
|
|
||||||
def request(
|
def request(
|
||||||
self,
|
self,
|
||||||
|
|
@ -175,7 +179,7 @@ class Scenario:
|
||||||
|
|
||||||
def delete_team(self, identity: str) -> None:
|
def delete_team(self, identity: str) -> None:
|
||||||
self.gateway.post("/team/delete", {"team_ids": [identity]})
|
self.gateway.post("/team/delete", {"team_ids": [identity]})
|
||||||
assert read_rows('SELECT team_id FROM "LiteLLM_TeamTable" WHERE team_id = %s', (identity,)) == []
|
assert self.gateway.rows('SELECT team_id FROM "LiteLLM_TeamTable" WHERE team_id = %s', (identity,)) == []
|
||||||
|
|
||||||
def project(self, team_id: str, **fields: JsonValue) -> str:
|
def project(self, team_id: str, **fields: JsonValue) -> str:
|
||||||
created: Final = self.gateway.post(
|
created: Final = self.gateway.post(
|
||||||
|
|
@ -188,7 +192,9 @@ class Scenario:
|
||||||
def delete_project(self, identity: str) -> None:
|
def delete_project(self, identity: str) -> None:
|
||||||
response: Final = self.gateway.request("DELETE", "/project/delete", {"project_ids": [identity]})
|
response: Final = self.gateway.request("DELETE", "/project/delete", {"project_ids": [identity]})
|
||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
assert read_rows('SELECT project_id FROM "LiteLLM_ProjectTable" WHERE project_id = %s', (identity,)) == []
|
assert (
|
||||||
|
self.gateway.rows('SELECT project_id FROM "LiteLLM_ProjectTable" WHERE project_id = %s', (identity,)) == []
|
||||||
|
)
|
||||||
|
|
||||||
def budget(self, **fields: JsonValue) -> str:
|
def budget(self, **fields: JsonValue) -> str:
|
||||||
created: Final = self.gateway.post("/budget/new", fields)
|
created: Final = self.gateway.post("/budget/new", fields)
|
||||||
|
|
@ -198,7 +204,7 @@ class Scenario:
|
||||||
|
|
||||||
def delete_budget(self, identity: str) -> None:
|
def delete_budget(self, identity: str) -> None:
|
||||||
self.gateway.post("/budget/delete", {"id": identity})
|
self.gateway.post("/budget/delete", {"id": identity})
|
||||||
assert read_rows('SELECT budget_id FROM "LiteLLM_BudgetTable" WHERE budget_id = %s', (identity,)) == []
|
assert self.gateway.rows('SELECT budget_id FROM "LiteLLM_BudgetTable" WHERE budget_id = %s', (identity,)) == []
|
||||||
|
|
||||||
def organization(self, **fields: JsonValue) -> str:
|
def organization(self, **fields: JsonValue) -> str:
|
||||||
created: Final = self.gateway.post(
|
created: Final = self.gateway.post(
|
||||||
|
|
@ -212,7 +218,9 @@ class Scenario:
|
||||||
response: Final = self.gateway.request("DELETE", "/organization/delete", {"organization_ids": [identity]})
|
response: Final = self.gateway.request("DELETE", "/organization/delete", {"organization_ids": [identity]})
|
||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
assert (
|
assert (
|
||||||
read_rows('SELECT organization_id FROM "LiteLLM_OrganizationTable" WHERE organization_id = %s', (identity,))
|
self.gateway.rows(
|
||||||
|
'SELECT organization_id FROM "LiteLLM_OrganizationTable" WHERE organization_id = %s', (identity,)
|
||||||
|
)
|
||||||
== []
|
== []
|
||||||
)
|
)
|
||||||
self.delete_budget(budget_id)
|
self.delete_budget(budget_id)
|
||||||
|
|
@ -236,7 +244,7 @@ class Scenario:
|
||||||
def delete_user(self, identity: str) -> None:
|
def delete_user(self, identity: str) -> None:
|
||||||
response: Final = self.gateway.request("POST", "/user/delete", {"user_ids": [identity]})
|
response: Final = self.gateway.request("POST", "/user/delete", {"user_ids": [identity]})
|
||||||
assert response.status_code == 200 and response.json() == 1, response.text
|
assert response.status_code == 200 and response.json() == 1, response.text
|
||||||
assert read_rows('SELECT user_id FROM "LiteLLM_UserTable" WHERE user_id = %s', (identity,)) == []
|
assert self.gateway.rows('SELECT user_id FROM "LiteLLM_UserTable" WHERE user_id = %s', (identity,)) == []
|
||||||
|
|
||||||
def member(self, team_id: str, role: str = "user") -> str:
|
def member(self, team_id: str, role: str = "user") -> str:
|
||||||
"""Create an internal user and add them to ``team_id``; deleting the user later removes the membership."""
|
"""Create an internal user and add them to ``team_id``; deleting the user later removes the membership."""
|
||||||
|
|
@ -247,7 +255,7 @@ class Scenario:
|
||||||
def delete_key(self, token: str) -> None:
|
def delete_key(self, token: str) -> None:
|
||||||
self.gateway.post("/key/delete", {"keys": [token]})
|
self.gateway.post("/key/delete", {"keys": [token]})
|
||||||
hashed: Final = sha256(token.encode()).hexdigest()
|
hashed: Final = sha256(token.encode()).hexdigest()
|
||||||
assert read_rows('SELECT token FROM "LiteLLM_VerificationToken" WHERE token = %s', (hashed,)) == []
|
assert self.gateway.rows('SELECT token FROM "LiteLLM_VerificationToken" WHERE token = %s', (hashed,)) == []
|
||||||
info: Final = object_value(self.gateway.get("/key/info", {"key": hashed})["info"])
|
info: Final = object_value(self.gateway.get("/key/info", {"key": hashed})["info"])
|
||||||
assert info["status"] == "deleted", f"Deleted key still served as live: {info['status']}"
|
assert info["status"] == "deleted", f"Deleted key still served as live: {info['status']}"
|
||||||
|
|
||||||
|
|
@ -265,7 +273,9 @@ class Scenario:
|
||||||
lambda served: identity not in served,
|
lambda served: identity not in served,
|
||||||
seconds=2 * PROXY_CONFIG_RELOAD_INTERVAL_SECONDS,
|
seconds=2 * PROXY_CONFIG_RELOAD_INTERVAL_SECONDS,
|
||||||
)
|
)
|
||||||
assert read_rows('SELECT model_id FROM "LiteLLM_ProxyModelTable" WHERE model_id = %s', (identity,)) == []
|
assert (
|
||||||
|
self.gateway.rows('SELECT model_id FROM "LiteLLM_ProxyModelTable" WHERE model_id = %s', (identity,)) == []
|
||||||
|
)
|
||||||
|
|
||||||
def model(self, *, model_info: Mapping[str, JsonValue] | None = None, **parameters: JsonValue) -> str:
|
def model(self, *, model_info: Mapping[str, JsonValue] | None = None, **parameters: JsonValue) -> str:
|
||||||
name: Final = f"integration-{uuid.uuid4().hex}"
|
name: Final = f"integration-{uuid.uuid4().hex}"
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Generator, Iterable, Iterator, Mapping
|
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from contextlib import contextmanager, suppress
|
from contextlib import AbstractContextManager, contextmanager, suppress
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
|
|
@ -17,7 +17,7 @@ from ipaddress import IPv4Address
|
||||||
from itertools import accumulate
|
from itertools import accumulate
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
from typing import TYPE_CHECKING, Final
|
from typing import TYPE_CHECKING, Final, TypeAlias
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -33,6 +33,8 @@ from tests.integration._support.budget import import_seconds, readiness_for, rea
|
||||||
from tests.integration._support.client import GATEWAY_LIMITS, Gateway, gateway_from_environment
|
from tests.integration._support.client import GATEWAY_LIMITS, Gateway, gateway_from_environment
|
||||||
from tests.integration._support.database import scratch_database
|
from tests.integration._support.database import scratch_database
|
||||||
from tests.integration._support.ports import allocate_port
|
from tests.integration._support.ports import allocate_port
|
||||||
|
from tests.integration._support.redis_process import owned_redis
|
||||||
|
from tests.integration._support.workers import gateway_profile, replica_urls
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -43,6 +45,75 @@ LEGACY_MIGRATE_DEPLOY: Final = ("--use_legacy_migration_resolver",)
|
||||||
CONFIGURATION: Final = TypeAdapter(dict[str, object])
|
CONFIGURATION: Final = TypeAdapter(dict[str, object])
|
||||||
_DEPLOYMENT_IDS: Final = TypeAdapter(dict[str, list[str]])
|
_DEPLOYMENT_IDS: Final = TypeAdapter(dict[str, list[str]])
|
||||||
MCP_COVERAGE_CONFIG: Final = Path(__file__).resolve().parents[1] / "mcp_coverage.toml"
|
MCP_COVERAGE_CONFIG: Final = Path(__file__).resolve().parents[1] / "mcp_coverage.toml"
|
||||||
|
OWNED_STATE_VARIABLE: Final = "INTEGRATION_OWNED_STATE"
|
||||||
|
|
||||||
|
|
||||||
|
class OwnedState(Enum):
|
||||||
|
PRIVATE = "private"
|
||||||
|
SHARED_WITH_WORKER = "shared_with_worker"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class PrivateState:
|
||||||
|
database_url: str
|
||||||
|
environment: Mapping[str, str]
|
||||||
|
|
||||||
|
|
||||||
|
PrivateStateFactory: TypeAlias = Callable[[Path, Mapping[str, str]], AbstractContextManager[PrivateState]]
|
||||||
|
|
||||||
|
|
||||||
|
def boots_private_state(
|
||||||
|
state: OwnedState | None,
|
||||||
|
overrides: Mapping[str, str],
|
||||||
|
remove_environment: tuple[str, ...],
|
||||||
|
inherited: Mapping[str, str],
|
||||||
|
) -> bool:
|
||||||
|
chosen: Final = state or OwnedState(inherited.get(OWNED_STATE_VARIABLE, OwnedState.PRIVATE.value))
|
||||||
|
return chosen is OwnedState.PRIVATE and "DATABASE_URL" not in overrides and "DATABASE_URL" not in remove_environment
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def private_state(directory: Path, inherited: Mapping[str, str]) -> Iterator[PrivateState]:
|
||||||
|
with scratch_database("owned") as database, owned_redis(directory, inherited=inherited) as cache:
|
||||||
|
replica: Final = replica_urls(database, inherited) if gateway_profile(inherited).replica else None
|
||||||
|
yield PrivateState(
|
||||||
|
database,
|
||||||
|
MappingProxyType(
|
||||||
|
{
|
||||||
|
"DATABASE_URL": database if replica is None else replica["INTEGRATION_PROXY_DATABASE_URL"],
|
||||||
|
**(
|
||||||
|
{}
|
||||||
|
if replica is None
|
||||||
|
else {"DATABASE_URL_READ_REPLICA": replica["INTEGRATION_PROXY_READ_REPLICA_URL"]}
|
||||||
|
),
|
||||||
|
"REDIS_HOST": cache.host,
|
||||||
|
"REDIS_PORT": str(cache.port),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class OwnedEnvironment:
|
||||||
|
overrides: Mapping[str, str]
|
||||||
|
database_url: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def owned_environment(
|
||||||
|
directory: Path,
|
||||||
|
overrides: Mapping[str, str],
|
||||||
|
remove_environment: tuple[str, ...],
|
||||||
|
state: OwnedState | None,
|
||||||
|
inherited: Mapping[str, str],
|
||||||
|
private: PrivateStateFactory = private_state,
|
||||||
|
) -> Iterator[OwnedEnvironment]:
|
||||||
|
if not boots_private_state(state, overrides, remove_environment, inherited):
|
||||||
|
yield OwnedEnvironment(overrides, None)
|
||||||
|
return
|
||||||
|
with private(directory, inherited) as booted:
|
||||||
|
kept: Final = {name: value for name, value in booted.environment.items() if name not in remove_environment}
|
||||||
|
yield OwnedEnvironment(MappingProxyType({**kept, **overrides}), booted.database_url)
|
||||||
|
|
||||||
|
|
||||||
def proxy_database_environment(inherited: Mapping[str, str] = os.environ) -> Mapping[str, str]:
|
def proxy_database_environment(inherited: Mapping[str, str] = os.environ) -> Mapping[str, str]:
|
||||||
|
|
@ -368,6 +439,7 @@ def owned_proxy(
|
||||||
database_setup: tuple[str, ...] = DB_PUSH,
|
database_setup: tuple[str, ...] = DB_PUSH,
|
||||||
coverage_file: Path | None = None,
|
coverage_file: Path | None = None,
|
||||||
inherited: Mapping[str, str] = os.environ,
|
inherited: Mapping[str, str] = os.environ,
|
||||||
|
state: OwnedState | None = None,
|
||||||
) -> Iterator[Gateway]:
|
) -> Iterator[Gateway]:
|
||||||
with owned_proxy_process(
|
with owned_proxy_process(
|
||||||
gateway,
|
gateway,
|
||||||
|
|
@ -379,20 +451,21 @@ def owned_proxy(
|
||||||
database_setup=database_setup,
|
database_setup=database_setup,
|
||||||
coverage_file=coverage_file,
|
coverage_file=coverage_file,
|
||||||
inherited=inherited,
|
inherited=inherited,
|
||||||
|
state=state,
|
||||||
) as owned:
|
) as owned:
|
||||||
yield owned.gateway
|
yield owned.gateway
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def isolated_gateway(directory: Path, environment: "pytest.MonkeyPatch") -> Iterator[Gateway]:
|
def isolated_gateway(directory: Path, environment: "pytest.MonkeyPatch") -> Iterator[Gateway]:
|
||||||
with gateway_from_environment() as shared, scratch_database() as database:
|
with gateway_from_environment() as shared, owned_proxy(shared, directory, {}, state=OwnedState.PRIVATE) as owned:
|
||||||
environment.setenv("DATABASE_URL", database)
|
assert owned.database_url is not None
|
||||||
environment.setenv("INTEGRATION_PROXY_DATABASE_URL", database)
|
environment.setenv("DATABASE_URL", owned.database_url)
|
||||||
|
environment.setenv("INTEGRATION_PROXY_DATABASE_URL", owned.database_url)
|
||||||
environment.delenv("INTEGRATION_PROXY_READ_REPLICA_URL", raising=False)
|
environment.delenv("INTEGRATION_PROXY_READ_REPLICA_URL", raising=False)
|
||||||
environment.delenv("DATABASE_URL_READ_REPLICA", raising=False)
|
environment.delenv("DATABASE_URL_READ_REPLICA", raising=False)
|
||||||
with owned_proxy(shared, directory, {}) as owned:
|
owned.post("/config/update", {"router_settings": {"num_retries": 0}})
|
||||||
owned.post("/config/update", {"router_settings": {"num_retries": 0}})
|
yield owned
|
||||||
yield owned
|
|
||||||
|
|
||||||
|
|
||||||
def _stop(process: subprocess.Popen[bytes]) -> None:
|
def _stop(process: subprocess.Popen[bytes]) -> None:
|
||||||
|
|
@ -634,6 +707,32 @@ def _proxy_root() -> Path:
|
||||||
return Path(os.environ.get("INTEGRATION_PROXY_ROOT") or Path(__file__).resolve().parents[3])
|
return Path(os.environ.get("INTEGRATION_PROXY_ROOT") or Path(__file__).resolve().parents[3])
|
||||||
|
|
||||||
|
|
||||||
|
def _configuration_copy(config: Path, directory: Path, prefix: str) -> Path:
|
||||||
|
checkouts: Final = (_harness_root(), _proxy_root().resolve())
|
||||||
|
inside_checkout: Final = any(config.resolve().is_relative_to(checkout) for checkout in checkouts)
|
||||||
|
target: Final = directory if inside_checkout else config.parent
|
||||||
|
target.mkdir(parents=True, exist_ok=True)
|
||||||
|
return target / f"{prefix}-{uuid.uuid4().hex}.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
def private_configuration(config: Path, directory: Path) -> Path:
|
||||||
|
loaded: Final[object] = yaml.safe_load(config.read_text())
|
||||||
|
if not isinstance(loaded, dict):
|
||||||
|
return config
|
||||||
|
settings: Final = CONFIGURATION.validate_python(loaded)
|
||||||
|
router: Final = settings.get("router_settings") or {}
|
||||||
|
if not isinstance(router, dict) or "num_retries" in router:
|
||||||
|
return config
|
||||||
|
destination: Final = _configuration_copy(config, directory, "private-config")
|
||||||
|
destination.write_text(
|
||||||
|
yaml.safe_dump(
|
||||||
|
{**settings, "router_settings": {**CONFIGURATION.validate_python(router), "num_retries": 0}},
|
||||||
|
sort_keys=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return destination
|
||||||
|
|
||||||
|
|
||||||
def worker_configuration(config: Path, directory: Path, inherited: Mapping[str, str] = os.environ) -> Path:
|
def worker_configuration(config: Path, directory: Path, inherited: Mapping[str, str] = os.environ) -> Path:
|
||||||
if inherited.get("INTEGRATION_WORKER_ISOLATION") != "1":
|
if inherited.get("INTEGRATION_WORKER_ISOLATION") != "1":
|
||||||
return config
|
return config
|
||||||
|
|
@ -644,11 +743,7 @@ def worker_configuration(config: Path, directory: Path, inherited: Mapping[str,
|
||||||
return f"http://127.0.0.1:{inherited['INTEGRATION_PROVIDER_PORT']}"
|
return f"http://127.0.0.1:{inherited['INTEGRATION_PROVIDER_PORT']}"
|
||||||
|
|
||||||
contents: Final = re.sub(r"http://127[.]0[.]0[.]1:(8190|8191)(?![0-9])", endpoint, config.read_text())
|
contents: Final = re.sub(r"http://127[.]0[.]0[.]1:(8190|8191)(?![0-9])", endpoint, config.read_text())
|
||||||
checkouts: Final = (_harness_root(), _proxy_root().resolve())
|
destination: Final = _configuration_copy(config, directory, "worker-config")
|
||||||
inside_checkout: Final = any(config.resolve().is_relative_to(checkout) for checkout in checkouts)
|
|
||||||
target: Final = directory if inside_checkout else config.parent
|
|
||||||
target.mkdir(parents=True, exist_ok=True)
|
|
||||||
destination: Final = target / f"worker-config-{uuid.uuid4().hex}.yaml"
|
|
||||||
loaded: Final[object] = yaml.safe_load(contents)
|
loaded: Final[object] = yaml.safe_load(contents)
|
||||||
if not isinstance(loaded, dict):
|
if not isinstance(loaded, dict):
|
||||||
destination.write_text(contents)
|
destination.write_text(contents)
|
||||||
|
|
@ -753,42 +848,58 @@ def owned_proxy_process(
|
||||||
healthcheck: WorkerHealthcheck = WorkerHealthcheck.STRICT,
|
healthcheck: WorkerHealthcheck = WorkerHealthcheck.STRICT,
|
||||||
inherited: Mapping[str, str] = os.environ,
|
inherited: Mapping[str, str] = os.environ,
|
||||||
client_timeout: float = 15,
|
client_timeout: float = 15,
|
||||||
|
state: OwnedState | None = None,
|
||||||
) -> Iterator[OwnedProxy]:
|
) -> Iterator[OwnedProxy]:
|
||||||
assert "--timeout_worker_healthcheck" not in extra_arguments, "Choose the worker healthcheck with `healthcheck`"
|
assert "--timeout_worker_healthcheck" not in extra_arguments, "Choose the worker healthcheck with `healthcheck`"
|
||||||
root: Final = _proxy_root()
|
root: Final = _proxy_root()
|
||||||
environment: Final = _proxy_environment(
|
with owned_environment(directory, overrides, remove_environment, state, inherited) as owned:
|
||||||
gateway,
|
configured: Final = worker_configuration(
|
||||||
{**overrides, **({"COVERAGE_FILE": str(coverage_file)} if coverage_file else {})},
|
config or root / "tests/integration/proxy_config.yaml", directory, inherited
|
||||||
remove_environment,
|
)
|
||||||
inherited,
|
owned_configuration: Final = (
|
||||||
)
|
configured if owned.database_url is None else private_configuration(configured, directory)
|
||||||
output: Final = Path(inherited.get("INTEGRATION_RESULTS_DIR", str(directory)))
|
)
|
||||||
output.mkdir(parents=True, exist_ok=True)
|
environment: Final = _proxy_environment(
|
||||||
command: Final = (
|
gateway,
|
||||||
sys.executable,
|
{**owned.overrides, **({"COVERAGE_FILE": str(coverage_file)} if coverage_file else {})},
|
||||||
"-m",
|
remove_environment,
|
||||||
*(("coverage", "run", f"--rcfile={MCP_COVERAGE_CONFIG}", "-m") if coverage_file else ()),
|
inherited,
|
||||||
"integration._support.proxy",
|
)
|
||||||
"--config",
|
output: Final = Path(inherited.get("INTEGRATION_RESULTS_DIR", str(directory)))
|
||||||
str(worker_configuration(config or root / "tests/integration/proxy_config.yaml", directory, inherited)),
|
output.mkdir(parents=True, exist_ok=True)
|
||||||
"--host",
|
command: Final = (
|
||||||
"127.0.0.1",
|
sys.executable,
|
||||||
"--num_workers",
|
"-m",
|
||||||
str(workers),
|
*(("coverage", "run", f"--rcfile={MCP_COVERAGE_CONFIG}", "-m") if coverage_file else ()),
|
||||||
"--timeout_worker_healthcheck",
|
"integration._support.proxy",
|
||||||
str(healthcheck_seconds(healthcheck)),
|
"--config",
|
||||||
*database_setup,
|
str(owned_configuration),
|
||||||
*extra_arguments,
|
"--host",
|
||||||
)
|
"127.0.0.1",
|
||||||
launch: Final = _launch_until_ready(command, root, environment, output, workers)
|
"--num_workers",
|
||||||
process: Final = launch.process
|
str(workers),
|
||||||
try:
|
"--timeout_worker_healthcheck",
|
||||||
with httpx.Client(
|
str(healthcheck_seconds(healthcheck)),
|
||||||
base_url=f"http://127.0.0.1:{launch.port}", timeout=client_timeout, trust_env=False, limits=GATEWAY_LIMITS
|
*database_setup,
|
||||||
) as client:
|
*extra_arguments,
|
||||||
yield OwnedProxy(Gateway(client, gateway.key, gateway.upstream_url), process, launch.log, healthcheck)
|
)
|
||||||
finally:
|
launch: Final = _launch_until_ready(command, root, environment, output, workers)
|
||||||
_stop_launch(launch)
|
process: Final = launch.process
|
||||||
|
try:
|
||||||
|
with httpx.Client(
|
||||||
|
base_url=f"http://127.0.0.1:{launch.port}",
|
||||||
|
timeout=client_timeout,
|
||||||
|
trust_env=False,
|
||||||
|
limits=GATEWAY_LIMITS,
|
||||||
|
) as client:
|
||||||
|
yield OwnedProxy(
|
||||||
|
Gateway(client, gateway.key, gateway.upstream_url, owned.database_url),
|
||||||
|
process,
|
||||||
|
launch.log,
|
||||||
|
healthcheck,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
_stop_launch(launch)
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import httpx
|
||||||
import yaml
|
import yaml
|
||||||
from integration._support.client import Gateway, Scenario, eventually, gateway_from_environment, object_value
|
from integration._support.client import Gateway, Scenario, eventually, gateway_from_environment, object_value
|
||||||
from integration._support.database import read_rows
|
from integration._support.database import read_rows
|
||||||
from integration._support.process import OwnedProxy, owned_proxy_process
|
from integration._support.process import OwnedProxy, OwnedState, owned_proxy_process
|
||||||
from integration._support.responses_vendor import ResponsesVendor, same_response
|
from integration._support.responses_vendor import ResponsesVendor, same_response
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from prometheus_client.mmap_dict import MmapedDict
|
from prometheus_client.mmap_dict import MmapedDict
|
||||||
|
|
@ -207,6 +207,7 @@ def series_cap_rig(
|
||||||
config=config,
|
config=config,
|
||||||
remove_environment=() if shared_samples else ("PROMETHEUS_MULTIPROC_DIR",),
|
remove_environment=() if shared_samples else ("PROMETHEUS_MULTIPROC_DIR",),
|
||||||
workers=workers,
|
workers=workers,
|
||||||
|
state=OwnedState.SHARED_WITH_WORKER,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
scenario: Final = stack.enter_context(owned.gateway.scenario())
|
scenario: Final = stack.enter_context(owned.gateway.scenario())
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ from integration._support.client import (
|
||||||
string_value,
|
string_value,
|
||||||
)
|
)
|
||||||
from integration._support.database import read_rows
|
from integration._support.database import read_rows
|
||||||
from integration._support.process import OwnedProxy, owned_proxy_process
|
from integration._support.process import OwnedProxy, OwnedState, owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
|
@ -313,6 +313,7 @@ def safeguards_rig(
|
||||||
config=path,
|
config=path,
|
||||||
remove_environment=remove_environment,
|
remove_environment=remove_environment,
|
||||||
workers=workers,
|
workers=workers,
|
||||||
|
state=OwnedState.SHARED_WITH_WORKER,
|
||||||
) as owned:
|
) as owned:
|
||||||
yield Rig(wire, scripts, owned)
|
yield Rig(wire, scripts, owned)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,9 @@ def worker_endpoints() -> Mapping[str, str]:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _replica_urls(database: str) -> Mapping[str, str]:
|
def replica_urls(database: str, inherited: Mapping[str, str] = os.environ) -> Mapping[str, str]:
|
||||||
writer: Final = urlsplit(os.environ["INTEGRATION_PROXY_DATABASE_URL"])
|
writer: Final = urlsplit(inherited["INTEGRATION_PROXY_DATABASE_URL"])
|
||||||
reader: Final = urlsplit(os.environ["INTEGRATION_PROXY_READ_REPLICA_URL"])
|
reader: Final = urlsplit(inherited["INTEGRATION_PROXY_READ_REPLICA_URL"])
|
||||||
database_path: Final = urlsplit(database).path
|
database_path: Final = urlsplit(database).path
|
||||||
assert writer.username is not None and reader.username is not None
|
assert writer.username is not None and reader.username is not None
|
||||||
with psycopg.connect(database, autocommit=True) as admin:
|
with psycopg.connect(database, autocommit=True) as admin:
|
||||||
|
|
@ -164,7 +164,7 @@ def worker_services(
|
||||||
"INTEGRATION_PROXY_READ_REPLICA_URL": "",
|
"INTEGRATION_PROXY_READ_REPLICA_URL": "",
|
||||||
"REDIS_HOST": cache.host,
|
"REDIS_HOST": cache.host,
|
||||||
"REDIS_PORT": str(cache.port),
|
"REDIS_PORT": str(cache.port),
|
||||||
**(_replica_urls(database) if replica else {}),
|
**(replica_urls(database, inherited) if replica else {}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import BaseModel, TypeAdapter
|
from pydantic import BaseModel, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_INPUT_RATE: Final = 0.000001
|
_INPUT_RATE: Final = 0.000001
|
||||||
_OUTPUT_RATE: Final = 0.00001
|
_OUTPUT_RATE: Final = 0.00001
|
||||||
_MAX_BUDGET: Final = 0.0001
|
_MAX_BUDGET: Final = 0.0001
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ from integration.authorization._hidden_alias_budget import (
|
||||||
)
|
)
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
PAID_INPUT_COST_PER_TOKEN: Final = 0.001
|
PAID_INPUT_COST_PER_TOKEN: Final = 0.001
|
||||||
PAID_OUTPUT_COST_PER_TOKEN: Final = 0.002
|
PAID_OUTPUT_COST_PER_TOKEN: Final = 0.002
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ from integration._support.process import owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
|
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_CLIENT_ID: Final = "scripted-cli-client"
|
_CLIENT_ID: Final = "scripted-cli-client"
|
||||||
_CLIENT_SECRET: Final = "scripted-cli-secret"
|
_CLIENT_SECRET: Final = "scripted-cli-secret"
|
||||||
_AUTHORIZATION_CODE: Final = "scripted-auth-code"
|
_AUTHORIZATION_CODE: Final = "scripted-auth-code"
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ from tests.integration._support.process import OwnedProxy, group_members, owned_
|
||||||
from tests.integration._support.provider import SharedProvider
|
from tests.integration._support.provider import SharedProvider
|
||||||
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(300)
|
pytestmark: Final = [pytest.mark.timeout(300), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
CLI_SOURCE: Final = "litellm-cli"
|
CLI_SOURCE: Final = "litellm-cli"
|
||||||
CLI_STATE_PREFIX: Final = "litellm-session-token"
|
CLI_STATE_PREFIX: Final = "litellm-session-token"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ from integration._support.database import read_rows
|
||||||
from integration._support.process import owned_proxy
|
from integration._support.process import owned_proxy
|
||||||
from pydantic import BaseModel, JsonValue
|
from pydantic import BaseModel, JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
class _BlockedCustomerInfo(BaseModel):
|
class _BlockedCustomerInfo(BaseModel):
|
||||||
user_id: str
|
user_id: str
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ from integration.authorization._hidden_alias_budget import (
|
||||||
)
|
)
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
# $0 comes from litellm's own cost-map row (model_prices_and_context_window.json, added 2025-10-14 in 900a57f5b1f):
|
# $0 comes from litellm's own cost-map row (model_prices_and_context_window.json, added 2025-10-14 in 900a57f5b1f):
|
||||||
# Lemonade is a local inference server, so there is no vendor price for the row to drift from. A row that gains a
|
# Lemonade is a local inference server, so there is no vendor price for the row to drift from. A row that gains a
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ from integration.authorization._hidden_alias_budget import (
|
||||||
upstream_requests,
|
upstream_requests,
|
||||||
)
|
)
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
_STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
|
_STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
|
||||||
_WAVE: Final = 8
|
_WAVE: Final = 8
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from tests.integration._support.wire import Reply, Request, wire_server
|
||||||
KEY_ID: Final = "integration-jwt-map-existing-key"
|
KEY_ID: Final = "integration-jwt-map-existing-key"
|
||||||
MAPPING_INSERT: Final = b'INSERT INTO "public"."LiteLLM_JWTKeyMapping"'
|
MAPPING_INSERT: Final = b'INSERT INTO "public"."LiteLLM_JWTKeyMapping"'
|
||||||
|
|
||||||
pytestmark = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
|
|
||||||
def _hash(key: str) -> str:
|
def _hash(key: str) -> str:
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from tests.integration._support.database import read_rows
|
||||||
from tests.integration._support.process import owned_proxy
|
from tests.integration._support.process import owned_proxy
|
||||||
from tests.integration._support.wire import Reply, Request, wire_server
|
from tests.integration._support.wire import Reply, Request, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
KEY_ID: Final = "integration-jwt-signing-key"
|
KEY_ID: Final = "integration-jwt-signing-key"
|
||||||
TEAM_BUDGET: Final = 25.0
|
TEAM_BUDGET: Final = 25.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ _KEY_ID: Final = "integration-signing-key"
|
||||||
_UPSTREAM_KEY: Final = "synthetic-openai-key"
|
_UPSTREAM_KEY: Final = "synthetic-openai-key"
|
||||||
_CHANNEL: Final = "litellm_proxy.auth_cache_invalidation"
|
_CHANNEL: Final = "litellm_proxy.auth_cache_invalidation"
|
||||||
_CHAT_BODY: Final = TypeAdapter(dict[str, JsonValue])
|
_CHAT_BODY: Final = TypeAdapter(dict[str, JsonValue])
|
||||||
pytestmark: Final = pytest.mark.timeout(180)
|
pytestmark: Final = [pytest.mark.timeout(180), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
|
|
||||||
class _Usage(BaseModel):
|
class _Usage(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
from jwt.algorithms import RSAAlgorithm
|
from jwt.algorithms import RSAAlgorithm
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
AUDIENCE: Final = "litellm-integration"
|
AUDIENCE: Final = "litellm-integration"
|
||||||
KEY_ID: Final = "integration-signing-key"
|
KEY_ID: Final = "integration-signing-key"
|
||||||
CLIENT_CLAIM: Final = "client_id"
|
CLIENT_CLAIM: Final = "client_id"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from integration._support.client import Gateway, Scenario, object_value, string_value
|
from integration._support.client import Gateway, Scenario, object_value, string_value
|
||||||
|
|
@ -16,6 +17,8 @@ from integration.authorization._guardrail_opt_out import (
|
||||||
stored_metadata,
|
stored_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_KEY_ROUTES: Final = ["/key/generate", "/key/update", "/key/regenerate", "/v1/chat/completions"]
|
_KEY_ROUTES: Final = ["/key/generate", "/key/update", "/key/regenerate", "/v1/chat/completions"]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from queue import SimpleQueue
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import pytest
|
||||||
|
|
||||||
from integration._support.client import Gateway, eventually, string_value
|
from integration._support.client import Gateway, eventually, string_value
|
||||||
from integration._support.database import read_rows
|
from integration._support.database import read_rows
|
||||||
|
|
@ -24,6 +25,8 @@ from integration.authorization._guardrail_opt_out import (
|
||||||
upstream_observations,
|
upstream_observations,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
class _GuardrailSink:
|
class _GuardrailSink:
|
||||||
"""Test-owned guardrail endpoint that can be stopped and restarted on the same port."""
|
"""Test-owned guardrail endpoint that can be stopped and restarted on the same port."""
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import pytest
|
||||||
from anthropic import Anthropic
|
from anthropic import Anthropic
|
||||||
from openai import AsyncOpenAI, OpenAI
|
from openai import AsyncOpenAI, OpenAI
|
||||||
|
|
||||||
|
|
@ -20,6 +21,8 @@ from integration.authorization._guardrail_opt_out import (
|
||||||
upstream_hits,
|
upstream_hits,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
def _wire_hits(wire: Wire, marker: str) -> int:
|
def _wire_hits(wire: Wire, marker: str) -> int:
|
||||||
return sum(1 for request in wire.drain() if marker.encode() in request.body)
|
return sum(1 for request in wire.drain() if marker.encode() in request.body)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ from openai.types.chat import ChatCompletion
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
CONFIG_STORE_ID: Final = "vs_integration_config_store"
|
CONFIG_STORE_ID: Final = "vs_integration_config_store"
|
||||||
PROXY_CONFIG: Final = Path(__file__).resolve().parents[1] / "proxy_config.yaml"
|
PROXY_CONFIG: Final = Path(__file__).resolve().parents[1] / "proxy_config.yaml"
|
||||||
REMOVE_OPENAI_API_BASE: Final = ("OPENAI_API_BASE",)
|
REMOVE_OPENAI_API_BASE: Final = ("OPENAI_API_BASE",)
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ AWS_AMBIENT_CREDENTIALS: Final = (
|
||||||
"AWS_WEB_IDENTITY_TOKEN_FILE",
|
"AWS_WEB_IDENTITY_TOKEN_FILE",
|
||||||
)
|
)
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(OWNED_PROXY_CELL_SECONDS)
|
pytestmark: Final = [pytest.mark.timeout(OWNED_PROXY_CELL_SECONDS), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
Signature = tuple[str, str, str, str | None]
|
Signature = tuple[str, str, str, str | None]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ from integration.authorization._responses_compact import (
|
||||||
)
|
)
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_CLIENTS: Final = ("sync", "async", "httpx")
|
_CLIENTS: Final = ("sync", "async", "httpx")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ from integration.authorization._responses_compact import (
|
||||||
vendor_id,
|
vendor_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_Kind = Literal["owner", "other_user", "other_team", "unmanaged"]
|
_Kind = Literal["owner", "other_user", "other_team", "unmanaged"]
|
||||||
_KINDS: Final[tuple[_Kind, ...]] = ("owner", "other_user", "other_team", "unmanaged")
|
_KINDS: Final[tuple[_Kind, ...]] = ("owner", "other_user", "other_team", "unmanaged")
|
||||||
_REFUSING: Final[tuple[_Kind, ...]] = ("other_user", "other_team", "unmanaged")
|
_REFUSING: Final[tuple[_Kind, ...]] = ("other_user", "other_team", "unmanaged")
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ from integration._support.worker_readiness import wait_for_worker_readiness
|
||||||
from openai.types.responses import ResponseInputParam
|
from openai.types.responses import ResponseInputParam
|
||||||
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
|
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_MODEL: Final = "gpt-5"
|
_MODEL: Final = "gpt-5"
|
||||||
_API_KEY: Final = "synthetic-responses-key"
|
_API_KEY: Final = "synthetic-responses-key"
|
||||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ from tests.integration._support.database_relay import dropped_connection_relay
|
||||||
from tests.integration._support.process import OwnedProxy, owned_proxy_process
|
from tests.integration._support.process import OwnedProxy, owned_proxy_process
|
||||||
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(300)
|
pytestmark: Final = [pytest.mark.timeout(300), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
CLIENT_ID: Final = "integration-entra-client"
|
CLIENT_ID: Final = "integration-entra-client"
|
||||||
CLIENT_SECRET: Final = "integration-entra-secret"
|
CLIENT_SECRET: Final = "integration-entra-secret"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from pydantic import JsonValue
|
||||||
|
|
||||||
from tests.integration.caching.response_cache_case import await_entry, redis_store
|
from tests.integration.caching.response_cache_case import await_entry, redis_store
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(2 * graceful_stop_seconds() + 120)
|
pytestmark: Final = [pytest.mark.timeout(2 * graceful_stop_seconds() + 120), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
_POLICY: Final = "integration-tool-policy"
|
_POLICY: Final = "integration-tool-policy"
|
||||||
_BLOCK: Final = "tool-permission-block"
|
_BLOCK: Final = "tool-permission-block"
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ from pydantic import JsonValue, TypeAdapter
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
from redis.exceptions import ResponseError
|
from redis.exceptions import ResponseError
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_Turns = Callable[[str], tuple[list[JsonValue], list[JsonValue]]]
|
_Turns = Callable[[str], tuple[list[JsonValue], list[JsonValue]]]
|
||||||
_INDEXES: Final = TypeAdapter(tuple[bytes, ...])
|
_INDEXES: Final = TypeAdapter(tuple[bytes, ...])
|
||||||
_COUNT: Final = TypeAdapter(tuple[int])
|
_COUNT: Final = TypeAdapter(tuple[int])
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ from tests.integration.caching.response_cache_case import (
|
||||||
slowed,
|
slowed,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
RecordProperty = Callable[[str, object], None]
|
RecordProperty = Callable[[str, object], None]
|
||||||
|
|
||||||
WORKERS: Final = 2
|
WORKERS: Final = 2
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ def owned_gateway(
|
||||||
gateway_from_environment() as gateway,
|
gateway_from_environment() as gateway,
|
||||||
owned_proxy_process(gateway, directory, environment or {}, config=path, workers=workers) as owned,
|
owned_proxy_process(gateway, directory, environment or {}, config=path, workers=workers) as owned,
|
||||||
):
|
):
|
||||||
yield Gateway(owned.gateway.client, owned.gateway.key, gateway.upstream_url)
|
yield owned.gateway
|
||||||
|
|
||||||
|
|
||||||
def stream_finished(endpoint: str, lines: Sequence[str]) -> bool:
|
def stream_finished(endpoint: str, lines: Sequence[str]) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ from tests.integration._support.database import read_rows
|
||||||
from tests.integration._support.process import owned_proxy
|
from tests.integration._support.process import owned_proxy
|
||||||
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
CLIENT_VERSION: Final = "0.159.3"
|
CLIENT_VERSION: Final = "0.159.3"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ from tests.integration.cost_calculation.cost_tracking_case import (
|
||||||
SseResponse,
|
SseResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_Route = tuple[str, str, tuple[str, ...], dict[str, JsonValue], str]
|
_Route = tuple[str, str, tuple[str, ...], dict[str, JsonValue], str]
|
||||||
_EVAL_DATA_SOURCE_CONFIG: Final[dict[str, JsonValue]] = {
|
_EVAL_DATA_SOURCE_CONFIG: Final[dict[str, JsonValue]] = {
|
||||||
"type": "custom",
|
"type": "custom",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ from pydantic import JsonValue
|
||||||
|
|
||||||
from tests.integration.cost_calculation.cost_tracking_case import JsonResponse, RoutedResponse, SseResponse
|
from tests.integration.cost_calculation.cost_tracking_case import JsonResponse, RoutedResponse, SseResponse
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_Kind: TypeAlias = Literal["chat", "chat_stream", "messages", "responses", "completions", "moderations"]
|
_Kind: TypeAlias = Literal["chat", "chat_stream", "messages", "responses", "completions", "moderations"]
|
||||||
|
|
||||||
_ALL_KINDS: Final[tuple[_Kind, ...]] = ("chat", "chat_stream", "messages", "responses", "completions", "moderations")
|
_ALL_KINDS: Final[tuple[_Kind, ...]] = ("chat", "chat_stream", "messages", "responses", "completions", "moderations")
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ from integration._support.database import read_rows
|
||||||
from integration._support.mcp import call_tool, mcp_peer, register_mcp, tool_names
|
from integration._support.mcp import call_tool, mcp_peer, register_mcp, tool_names
|
||||||
from integration._support.process import owned_proxy
|
from integration._support.process import owned_proxy
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.covers("other.compatibility.mcp.persisted_tool_names_survive_candidate_startup")
|
@pytest.mark.covers("other.compatibility.mcp.persisted_tool_names_survive_candidate_startup")
|
||||||
def test_existing_toolset_format_loads_before_start_and_keeps_sibling_denied(gateway: Gateway, tmp_path: Path) -> None:
|
def test_existing_toolset_format_loads_before_start_and_keeps_sibling_denied(gateway: Gateway, tmp_path: Path) -> None:
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ def router_default_proxy(tmp_path_factory: pytest.TempPathFactory) -> Iterator[G
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
with owned_proxy_process(gateway, directory, {}, config=config) as owned:
|
with owned_proxy_process(gateway, directory, {}, config=config) as owned:
|
||||||
yield Gateway(owned.gateway.client, owned.gateway.key, gateway.upstream_url)
|
yield owned.gateway
|
||||||
|
|
||||||
|
|
||||||
def test_ocr_multipart_without_file_returns_400(gateway: Gateway) -> None:
|
def test_ocr_multipart_without_file_returns_400(gateway: Gateway) -> None:
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ from pydantic import JsonValue
|
||||||
|
|
||||||
from tests.integration.cost_calculation.cost_tracking_case import StoredResponse
|
from tests.integration.cost_calculation.cost_tracking_case import StoredResponse
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(480)
|
pytestmark: Final = [pytest.mark.timeout(480), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
_STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
|
_STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
|
||||||
_MARKER_HEADER: Final = "x-litellm-spend-logs-metadata"
|
_MARKER_HEADER: Final = "x-litellm-spend-logs-metadata"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ from pydantic import JsonValue
|
||||||
from tests.integration._support.client import JSON_OBJECT, Gateway, Scenario, eventually, object_value, string_value
|
from tests.integration._support.client import JSON_OBJECT, Gateway, Scenario, eventually, object_value, string_value
|
||||||
from tests.integration._support.process import owned_proxy, owned_proxy_process
|
from tests.integration._support.process import owned_proxy, owned_proxy_process
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
CLIENT_VERSION: Final = "0.159.3"
|
CLIENT_VERSION: Final = "0.159.3"
|
||||||
FRESH_CONNECTION: Final = {"Connection": "close"}
|
FRESH_CONNECTION: Final = {"Connection": "close"}
|
||||||
CONSISTENT_READS: Final = 8
|
CONSISTENT_READS: Final = 8
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ from tests.integration._support.database import read_rows, scratch_database
|
||||||
from tests.integration._support.process import owned_proxy
|
from tests.integration._support.process import owned_proxy
|
||||||
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections.abc import AsyncIterator, Generator, Iterator, Mapping, Sequence
|
from collections.abc import AsyncIterator, Generator, Iterator, Mapping, Sequence
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack, contextmanager
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
@ -23,6 +23,7 @@ from tests.integration._support.client import Gateway, Scenario, eventually, gat
|
||||||
from tests.integration._support.generation import LIFECYCLE_SETTINGS
|
from tests.integration._support.generation import LIFECYCLE_SETTINGS
|
||||||
from tests.integration._support.manifest import OWNED_DIRECTORIES
|
from tests.integration._support.manifest import OWNED_DIRECTORIES
|
||||||
from tests.integration._support.ordering import collected_case, fixture_groups, scheduling_groups
|
from tests.integration._support.ordering import collected_case, fixture_groups, scheduling_groups
|
||||||
|
from tests.integration._support.process import OWNED_STATE_VARIABLE, OwnedState
|
||||||
from tests.integration._support.provider import SharedProvider, shared_provider
|
from tests.integration._support.provider import SharedProvider, shared_provider
|
||||||
from tests.integration._support.routing import RoutingPlugin
|
from tests.integration._support.routing import RoutingPlugin
|
||||||
from tests.integration._support.sdk_models import build_sdk_models
|
from tests.integration._support.sdk_models import build_sdk_models
|
||||||
|
|
@ -69,6 +70,7 @@ def pytest_addoption(parser: pytest.Parser) -> None:
|
||||||
def pytest_configure(config: pytest.Config) -> None:
|
def pytest_configure(config: pytest.Config) -> None:
|
||||||
config.addinivalue_line("markers", "integration: owned real-service integration contracts")
|
config.addinivalue_line("markers", "integration: owned real-service integration contracts")
|
||||||
config.addinivalue_line("markers", "covers(*ids): legacy contract IDs kept for existing tests, not enforced")
|
config.addinivalue_line("markers", "covers(*ids): legacy contract IDs kept for existing tests, not enforced")
|
||||||
|
config.addinivalue_line("markers", "shares_worker_state: owned proxies here share the worker DB and Redis")
|
||||||
config.stash[REPORTS] = []
|
config.stash[REPORTS] = []
|
||||||
config.stash[SHARD_FILES] = _shard_files(SHARD_PATH.validate_python(config.getoption("integration_shard_files")))
|
config.stash[SHARD_FILES] = _shard_files(SHARD_PATH.validate_python(config.getoption("integration_shard_files")))
|
||||||
config.pluginmanager.register(IntegrationReportPlugin(config))
|
config.pluginmanager.register(IntegrationReportPlugin(config))
|
||||||
|
|
@ -295,6 +297,32 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
|
||||||
session.exitstatus = pytest.ExitCode.OK
|
session.exitstatus = pytest.ExitCode.OK
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _owned_state(item: pytest.Item) -> Iterator[None]:
|
||||||
|
with pytest.MonkeyPatch.context() as environment:
|
||||||
|
if item.get_closest_marker("shares_worker_state") is not None:
|
||||||
|
environment.setenv(OWNED_STATE_VARIABLE, OwnedState.SHARED_WITH_WORKER.value)
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(wrapper=True)
|
||||||
|
def pytest_runtest_setup(item: pytest.Item) -> Generator[None, None, None]:
|
||||||
|
with _owned_state(item):
|
||||||
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(wrapper=True)
|
||||||
|
def pytest_runtest_call(item: pytest.Item) -> Generator[None, None, None]:
|
||||||
|
with _owned_state(item):
|
||||||
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(wrapper=True)
|
||||||
|
def pytest_runtest_teardown(item: pytest.Item, nextitem: pytest.Item | None) -> Generator[None, None, None]:
|
||||||
|
with _owned_state(item):
|
||||||
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def gateway() -> Iterator[Gateway]:
|
def gateway() -> Iterator[Gateway]:
|
||||||
with gateway_from_environment() as value:
|
with gateway_from_environment() as value:
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from integration._support.process import OwnedProxy, owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
_MODEL: Final = "cc-subscription-sol"
|
_MODEL: Final = "cc-subscription-sol"
|
||||||
_INPUT_TOKENS: Final = 1000
|
_INPUT_TOKENS: Final = 1000
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from integration._support.workers import cost_map_environment
|
||||||
from integration.cost_calculation.cost_tracking_case import COST_MAP
|
from integration.cost_calculation.cost_tracking_case import COST_MAP
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||||
_MESSAGES: Final = TypeAdapter(list[dict[str, JsonValue]])
|
_MESSAGES: Final = TypeAdapter(list[dict[str, JsonValue]])
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from integration._support.responses_ws_peer import PROVIDER_MODEL, USAGE, create
|
||||||
from integration._support.tls import write_self_signed_cert
|
from integration._support.tls import write_self_signed_cert
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(2 * graceful_stop_seconds() + 120)
|
pytestmark: Final = [pytest.mark.timeout(2 * graceful_stop_seconds() + 120), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
_INPUT_COST_PER_TOKEN: Final = 0.002
|
_INPUT_COST_PER_TOKEN: Final = 0.002
|
||||||
_OUTPUT_COST_PER_TOKEN: Final = 0.004
|
_OUTPUT_COST_PER_TOKEN: Final = 0.004
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ from integration._support.wire import Reply, Request, wire_server
|
||||||
from psycopg import sql
|
from psycopg import sql
|
||||||
from pydantic import BaseModel, JsonValue, TypeAdapter
|
from pydantic import BaseModel, JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.covers("other.database.regeneration.writer_updates_dependent_grants")
|
@pytest.mark.covers("other.database.regeneration.writer_updates_dependent_grants")
|
||||||
def test_key_regeneration_uses_writer_with_a_real_readonly_reader(gateway: Gateway, tmp_path: Path) -> None:
|
def test_key_regeneration_uses_writer_with_a_real_readonly_reader(gateway: Gateway, tmp_path: Path) -> None:
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ from integration.management._batch_file_caps import (
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
|
||||||
pytestmark = pytest.mark.timeout(600)
|
pytestmark: Final = [pytest.mark.timeout(600), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
YAML_RECORDS: Final = 3
|
YAML_RECORDS: Final = 3
|
||||||
YAML_UPLOADS: Final = 4
|
YAML_UPLOADS: Final = 4
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ from integration.management._batch_file_caps import (
|
||||||
)
|
)
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
pytestmark = pytest.mark.timeout(600)
|
pytestmark: Final = [pytest.mark.timeout(600), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
WORKERS: Final = 2
|
WORKERS: Final = 2
|
||||||
UPLOAD_CAP: Final = 5
|
UPLOAD_CAP: Final = 5
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ from integration.cost_calculation.cost_tracking_case import JsonResponse, SseRes
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
DAY: Final = 86400
|
DAY: Final = 86400
|
||||||
HOUR: Final = 3600
|
HOUR: Final = 3600
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ from tests.integration._support.client import Gateway, string_value
|
||||||
from tests.integration._support.database import read_rows
|
from tests.integration._support.database import read_rows
|
||||||
from tests.integration._support.process import owned_proxy
|
from tests.integration._support.process import owned_proxy
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
def _persisted_reset_at(budget_id: str) -> datetime:
|
def _persisted_reset_at(budget_id: str) -> datetime:
|
||||||
rows: Final = read_rows(
|
rows: Final = read_rows(
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from tests.integration._support.client import JSON_OBJECT, Gateway, Scenario, ev
|
||||||
from tests.integration._support.database import read_rows, write_rows
|
from tests.integration._support.database import read_rows, write_rows
|
||||||
from tests.integration._support.process import OwnedProxy, owned_proxy_process
|
from tests.integration._support.process import OwnedProxy, owned_proxy_process
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
PREFERENCES: Final = ("available", "auto_install", "required")
|
PREFERENCES: Final = ("available", "auto_install", "required")
|
||||||
INVALID_PREFERENCES: Final[tuple[JsonValue, ...]] = (
|
INVALID_PREFERENCES: Final[tuple[JsonValue, ...]] = (
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ from tests.integration._support.database import read_rows
|
||||||
from tests.integration._support.process import OwnedProxy, graceful_stop_seconds, owned_proxy_process
|
from tests.integration._support.process import OwnedProxy, graceful_stop_seconds, owned_proxy_process
|
||||||
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
from tests.integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ from tests.integration._support.process import (
|
||||||
)
|
)
|
||||||
from tests.integration._support.redis_process import owned_redis
|
from tests.integration._support.redis_process import owned_redis
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(300)
|
pytestmark: Final = [pytest.mark.timeout(300), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
PROVIDER_KEY: Final = "integration-provider-key"
|
PROVIDER_KEY: Final = "integration-provider-key"
|
||||||
EVICTION_WARNING: Final = "config cache eviction of router_settings failed"
|
EVICTION_WARNING: Final = "config cache eviction of router_settings failed"
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ from pydantic import JsonValue
|
||||||
from litellm.models.user import LiteLLM_UserTable
|
from litellm.models.user import LiteLLM_UserTable
|
||||||
from litellm.proxy.auth.auth_checks import ExperimentalUIJWTToken
|
from litellm.proxy.auth.auth_checks import ExperimentalUIJWTToken
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_OWNED_PROXY_SALT_KEY: Final = "sk-integration-salt"
|
_OWNED_PROXY_SALT_KEY: Final = "sk-integration-salt"
|
||||||
_OWNERSHIP_POOL_LIMIT: Final = 4
|
_OWNERSHIP_POOL_LIMIT: Final = 4
|
||||||
_CONNECTION_BUDGET: Final = re.compile(
|
_CONNECTION_BUDGET: Final = re.compile(
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from integration._support.process import OwnedProxy, graceful_stop_seconds, owne
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(4 * graceful_stop_seconds() + 240)
|
pytestmark: Final = [pytest.mark.timeout(4 * graceful_stop_seconds() + 240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
_RELOAD_SECONDS: Final = 5
|
_RELOAD_SECONDS: Final = 5
|
||||||
_PROPAGATION_SECONDS: Final = 6 * _RELOAD_SECONDS
|
_PROPAGATION_SECONDS: Final = 6 * _RELOAD_SECONDS
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ from integration._support.database import read_rows
|
||||||
from integration._support.process import owned_proxy_process
|
from integration._support.process import owned_proxy_process
|
||||||
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
|
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
SCIM_HEADERS: Final = {"Content-Type": "application/scim+json"}
|
SCIM_HEADERS: Final = {"Content-Type": "application/scim+json"}
|
||||||
SCIM_CORE_USER_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:User"
|
SCIM_CORE_USER_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:User"
|
||||||
SCIM_CORE_GROUP_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:Group"
|
SCIM_CORE_GROUP_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:Group"
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ from integration._support.process import OwnedProxy, graceful_stop_seconds, owne
|
||||||
from psycopg import sql
|
from psycopg import sql
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
GROUP_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:Group"
|
GROUP_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:Group"
|
||||||
PATCH_OP_SCHEMA: Final = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
|
PATCH_OP_SCHEMA: Final = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
|
||||||
SMALL_PUSH: Final = 5
|
SMALL_PUSH: Final = 5
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ from integration._support.process import owned_proxy_process
|
||||||
from integration.authorization._guardrail_opt_out import upstream_hits
|
from integration.authorization._guardrail_opt_out import upstream_hits
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
PATCH_OP_SCHEMA: Final = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
|
PATCH_OP_SCHEMA: Final = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
|
||||||
GROUP_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:Group"
|
GROUP_SCHEMA: Final = "urn:ietf:params:scim:schemas:core:2.0:Group"
|
||||||
BURST_TEAMS: Final = 10
|
BURST_TEAMS: Final = 10
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ from types import MappingProxyType
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
import pytest
|
||||||
from integration._support.client import Gateway, string_value
|
from integration._support.client import Gateway, string_value
|
||||||
from integration._support.database import read_rows
|
from integration._support.database import read_rows
|
||||||
from integration._support.process import owned_proxy
|
from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_PROXY_CONFIG: Final = Path(__file__).resolve().parents[1] / "proxy_config.yaml"
|
_PROXY_CONFIG: Final = Path(__file__).resolve().parents[1] / "proxy_config.yaml"
|
||||||
_ORIGINAL_KEY: Final = "exa-original-provider-key"
|
_ORIGINAL_KEY: Final = "exa-original-provider-key"
|
||||||
_ROTATED_KEY: Final = "exa-rotated-provider-key"
|
_ROTATED_KEY: Final = "exa-rotated-provider-key"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from tests.integration._support.database import read_rows
|
||||||
from tests.integration._support.process import owned_proxy
|
from tests.integration._support.process import owned_proxy
|
||||||
from tests.integration._support.wire import Reply, Request, wire_server
|
from tests.integration._support.wire import Reply, Request, wire_server
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.timeout(300)
|
pytestmark: Final = [pytest.mark.timeout(300), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
CLIENT_ID: Final = "integration-sso-client"
|
CLIENT_ID: Final = "integration-sso-client"
|
||||||
CLIENT_SECRET: Final = "integration-sso-secret"
|
CLIENT_SECRET: Final = "integration-sso-secret"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from tests.integration._support.client import Gateway, string_value
|
||||||
from tests.integration._support.database import read_rows
|
from tests.integration._support.database import read_rows
|
||||||
from tests.integration._support.process import owned_proxy
|
from tests.integration._support.process import owned_proxy
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
def _budget_row(team_id: str) -> dict[str, JsonValue]:
|
def _budget_row(team_id: str) -> dict[str, JsonValue]:
|
||||||
rows: Final = read_rows(
|
rows: Final = read_rows(
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ from tests.integration._support.client import Gateway, Scenario, eventually, obj
|
||||||
from tests.integration._support.database import read_rows
|
from tests.integration._support.database import read_rows
|
||||||
from tests.integration._support.process import graceful_stop_seconds, owned_proxy_process
|
from tests.integration._support.process import graceful_stop_seconds, owned_proxy_process
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
METRIC: Final = "litellm_team_members_metric"
|
METRIC: Final = "litellm_team_members_metric"
|
||||||
METRICS_ROUTE: Final = "/metrics/"
|
METRICS_ROUTE: Final = "/metrics/"
|
||||||
MEMBERS: Final = 3
|
MEMBERS: Final = 3
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ from pydantic import BaseModel, JsonValue
|
||||||
|
|
||||||
from litellm.repositories.chunked_in import IN_LIST_CHUNK_SIZE
|
from litellm.repositories.chunked_in import IN_LIST_CHUNK_SIZE
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
AUDIENCE: Final = "litellm-integration"
|
AUDIENCE: Final = "litellm-integration"
|
||||||
KEY_ID: Final = "integration-signing-key"
|
KEY_ID: Final = "integration-signing-key"
|
||||||
CLIENT_CLAIM: Final = "client_id"
|
CLIENT_CLAIM: Final = "client_id"
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ from integration._support.ports import allocate_port
|
||||||
from openai import APIStatusError, AsyncOpenAI, OpenAI
|
from openai import APIStatusError, AsyncOpenAI, OpenAI
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
MANAGED_PREFIX: Final = "litellm_proxy:"
|
MANAGED_PREFIX: Final = "litellm_proxy:"
|
||||||
CARRIED_PROVIDER_FILE_ID: Final = re.compile(r"(?:^|;)llm_output_file_id,([^;]+)")
|
CARRIED_PROVIDER_FILE_ID: Final = re.compile(r"(?:^|;)llm_output_file_id,([^;]+)")
|
||||||
UPLOAD_FILENAME: Final = re.compile(rb'filename="([^"]+)"')
|
UPLOAD_FILENAME: Final = re.compile(rb'filename="([^"]+)"')
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
DEFAULT_COST: Final = 0.25
|
DEFAULT_COST: Final = 0.25
|
||||||
ADD_COST: Final = 0.5
|
ADD_COST: Final = 0.5
|
||||||
FORBIDDEN: Final = "forbidden-integration-word"
|
FORBIDDEN: Final = "forbidden-integration-word"
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ from integration._support.mcp import (
|
||||||
from integration._support.process import owned_proxy_process
|
from integration._support.process import owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
TENANT: Final = "00000000-0000-4000-8000-0000000a3650"
|
TENANT: Final = "00000000-0000-4000-8000-0000000a3650"
|
||||||
REJECTED: Final = "Agent 365 guardrail rejected the tool call"
|
REJECTED: Final = "Agent 365 guardrail rejected the tool call"
|
||||||
GUARDRAIL_ROWS: Final = (
|
GUARDRAIL_ROWS: Final = (
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ from integration._support.mcp import EntryPoint, McpCaller, McpPeer, echo_tool,
|
||||||
from integration._support.process import owned_proxy_process
|
from integration._support.process import owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
AKTO_KEY: Final = "synthetic-akto-mcp-log-key"
|
AKTO_KEY: Final = "synthetic-akto-mcp-log-key"
|
||||||
BLOCK_MARK: Final = "SYNTHETIC-AKTO-BLOCK"
|
BLOCK_MARK: Final = "SYNTHETIC-AKTO-BLOCK"
|
||||||
RESPONSE_CHECK: Final = {"akto_connector": "litellm", "response_guardrails": "true", "ingest_data": "true"}
|
RESPONSE_CHECK: Final = {"akto_connector": "litellm", "response_guardrails": "true", "ingest_data": "true"}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ from openai.types.responses.tool_param import Mcp
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
|
||||||
pytestmark: Final = pytest.mark.xdist_group("mcp_llm_private_gateway")
|
pytestmark: Final = [pytest.mark.xdist_group("mcp_llm_private_gateway"), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ from pydantic import BaseModel, JsonValue
|
||||||
from litellm.models.user import LiteLLM_UserTable
|
from litellm.models.user import LiteLLM_UserTable
|
||||||
from litellm.proxy.auth.auth_checks import ExperimentalUIJWTToken
|
from litellm.proxy.auth.auth_checks import ExperimentalUIJWTToken
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
ADD: Final = {"a": 4, "b": 5}
|
ADD: Final = {"a": 4, "b": 5}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
ADD: Final = {"a": 2, "b": 3}
|
ADD: Final = {"a": 2, "b": 3}
|
||||||
CLIENT_REDIRECT: Final = "http://127.0.0.1:9/cb"
|
CLIENT_REDIRECT: Final = "http://127.0.0.1:9/cb"
|
||||||
ACCEPT: Final = {"Accept": "application/json, text/event-stream"}
|
ACCEPT: Final = {"Accept": "application/json, text/event-stream"}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ from integration._support.client import Gateway
|
||||||
from integration._support.mcp import McpPeer, call_tool, register_mcp, tool_names
|
from integration._support.mcp import McpPeer, call_tool, register_mcp, tool_names
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.covers("other.mcp.errors.protocol_and_malformed_results_cannot_be_empty_success")
|
@pytest.mark.covers("other.mcp.errors.protocol_and_malformed_results_cannot_be_empty_success")
|
||||||
def test_jsonrpc_error_and_malformed_tool_result_remain_errors(gateway: Gateway) -> None:
|
def test_jsonrpc_error_and_malformed_tool_result_remain_errors(gateway: Gateway) -> None:
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ from integration._support.process import owned_proxy_process
|
||||||
from integration._support.wire import Reply
|
from integration._support.wire import Reply
|
||||||
from pydantic import BaseModel, TypeAdapter
|
from pydantic import BaseModel, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_ECHO: Final = "catalog-echo:"
|
_ECHO: Final = "catalog-echo:"
|
||||||
_PROBE: Final = "catalog-probe"
|
_PROBE: Final = "catalog-probe"
|
||||||
_DESCRIPTION: Final = "Look up one record"
|
_DESCRIPTION: Final = "Look up one record"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from integration._support.mcp import McpPeer, call_tool, mcp_peer, register_mcp,
|
||||||
from integration._support.process import owned_proxy_process
|
from integration._support.process import owned_proxy_process
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
TOKEN: Final = "USER_TOKEN"
|
TOKEN: Final = "USER_TOKEN"
|
||||||
WORKSPACE: Final = "WORKSPACE"
|
WORKSPACE: Final = "WORKSPACE"
|
||||||
METHODS: Final = ("GET", "POST", "DELETE")
|
METHODS: Final = ("GET", "POST", "DELETE")
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_Upstream = Callable[[Request], Reply]
|
_Upstream = Callable[[Request], Reply]
|
||||||
CLIENT_REDIRECT: Final = "http://127.0.0.1:9/cb"
|
CLIENT_REDIRECT: Final = "http://127.0.0.1:9/cb"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall
|
||||||
from openai.types.responses import FunctionToolParam, ResponseFunctionToolCall, ResponseOutputItemDoneEvent
|
from openai.types.responses import FunctionToolParam, ResponseFunctionToolCall, ResponseOutputItemDoneEvent
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_BACKEND: Final = "mistral-multi-tool"
|
_BACKEND: Final = "mistral-multi-tool"
|
||||||
_PROVIDER_MODEL: Final = f"mistral/{_BACKEND}"
|
_PROVIDER_MODEL: Final = f"mistral/{_BACKEND}"
|
||||||
_PROVIDER_KEY: Final = "integration-provider-key"
|
_PROVIDER_KEY: Final = "integration-provider-key"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from integration._support.process import owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_BACKEND: Final = "claude-sonnet-4-6"
|
_BACKEND: Final = "claude-sonnet-4-6"
|
||||||
_API_KEY: Final = "synthetic-anthropic-key"
|
_API_KEY: Final = "synthetic-anthropic-key"
|
||||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ _STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
|
||||||
_OUTAGE_STATUSES: Final = (529, 429, 503)
|
_OUTAGE_STATUSES: Final = (529, 429, 503)
|
||||||
_ROUTING_ENCODED_ID: Final = re.compile(r"resp_([A-Za-z0-9+/]+=*)")
|
_ROUTING_ENCODED_ID: Final = re.compile(r"resp_([A-Za-z0-9+/]+=*)")
|
||||||
|
|
||||||
pytestmark = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
Endpoint = Literal["messages", "chat", "responses"]
|
Endpoint = Literal["messages", "chat", "responses"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ from integration._support.vertex import service_account_json
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
BETA: Final = "dangerous-tool-use-2026-09-03"
|
BETA: Final = "dangerous-tool-use-2026-09-03"
|
||||||
SAFEGUARDS: Final[list[JsonValue]] = [
|
SAFEGUARDS: Final[list[JsonValue]] = [
|
||||||
{"type": "dangerous_tool_use", "classifier_context": {"v": 1, "permission_mode": "auto"}}
|
{"type": "dangerous_tool_use", "classifier_context": {"v": 1, "permission_mode": "auto"}}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,15 @@ from hashlib import sha256
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from integration._support.client import Gateway, JsonValue, eventually
|
from integration._support.client import Gateway, JsonValue, eventually
|
||||||
from integration._support.database import read_rows
|
from integration._support.database import read_rows
|
||||||
from integration._support.process import owned_proxy
|
from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_MODEL: Final = "claude-sonnet-4-5-20250929"
|
_MODEL: Final = "claude-sonnet-4-5-20250929"
|
||||||
_KEY: Final = "synthetic-anthropic-key"
|
_KEY: Final = "synthetic-anthropic-key"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ from integration._support.wire import Reply, Request, wire_server
|
||||||
from integration.providers._cache_control_marks_support import anthropic_peer, owned_config
|
from integration.providers._cache_control_marks_support import anthropic_peer, owned_config
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
|
_STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
|
||||||
_MODEL: Final = "burst-claude"
|
_MODEL: Final = "burst-claude"
|
||||||
_PROVIDER_KEY: Final = "synthetic-provider-key"
|
_PROVIDER_KEY: Final = "synthetic-provider-key"
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,8 @@ import httpx
|
||||||
import pytest
|
import pytest
|
||||||
from integration._support.client import Gateway, eventually
|
from integration._support.client import Gateway, eventually
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_QUERY: Final = "integration capped search"
|
_QUERY: Final = "integration capped search"
|
||||||
_TEXT_BLOCK: Final = {"type": "text", "text": "searching once more"}
|
_TEXT_BLOCK: Final = {"type": "text", "text": "searching once more"}
|
||||||
_NOT_INTERCEPTED: Final = "native tool reached the provider"
|
_NOT_INTERCEPTED: Final = "native tool reached the provider"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue, TypeAdapter
|
from pydantic import JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
CLAUDE_MODEL: Final = "caller-tools-claude"
|
CLAUDE_MODEL: Final = "caller-tools-claude"
|
||||||
OPENAI_MODEL: Final = "caller-tools-openai"
|
OPENAI_MODEL: Final = "caller-tools-openai"
|
||||||
GEMINI_MODEL: Final = "caller-tools-gemini"
|
GEMINI_MODEL: Final = "caller-tools-gemini"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import json
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Callable, Iterator, Mapping, Sequence
|
from collections.abc import Callable, Iterator, Mapping, Sequence
|
||||||
from contextlib import AbstractContextManager, ExitStack, contextmanager, nullcontext
|
from contextlib import AbstractContextManager, ExitStack, contextmanager, nullcontext
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, replace
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
@ -854,11 +854,7 @@ def _rig(
|
||||||
if fresh_client_connections
|
if fresh_client_connections
|
||||||
else owned.gateway.client
|
else owned.gateway.client
|
||||||
)
|
)
|
||||||
proxy: Final = Gateway(
|
proxy: Final = replace(owned.gateway, client=proxy_client)
|
||||||
client=proxy_client,
|
|
||||||
key=owned.gateway.key,
|
|
||||||
upstream_url=owned.gateway.upstream_url,
|
|
||||||
)
|
|
||||||
scenario: Final = resources.enter_context(proxy.scenario())
|
scenario: Final = resources.enter_context(proxy.scenario())
|
||||||
model: Final = scenario.model(
|
model: Final = scenario.model(
|
||||||
model=model_name,
|
model=model_name,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from typing import Final
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from integration._support.client import Gateway, object_value
|
from integration._support.client import Gateway, object_value
|
||||||
from integration._support.process import OwnedProxy, owned_proxy_process
|
from integration._support.process import OwnedProxy, OwnedState, owned_proxy_process
|
||||||
from integration._support.redis_process import OwnedRedis, owned_redis
|
from integration._support.redis_process import OwnedRedis, owned_redis
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
@ -528,6 +528,7 @@ def dispatch_proxy(
|
||||||
},
|
},
|
||||||
config=config,
|
config=config,
|
||||||
workers=workers,
|
workers=workers,
|
||||||
|
state=OwnedState.SHARED_WITH_WORKER,
|
||||||
) as owned:
|
) as owned:
|
||||||
yield owned
|
yield owned
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ from integration._support.database import read_rows
|
||||||
from integration._support.process import owned_proxy_process
|
from integration._support.process import owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
LOG_KEY: Final = "synthetic-akto-log-key"
|
LOG_KEY: Final = "synthetic-akto-log-key"
|
||||||
INPUT_KEY: Final = "synthetic-akto-input-key"
|
INPUT_KEY: Final = "synthetic-akto-input-key"
|
||||||
OUTPUT_KEY: Final = "synthetic-akto-output-key"
|
OUTPUT_KEY: Final = "synthetic-akto-output-key"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ from integration._support.process import OwnedProxy, owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_ATTACK_MARKER: Final = "synthetic-attack-marker"
|
_ATTACK_MARKER: Final = "synthetic-attack-marker"
|
||||||
_MODERATION_MARKER: Final = "synthetic-moderation-marker"
|
_MODERATION_MARKER: Final = "synthetic-moderation-marker"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_ATTACK_MARKER: Final = "synthetic-attack-marker"
|
_ATTACK_MARKER: Final = "synthetic-attack-marker"
|
||||||
|
|
||||||
_AZURE_TARGET_PREFIX: Final = "/contentsafety/text:shieldPrompt?api-version="
|
_AZURE_TARGET_PREFIX: Final = "/contentsafety/text:shieldPrompt?api-version="
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from _azure_storage_support import (
|
from _azure_storage_support import (
|
||||||
SINK_HOSTS,
|
SINK_HOSTS,
|
||||||
RecordingDataLakeSink,
|
RecordingDataLakeSink,
|
||||||
|
|
@ -18,6 +20,8 @@ from integration._support.process import owned_proxy
|
||||||
from integration._support.tls import server_context, write_self_signed_cert
|
from integration._support.tls import server_context, write_self_signed_cert
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
WORKERS: Final = 2
|
WORKERS: Final = 2
|
||||||
FLUSH_SECONDS: Final = "1"
|
FLUSH_SECONDS: Final = "1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ from integration._support.process import owned_proxy_process
|
||||||
from integration._support.upstream import aws_event_stream_frame
|
from integration._support.upstream import aws_event_stream_frame
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
MODEL_ID: Final = "anthropic.claude-sonnet-5-v1:0"
|
MODEL_ID: Final = "anthropic.claude-sonnet-5-v1:0"
|
||||||
ROUTER_MODEL: Final = "converse-relay"
|
ROUTER_MODEL: Final = "converse-relay"
|
||||||
EVENT_STREAM: Final = "application/vnd.amazon.eventstream"
|
EVENT_STREAM: Final = "application/vnd.amazon.eventstream"
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ from integration._support.process import owned_proxy_process
|
||||||
from integration._support.upstream import aws_event_stream_frame
|
from integration._support.upstream import aws_event_stream_frame
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
CLAUDE_ID: Final = "anthropic.claude-sonnet-5-v1:0"
|
CLAUDE_ID: Final = "anthropic.claude-sonnet-5-v1:0"
|
||||||
LLAMA_ID: Final = "meta.llama4-maverick-17b-instruct-v1:0"
|
LLAMA_ID: Final = "meta.llama4-maverick-17b-instruct-v1:0"
|
||||||
PROFILE_ID: Final = "arn:aws:bedrock:us-east-1:000000000000:application-inference-profile/relayprofile"
|
PROFILE_ID: Final = "arn:aws:bedrock:us-east-1:000000000000:application-inference-profile/relayprofile"
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ from integration._support.process import OwnedProxy, owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from prometheus_client.parser import text_string_to_metric_families
|
from prometheus_client.parser import text_string_to_metric_families
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
GUARDRAIL_PATH: Final = "/beta/litellm_basic_guardrail_api"
|
GUARDRAIL_PATH: Final = "/beta/litellm_basic_guardrail_api"
|
||||||
DEPLOYMENT_FAILURE: Final = "litellm_deployment_failure_responses_total"
|
DEPLOYMENT_FAILURE: Final = "litellm_deployment_failure_responses_total"
|
||||||
DEPLOYMENT_REQUESTS: Final = "litellm_deployment_total_requests_total"
|
DEPLOYMENT_REQUESTS: Final = "litellm_deployment_total_requests_total"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import pytest
|
||||||
from integration._support.client import Gateway, eventually, object_value, string_value
|
from integration._support.client import Gateway, eventually, object_value, string_value
|
||||||
from integration._support.process import owned_proxy_process
|
from integration._support.process import owned_proxy_process
|
||||||
from integration._support.redis_process import owned_redis
|
from integration._support.redis_process import owned_redis
|
||||||
|
|
@ -24,6 +25,8 @@ from test_cache_hit_guardrail_metrics import (
|
||||||
_rig,
|
_rig,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
BURST: Final = 10
|
BURST: Final = 10
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from integration._support.database import read_rows
|
||||||
from integration._support.process import graceful_stop_seconds, owned_proxy
|
from integration._support.process import graceful_stop_seconds, owned_proxy
|
||||||
from integration._support.wire import Reply, Request, wire_server
|
from integration._support.wire import Reply, Request, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.covers(
|
@pytest.mark.covers(
|
||||||
"other.observability.callbacks.credentials_stay_out_of_event_bodies",
|
"other.observability.callbacks.credentials_stay_out_of_event_bodies",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ from integration._support.otlp_sink import AuditConfigWriter, SpanSinks, recorde
|
||||||
from integration._support.process import owned_proxy_process
|
from integration._support.process import owned_proxy_process
|
||||||
from integration._support.wire import Reply, Request, Wire, wire_server
|
from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
AWS_KEY: Final = "AKIA" + "SYNTHETIC0000000"
|
AWS_KEY: Final = "AKIA" + "SYNTHETIC0000000"
|
||||||
GITHUB_TOKEN: Final = "ghp_" + "a1" * 18
|
GITHUB_TOKEN: Final = "ghp_" + "a1" * 18
|
||||||
FORBIDDEN_WORD: Final = "synthetic-forbidden-word"
|
FORBIDDEN_WORD: Final = "synthetic-forbidden-word"
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ INSTRUCTIONS: Final = "Reply in French. redaction-instructions-canary"
|
||||||
BRIDGED: Final = "bridged-redaction-canary"
|
BRIDGED: Final = "bridged-redaction-canary"
|
||||||
USAGE_DEPLOYMENT: Final = f"redaction-usage-{uuid.uuid4().hex}"
|
USAGE_DEPLOYMENT: Final = f"redaction-usage-{uuid.uuid4().hex}"
|
||||||
REDACT: Final = {"x-litellm-enable-message-redaction": "true"}
|
REDACT: Final = {"x-litellm-enable-message-redaction": "true"}
|
||||||
pytestmark = pytest.mark.timeout(240)
|
pytestmark: Final = [pytest.mark.timeout(240), pytest.mark.shares_worker_state]
|
||||||
|
|
||||||
|
|
||||||
def _provider(request: Request) -> Reply:
|
def _provider(request: Request) -> Reply:
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ from openai import APIError, AsyncOpenAI, OpenAI
|
||||||
from openai.types.chat import ChatCompletionChunk
|
from openai.types.chat import ChatCompletionChunk
|
||||||
from pydantic import BaseModel, JsonValue
|
from pydantic import BaseModel, JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.covers("other.observability.guardrails.rewrite_reaches_correct_anthropic_positions")
|
@pytest.mark.covers("other.observability.guardrails.rewrite_reaches_correct_anthropic_positions")
|
||||||
def test_guardrail_rewrites_system_and_user_in_actual_anthropic_request(gateway: Gateway, tmp_path: Path) -> None:
|
def test_guardrail_rewrites_system_and_user_in_actual_anthropic_request(gateway: Gateway, tmp_path: Path) -> None:
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ from pydantic import TypeAdapter
|
||||||
|
|
||||||
import litellm
|
import litellm
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||||
EndpointScope: TypeAlias = Literal["streaming", "non_streaming"]
|
EndpointScope: TypeAlias = Literal["streaming", "non_streaming"]
|
||||||
ModelKind: TypeAlias = Literal["router", "direct"]
|
ModelKind: TypeAlias = Literal["router", "direct"]
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ from integration._support.wire import Reply, Request, Wire, wire_server
|
||||||
from integration._support.ports import allocate_port
|
from integration._support.ports import allocate_port
|
||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
ChaosEndpoint: TypeAlias = Literal["chat", "messages", "responses"]
|
ChaosEndpoint: TypeAlias = Literal["chat", "messages", "responses"]
|
||||||
CHAOS_ENDPOINTS: Final[tuple[ChaosEndpoint, ...]] = ("chat", "messages", "responses")
|
CHAOS_ENDPOINTS: Final[tuple[ChaosEndpoint, ...]] = ("chat", "messages", "responses")
|
||||||
CHAOS_MODELS: Final = MappingProxyType(
|
CHAOS_MODELS: Final = MappingProxyType(
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ from integration.cost_calculation.cost_tracking_case import RealtimeResponse
|
||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
from websockets.asyncio.server import ServerConnection, serve
|
from websockets.asyncio.server import ServerConnection, serve
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
Endpoint: TypeAlias = Literal["chat", "messages", "responses"]
|
Endpoint: TypeAlias = Literal["chat", "messages", "responses"]
|
||||||
Mode: TypeAlias = Literal["pre_call", "during_call", "post_call", "logging_only"]
|
Mode: TypeAlias = Literal["pre_call", "during_call", "post_call", "logging_only"]
|
||||||
Scope: TypeAlias = Literal["streaming", "non_streaming"]
|
Scope: TypeAlias = Literal["streaming", "non_streaming"]
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ from opentelemetry.proto.common.v1.common_pb2 import KeyValue
|
||||||
from opentelemetry.proto.trace.v1.trace_pb2 import Span
|
from opentelemetry.proto.trace.v1.trace_pb2 import Span
|
||||||
from pydantic import BaseModel, JsonValue, TypeAdapter
|
from pydantic import BaseModel, JsonValue, TypeAdapter
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
PUBLIC_KEY: Final = "pk-lf-integration"
|
PUBLIC_KEY: Final = "pk-lf-integration"
|
||||||
SECRET_KEY: Final = "sk-lf-integration"
|
SECRET_KEY: Final = "sk-lf-integration"
|
||||||
PROJECTS_PATH: Final = "/api/public/projects"
|
PROJECTS_PATH: Final = "/api/public/projects"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import uuid
|
||||||
from collections.abc import Callable, Iterator, Sequence
|
from collections.abc import Callable, Iterator, Sequence
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field, replace
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from queue import SimpleQueue
|
from queue import SimpleQueue
|
||||||
|
|
@ -659,7 +659,7 @@ def test_langtrace_survives_a_killed_worker(gateway: Gateway, tmp_path: Path) ->
|
||||||
limits=httpx.Limits(max_keepalive_connections=0),
|
limits=httpx.Limits(max_keepalive_connections=0),
|
||||||
) as fresh_connections,
|
) as fresh_connections,
|
||||||
):
|
):
|
||||||
proxy: Final = Gateway(fresh_connections, owned.gateway.key, owned.gateway.upstream_url)
|
proxy: Final = replace(owned.gateway, client=fresh_connections)
|
||||||
with proxy.scenario() as scenario:
|
with proxy.scenario() as scenario:
|
||||||
rig: Final = _Rig(proxy, scenario.model(api_base=provider.url + "/v1"), provider, _Sink(sink, key))
|
rig: Final = _Rig(proxy, scenario.model(api_base=provider.url + "/v1"), provider, _Sink(sink, key))
|
||||||
_assert_kill_and_recovery(owned, rig)
|
_assert_kill_and_recovery(owned, rig)
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ from integration._support.ports import allocate_port
|
||||||
from openai import APIConnectionError as OpenAIAPIConnectionError
|
from openai import APIConnectionError as OpenAIAPIConnectionError
|
||||||
from pydantic import JsonValue
|
from pydantic import JsonValue
|
||||||
|
|
||||||
|
pytestmark: Final = pytest.mark.shares_worker_state
|
||||||
|
|
||||||
_EDGE_DELAY_SECONDS: Final = 5
|
_EDGE_DELAY_SECONDS: Final = 5
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue