test: набор pytest-тестов под site/ (unit + интеграционные + безопасность)
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""Unit-тесты services/metrics.py — чистая логика, без БД."""
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from services.metrics import (
|
||||
check_arithmetic,
|
||||
normalize_date,
|
||||
_to_decimal,
|
||||
ClassifyMetrics,
|
||||
)
|
||||
|
||||
|
||||
class TestNormalizeDate:
|
||||
def test_dd_mm_yyyy(self):
|
||||
assert normalize_date("01.01.2025") == "2025-01-01"
|
||||
|
||||
def test_already_iso(self):
|
||||
assert normalize_date("2025-01-01") == "2025-01-01"
|
||||
|
||||
def test_empty(self):
|
||||
assert normalize_date("") is None
|
||||
assert normalize_date(None) is None
|
||||
|
||||
def test_unrecognized_passthrough(self):
|
||||
assert normalize_date("01 января 2025") == "01 января 2025"
|
||||
|
||||
|
||||
class TestToDecimal:
|
||||
def test_int(self):
|
||||
assert _to_decimal("50000") == Decimal("50000")
|
||||
|
||||
def test_russian_number(self):
|
||||
assert _to_decimal("50 000,00") == Decimal("50000.00")
|
||||
|
||||
def test_nbsp(self):
|
||||
assert _to_decimal("1\u00a0000,50") == Decimal("1000.50")
|
||||
|
||||
def test_none(self):
|
||||
assert _to_decimal(None) is None
|
||||
assert _to_decimal("") is None
|
||||
|
||||
def test_garbage(self):
|
||||
assert _to_decimal("не число") is None
|
||||
|
||||
|
||||
class TestCheckArithmetic:
|
||||
def test_ok(self):
|
||||
ops = [{"action": "ADD", "new_row": {"name": "x", "price": 100, "qty": 2, "sum": 200}}]
|
||||
assert check_arithmetic(ops) == []
|
||||
|
||||
def test_mismatch(self):
|
||||
ops = [{"action": "ADD", "new_row": {"name": "x", "price": 100, "qty": 2, "sum": 250}}]
|
||||
m = check_arithmetic(ops)
|
||||
assert len(m) == 1
|
||||
assert m[0]["expected_sum"] == 200.0
|
||||
assert m[0]["actual_sum"] == 250.0
|
||||
assert m[0]["diff"] == 50.0
|
||||
|
||||
def test_update_values(self):
|
||||
ops = [{"action": "UPDATE", "new_values": {"price": 10, "qty": 3, "sum": 30}}]
|
||||
assert check_arithmetic(ops) == []
|
||||
|
||||
def test_skip_incomplete(self):
|
||||
# нет qty → пропуск
|
||||
ops = [{"action": "ADD", "new_row": {"name": "x", "price": 100, "sum": 200}}]
|
||||
assert check_arithmetic(ops) == []
|
||||
|
||||
def test_skip_non_add_update(self):
|
||||
ops = [{"action": "DELETE", "target_hash": "h"}]
|
||||
assert check_arithmetic(ops) == []
|
||||
|
||||
|
||||
class TestClassifyMetrics:
|
||||
def test_fix_rate(self):
|
||||
m = ClassifyMetrics()
|
||||
m.record(False)
|
||||
m.record(True)
|
||||
m.record(False)
|
||||
assert m.total == 3
|
||||
assert m.fixes == 1
|
||||
assert m.fix_rate == pytest.approx(1 / 3)
|
||||
|
||||
def test_empty(self):
|
||||
m = ClassifyMetrics()
|
||||
assert m.fix_rate == 0.0
|
||||
assert m.summary()["total_classifications"] == 0
|
||||
Reference in New Issue
Block a user