litellm/tests/integration/management/test_key_updates.py
Yuneng Jiang 3b0fbc426d
fix(tests): resolve the integration support package without run.py's PYTHONPATH
tests/integration/conftest.py imported the bare `integration` package. Because
tests/__init__.py and tests/integration/__init__.py both exist, pytest's default
prepend import mode puts only the repo root on sys.path, so that name resolved
only under the PYTHONPATH that tests/integration/run.py injects. Every other
invocation died at conftest import with
ModuleNotFoundError: No module named 'integration' and exit 4, including the
command test_oci_integration.py documents in its own docstring.

The imports now use the tests.integration._support path that pytest actually
resolves, matching the 120 other `from tests.` imports in the suite. run.py's
PYTHONPATH still works because it already puts the repo root on the path.

tests/code_coverage_tests/test_integration_suite_imports.py collects every file
under tests/integration with PYTHONPATH scrubbed and asserts a non-zero
collection count, so an unresolvable import fails the code-quality job instead
of only the developers who run these files by hand. CI runs the three
pre-existing files through the allowlist rather than executing them, which is
why nothing caught this.
2026-09-15 21:10:08 -07:00

40 lines
1.9 KiB
Python

from typing import Final
from hashlib import sha256
import pytest
from tests.integration._support.client import Gateway, object_value
from tests.integration._support.database import read_rows
@pytest.mark.covers("mgmt.key.update.preserves_independent_fields")
def test_update_preserves_independent_fields_and_serving(gateway: Gateway) -> None:
with gateway.scenario() as scenario:
model: Final = scenario.model()
key: Final = scenario.key(models=[model], key_alias="before", metadata={"retained": "value"})
gateway.chat(model, key=key)
gateway.post("/key/update", {"key": key, "key_alias": "after"})
info: Final = object_value(gateway.get("/key/info", {"key": key})["info"])
assert info["key_alias"] == "after"
assert info["models"] == [model]
assert object_value(info["metadata"])["retained"] == "value"
response: Final = gateway.chat(model, key=key)
assert object_value(response["usage"])["total_tokens"] == 40
replacement: Final = scenario.model()
gateway.post("/key/update", {"key": key, "models": [replacement]})
saved: Final = read_rows(
'SELECT key_alias, models, metadata FROM "LiteLLM_VerificationToken" WHERE token = %s',
(sha256(key.encode()).hexdigest(),),
)
assert len(saved) == 1
assert saved[0]["models"] == [replacement]
assert saved[0]["key_alias"] == "after"
denied: Final = gateway.request(
"POST",
"/v1/chat/completions",
{"model": model, "messages": [{"role": "user", "content": "old grant"}]},
key=key,
)
assert denied.status_code == 403, denied.text
assert object_value(object_value(denied.json())["error"])["type"] == "key_model_access_denied"
assert object_value(gateway.chat(replacement, key=key)["usage"])["total_tokens"] == 40