216 lines
6.9 KiB
Python
216 lines
6.9 KiB
Python
"""Unit-тесты pydantic-моделей данных."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from selectel_ml_tui.models import (
|
|
Consumption,
|
|
ConsumptionItem,
|
|
HistoryPoint,
|
|
Metrics,
|
|
ModelMetrics,
|
|
ProjectRef,
|
|
format_percent,
|
|
format_rub,
|
|
format_tokens,
|
|
)
|
|
|
|
|
|
class TestConsumption:
|
|
def test_parse_with_z_timestamp(self) -> None:
|
|
item = Consumption.model_validate(
|
|
{
|
|
"period": "2026-08-01T00:00:00.000Z",
|
|
"amount_rub": "854.83",
|
|
"input_tokens": 100,
|
|
"output_tokens": 50,
|
|
"cache_read_tokens": 10,
|
|
"cache_write_tokens": 5,
|
|
}
|
|
)
|
|
assert item.period == datetime(2026, 8, 1, tzinfo=timezone.utc)
|
|
assert item.amount_rub == Decimal("854.83")
|
|
assert item.input_tokens == 100
|
|
|
|
def test_parse_naive_timestamp(self) -> None:
|
|
item = Consumption.model_validate({"period": "2026-08-01T00:00:00"})
|
|
assert item.period == datetime(2026, 8, 1)
|
|
assert item.amount_rub == Decimal("0")
|
|
assert item.input_tokens == 0
|
|
|
|
def test_negative_amount_rejected(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
Consumption.model_validate(
|
|
{"period": "2026-08-01T00:00:00", "amount_rub": "-1"}
|
|
)
|
|
|
|
def test_negative_tokens_rejected(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
Consumption.model_validate(
|
|
{
|
|
"period": "2026-08-01T00:00:00",
|
|
"input_tokens": -5,
|
|
}
|
|
)
|
|
|
|
def test_invalid_date_rejected(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
Consumption.model_validate({"period": "not-a-date"})
|
|
|
|
|
|
class TestMetrics:
|
|
def test_parse_metrics(self) -> None:
|
|
metrics = Metrics.model_validate(
|
|
{
|
|
"availability_pct": 99.912,
|
|
"avg_ttft_ms": 0.45,
|
|
"input_tokens": 1000,
|
|
"output_tokens": 900,
|
|
"cache_read_tokens": 800,
|
|
"cache_write_tokens": 700,
|
|
}
|
|
)
|
|
assert metrics.availability_pct == 99.912
|
|
assert metrics.avg_ttft_ms == 0.45
|
|
assert metrics.input_tokens == 1000
|
|
|
|
def test_missing_fields_defaults(self) -> None:
|
|
metrics = Metrics.model_validate({})
|
|
assert metrics.availability_pct == 100.0
|
|
assert metrics.input_tokens == 0
|
|
assert metrics.avg_ttft_ms == 0.0
|
|
|
|
def test_availability_bounds_rejected(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
Metrics.model_validate({"availability_pct": 101})
|
|
with pytest.raises(ValidationError):
|
|
Metrics.model_validate({"availability_pct": -1})
|
|
|
|
|
|
class TestModelMetrics:
|
|
def test_parse_model_metrics(self) -> None:
|
|
model = ModelMetrics.model_validate(
|
|
{
|
|
"model_id": "gpt-4o",
|
|
"model_name": "GPT-4o",
|
|
"availability_pct": 99.5,
|
|
"avg_ttft_ms": 0.3,
|
|
}
|
|
)
|
|
assert model.model_id == "gpt-4o"
|
|
assert model.model_name == "GPT-4o"
|
|
assert model.availability_pct == 99.5
|
|
|
|
|
|
class TestHistoryPoint:
|
|
def test_parse(self) -> None:
|
|
point = HistoryPoint.model_validate(
|
|
{
|
|
"timestamp": "2026-08-01T12:00:00Z",
|
|
"amount_rub": "12.5",
|
|
"tokens": {
|
|
"input_tokens": 100,
|
|
"output_tokens": 200,
|
|
"cache_read_tokens": 30,
|
|
"cache_write_tokens": 40,
|
|
},
|
|
}
|
|
)
|
|
assert point.timestamp == datetime(2026, 8, 1, 12, 0, tzinfo=timezone.utc)
|
|
assert point.amount_rub == Decimal("12.5")
|
|
assert point.tokens.output_tokens == 200
|
|
|
|
def test_missing_tokens_defaults(self) -> None:
|
|
point = HistoryPoint.model_validate({"timestamp": "2026-08-01T00:00:00"})
|
|
assert point.tokens.input_tokens == 0
|
|
assert point.amount_rub == Decimal("0")
|
|
|
|
|
|
class TestConsumptionItem:
|
|
def _base(self, **overrides: object) -> dict[str, object]:
|
|
data: dict[str, object] = {
|
|
"account_id": "239633",
|
|
"period": "2026-08-01T00:00:00",
|
|
"provider_key": "aig",
|
|
"value": 854.83,
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
def test_project_as_string(self) -> None:
|
|
item = ConsumptionItem.model_validate(
|
|
self._base(project="7bf2d2a328494e25b13d05a1926ca75a")
|
|
)
|
|
assert item.project_id == "7bf2d2a328494e25b13d05a1926ca75a"
|
|
|
|
def test_project_as_object(self) -> None:
|
|
item = ConsumptionItem.model_validate(
|
|
self._base(
|
|
project={"id": "proj-1", "name": "239633"},
|
|
)
|
|
)
|
|
assert isinstance(item.project, ProjectRef)
|
|
assert item.project_id == "proj-1"
|
|
assert item.project.name == "239633"
|
|
|
|
def test_project_null(self) -> None:
|
|
item = ConsumptionItem.model_validate(self._base(project=None))
|
|
assert item.project_id is None
|
|
|
|
def test_object_as_object(self) -> None:
|
|
item = ConsumptionItem.model_validate(
|
|
self._base(
|
|
object={
|
|
"id": "obj-1",
|
|
"name": "key-1",
|
|
"type": "ai_api_key",
|
|
"type_name": "Api ключ",
|
|
}
|
|
)
|
|
)
|
|
assert item.object_id == "obj-1"
|
|
assert item.object.type == "ai_api_key" # type: ignore[union-attr]
|
|
|
|
def test_object_as_string(self) -> None:
|
|
item = ConsumptionItem.model_validate(self._base(object="obj-1"))
|
|
assert item.object_id == "obj-1"
|
|
|
|
def test_metric_info(self) -> None:
|
|
item = ConsumptionItem.model_validate(
|
|
self._base(
|
|
metric={
|
|
"id": "air_tokens_cost",
|
|
"name": "Стоимость",
|
|
"unit": "nanorub",
|
|
"quantity": "854857248637",
|
|
}
|
|
)
|
|
)
|
|
assert item.metric is not None
|
|
assert item.metric.id == "air_tokens_cost"
|
|
assert item.metric.quantity == Decimal("854857248637")
|
|
|
|
def test_value_decimal(self) -> None:
|
|
item = ConsumptionItem.model_validate(self._base(value=42.5))
|
|
assert item.value == Decimal("42.5")
|
|
|
|
|
|
class TestFormatting:
|
|
def test_format_rub(self) -> None:
|
|
assert format_rub(Decimal("854.83")) == "854,83 ₽"
|
|
assert format_rub(0) == "0,00 ₽"
|
|
assert format_rub("1234.5") == "1 234,50 ₽"
|
|
|
|
def test_format_tokens(self) -> None:
|
|
assert format_tokens(0) == "0"
|
|
assert format_tokens(1234567) == "1 234 567"
|
|
|
|
def test_format_percent(self) -> None:
|
|
assert format_percent(99.912) == "99,9%"
|
|
assert format_percent(100.0) == "100,0%"
|