mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
* refactor(typing): narrow Any sources across providers, proxy and guardrails (wave 1) * refactor(typing): narrow Any sources across hubs, providers and proxy (wave 2) * refactor(typing): validate RunwayML task JSON and let dotprompt loaders accept exported prompts * fix: restore pass-through behavior on paths the typing pass tightened MCP client IP extraction trusts X-Forwarded-For only when use_x_forwarded_for is True, dotprompt skips a malformed JSON prompt entry instead of raising, RunwayML task payloads pass through unvalidated, and a malformed provider body raises the same AttributeError as before at the CompactifAI, Gemini, and mock transport parse sites * fix(runwayml): keep a malformed 2xx task body on the 500 path * fix(edenai,cli): keep a non-object body in the error and fail on a malformed migration report EdenAI's embedding and image generation parse dropped a non-object body from the error they raised, so the message no longer named what the provider sent. A helper now raises with the body in the message, and both paths have a regression test. `lite encryption migrate` exited 0 with no message when the proxy's report was not an object or its residual_legacy was not a number, where base crashed. It now fails with a message naming the value. The CompactifAI and Gemini image-edit non-object body tests accept TypeError or AttributeError, which both base and this branch raise, so they guard against a ValidationError instead of pinning one class. RunwayML's async parse gets the same non-object body test as the sync one. * fix: keep base's error classes, duck-typed checks and fail-closed guardrails the typing pass changed * test: annotate the new regression tests' locals with Final * test: annotate the remaining new test locals with Final
90 lines
3.4 KiB
Python
90 lines
3.4 KiB
Python
"""CLI tests for the ``lite encryption migrate`` command.
|
|
|
|
The HTTP client is mocked, so these assert the command's request routing (GET
|
|
check vs POST migrate, dry-run param) and its residual-state messaging without a
|
|
live proxy.
|
|
"""
|
|
|
|
from typing import Final
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from litellm.proxy.client.cli import main as cli_main
|
|
from litellm.proxy.client.cli.commands import encryption as enc_cli
|
|
|
|
|
|
class _FakeHTTPClient:
|
|
"""Stand-in for HTTPClient: records the last request and returns a canned body."""
|
|
|
|
last = None
|
|
response = {"status": "success", "report": {"residual_legacy": 0, "locations": {}}}
|
|
|
|
def __init__(self, base_url, api_key):
|
|
self.base_url = base_url
|
|
self.api_key = api_key
|
|
|
|
def request(self, method, path, **kwargs):
|
|
_FakeHTTPClient.last = {"method": method, "path": path, **kwargs}
|
|
return _FakeHTTPClient.response
|
|
|
|
|
|
@pytest.fixture
|
|
def runner(monkeypatch):
|
|
_FakeHTTPClient.last = None
|
|
_FakeHTTPClient.response = {
|
|
"status": "success",
|
|
"report": {"residual_legacy": 0, "locations": {}},
|
|
}
|
|
monkeypatch.setattr(enc_cli, "HTTPClient", _FakeHTTPClient)
|
|
return CliRunner()
|
|
|
|
|
|
def test_migrate_check_hits_check_route(runner):
|
|
result = runner.invoke(cli_main.cli, ["encryption", "migrate", "--check"])
|
|
assert result.exit_code == 0, result.output
|
|
assert _FakeHTTPClient.last["method"] == "GET"
|
|
assert _FakeHTTPClient.last["path"] == "/credentials/migrate-encryption/check"
|
|
assert "No legacy values remaining" in result.output
|
|
|
|
|
|
def test_migrate_default_posts_without_dry_run(runner):
|
|
result = runner.invoke(cli_main.cli, ["encryption", "migrate"])
|
|
assert result.exit_code == 0, result.output
|
|
assert _FakeHTTPClient.last["method"] == "POST"
|
|
assert _FakeHTTPClient.last["path"] == "/credentials/migrate-encryption"
|
|
assert _FakeHTTPClient.last["params"] is None
|
|
|
|
|
|
def test_migrate_dry_run_sets_param(runner):
|
|
result = runner.invoke(cli_main.cli, ["encryption", "migrate", "--dry-run"])
|
|
assert result.exit_code == 0, result.output
|
|
assert _FakeHTTPClient.last["method"] == "POST"
|
|
assert _FakeHTTPClient.last["params"] == {"dry_run": "true"}
|
|
|
|
|
|
def test_migrate_reports_residual_legacy(runner):
|
|
_FakeHTTPClient.response = {
|
|
"status": "success",
|
|
"report": {"residual_legacy": 3, "locations": {}},
|
|
}
|
|
result = runner.invoke(cli_main.cli, ["encryption", "migrate", "--check"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "Residual legacy values remaining: 3" in result.output
|
|
|
|
|
|
@pytest.mark.parametrize("residual", [-1, float("nan")])
|
|
def test_migrate_prints_no_residual_status_for_a_residual_below_zero_or_nan(runner, residual):
|
|
_FakeHTTPClient.response = {"status": "success", "report": {"residual_legacy": residual}}
|
|
result: Final = runner.invoke(cli_main.cli, ["encryption", "migrate", "--check"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "No legacy values remaining" not in result.output
|
|
assert "Residual legacy values remaining" not in result.output
|
|
|
|
|
|
@pytest.mark.parametrize("report", [None, "done", {"residual_legacy": "3"}])
|
|
def test_migrate_malformed_report_fails(runner, report):
|
|
_FakeHTTPClient.response = {"status": "success", "report": report}
|
|
result: Final = runner.invoke(cli_main.cli, ["encryption", "migrate", "--check"])
|
|
assert result.exit_code != 0
|
|
assert "Unexpected" in result.output
|