gendesign/tradein-mvp/backend/tests/test_estimator_price_trend_dedup.py
bot-backend d22e0e00ae
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 45s
Deploy Trade-In / build-backend (push) Successful in 58s
Deploy Trade-In / deploy (push) Successful in 47s
tech-debt(tradein/estimator): collapse won estimate_* flags into defaults (#1970) (#2475)
2026-07-12 12:46:18 +00:00

102 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Mera-audit fix-3: cross-source dedup в _fetch_price_trend.
Проверяет что один объект на avito_imv + yandex_valuation с разными ext_item_id
не double-count'ится в месячной медиане (dedup всегда включён).
"""
from __future__ import annotations
import os
from typing import Any
from unittest.mock import MagicMock, patch
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
import pytest
from app.services.estimator import _fetch_price_trend
def _make_mock_db(source1_rows: list[dict], source2_rows: list[dict]) -> MagicMock:
"""Возвращает mock db: Source1 (houses_price_dynamics) → source1_rows,
Source2 (house_placement_history) → source2_rows."""
mock_db = MagicMock()
def side_effect(*args: Any, **kwargs: Any) -> MagicMock:
call_text = str(args[0].text) if args else ""
result = MagicMock()
if "houses_price_dynamics" in call_text:
result.mappings.return_value.all.return_value = source1_rows
else:
result.mappings.return_value.all.return_value = source2_rows
return result
mock_db.execute.side_effect = side_effect
return mock_db
# ---------------------------------------------------------------------------
# Fix-3: dedup enabled (default True)
# ---------------------------------------------------------------------------
def test_price_trend_dedup_sql_uses_distinct_on() -> None:
"""SQL-запрос к house_placement_history всегда содержит DISTINCT ON (dedup)."""
mock_db = _make_mock_db([], [{"month": "2026-01", "ppm2": 150_000}])
with patch("app.services.estimator.settings") as mock_settings:
mock_settings.estimate_price_trend_max_age_months = 6
_fetch_price_trend(mock_db, target_house_id=42, min_points=1)
calls = mock_db.execute.call_args_list
# Второй вызов — Source 2 (house_placement_history)
assert len(calls) >= 2
second_sql = str(calls[1].args[0].text) if calls[1].args else ""
assert "DISTINCT ON" in second_sql.upper() or "distinct on" in second_sql.lower()
def test_price_trend_returns_result_with_dedup() -> None:
"""Функция корректно возвращает точки тренда (dedup включён)."""
rows = [
{"month": "2025-11", "ppm2": 148_000},
{"month": "2025-12", "ppm2": 152_000},
{"month": "2026-01", "ppm2": 155_000},
]
mock_db = _make_mock_db([], rows)
with patch("app.services.estimator.settings") as mock_settings:
mock_settings.estimate_price_trend_max_age_months = 6
result = _fetch_price_trend(mock_db, target_house_id=42, min_points=3)
assert result is not None
assert len(result) == 3
assert result[0]["month"] == "2025-11"
assert result[0]["ppm2"] == 148_000
def test_price_trend_source1_preferred_over_dedup_path() -> None:
"""Если Source1 (houses_price_dynamics) вернул ≥ min_points → Source2 не вызывается."""
source1_rows = [
{"month": "2025-11", "ppm2": 140_000},
{"month": "2025-12", "ppm2": 145_000},
{"month": "2026-01", "ppm2": 150_000},
]
mock_db = _make_mock_db(source1_rows, [])
with patch("app.services.estimator.settings") as mock_settings:
mock_settings.estimate_price_trend_max_age_months = 6
result = _fetch_price_trend(mock_db, target_house_id=42, min_points=3)
# Source1 вернул 3 точки — Source2 не должен вызываться
assert result is not None
assert len(result) == 3
calls = mock_db.execute.call_args_list
# Ровно один вызов (Source1)
assert len(calls) == 1
if __name__ == "__main__": # pragma: no cover
raise SystemExit(pytest.main([__file__, "-q"]))