mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
* refactor(proxy): extract shared spend log read policy * test(proxy): use named bindings for spend scope regression * test(proxy): reuse existing spend log query harness * test(proxy): cover spend log permission lookup adoption * chore(proxy): relocate existing spend query baseline * refactor(proxy): make scope query returns explicit * refactor(proxy): inject deferred log permission lookup * test(proxy): cover teamless management compatibility lookup * refactor(proxy): compose user and team log grants * refactor(proxy): share generic authorization composition * refactor(proxy): compose trace read permissions * refactor(proxy): centralize spend and trace authorization * refactor(proxy): strengthen spend and trace scope types * refactor(proxy): flatten log read scope into owned logs Replace the AnyOf grant tree with a flat OwnedLogs(user_id, team_ids) scope, and OwnedTraces(logs, api_key_hash) for traces, since every consumer flattened the tree back into that shape. A caller with no user id now gets an empty scope instead of matching ownerless rows through Prisma's IS NULL. The dead request_id guard in ui_view_spend_logs is removed, and the management facets inject the log team lookup and reuse read_scope_sql instead of the list shim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(proxy): run spend scope tests through one SQLite emulator Replace the string-matching payload emulator and the hand-rolled Prisma where interpreter with one SQLite helper that runs the real scope SQL. Session scope tests now go through the endpoint, including the no-user caller that must not match ownerless rows. Drop duplicated lookup-failure and trace mapping cases. load_permitted_log_team_ids returns no teams without a database instead of relying on the resolver's broad except. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(proxy): unify log and trace ownership permissions * test(tracing): align fixtures with ownership read scopes * refactor(tracing): align query scopes with row ownership * refactor(spend): make ownership SQL predicates explicit * test(spend): validate ownership SQL against PostgreSQL * docs(traces): drop key-row visibility from query help guide Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(spend): reach the empty-memberships branch in team lookup test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore(ui): regenerate dashboard API types Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
57 lines
2.1 KiB
Python
57 lines
2.1 KiB
Python
from typing import Final
|
|
|
|
import pytest
|
|
from pydantic import JsonValue, ValidationError
|
|
|
|
from litellm.rust_bridge.trace_queries import SPAN_DETAIL, SPAN_ERROR, SpanDetailParams
|
|
from litellm.rust_bridge.trace_query_responses import TraceSQLResponse
|
|
|
|
|
|
@pytest.mark.parametrize("offset", (-1, 2**64))
|
|
def test_named_query_rejects_offsets_outside_the_native_integer_range(offset: int) -> None:
|
|
with pytest.raises(ValidationError) as error:
|
|
SPAN_ERROR.parameters.model_validate(
|
|
{
|
|
"all_teams": 0,
|
|
"user_id": "",
|
|
"team_ids": ["team"],
|
|
"trace_id": "trace",
|
|
"trace_ref": "ref",
|
|
"span_id": "span",
|
|
"error_offset": offset,
|
|
"error_version": "",
|
|
}
|
|
)
|
|
assert error.value.error_count() == 1
|
|
|
|
|
|
def test_named_query_rejects_parameters_for_a_different_query() -> None:
|
|
detail: Final = SpanDetailParams(
|
|
all_teams=0,
|
|
user_id="",
|
|
team_ids=("team",),
|
|
trace_id="trace",
|
|
trace_ref="ref",
|
|
span_id="span",
|
|
)
|
|
with pytest.raises(ValidationError) as error:
|
|
SPAN_ERROR.parameters.model_validate(detail)
|
|
assert error.value.error_count() == 1
|
|
|
|
|
|
def test_named_query_rejects_rows_missing_required_result_fields() -> None:
|
|
with pytest.raises(ValidationError) as error:
|
|
SPAN_DETAIL.response.validate_json('{"data":[{"span_id":"span","input":"input","output":"output"}]}')
|
|
assert error.value.error_count() == 1
|
|
|
|
|
|
def test_sql_envelope_preserves_nested_data_large_integer_strings_and_extra_fields() -> None:
|
|
envelope: Final[dict[str, JsonValue]] = {
|
|
"meta": [{"name": "count", "type": "UInt64", "comment": "label"}],
|
|
"data": [{"count": "9007199254740993", "nested": [True, None, {"value": 2}]}],
|
|
"rows": "1",
|
|
"statistics": {"elapsed": 0.01, "rows_read": "1", "bytes_read": "8", "extra_stat": 4},
|
|
"totals": {"count": "9007199254740993"},
|
|
}
|
|
result: Final = TraceSQLResponse.model_validate(envelope)
|
|
assert result.model_dump(mode="json", exclude_unset=True) == envelope
|