142 lines
4.3 KiB
Python
142 lines
4.3 KiB
Python
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
from app.services.scrapers.cian_detail import (
|
|
DetailEnrichment,
|
|
_parse_float,
|
|
_parse_views,
|
|
_extract_price_changes,
|
|
_extract_agent_profile,
|
|
fetch_detail,
|
|
)
|
|
|
|
|
|
def test_parse_views():
|
|
assert _parse_views("1 234") == 1234
|
|
assert _parse_views("567") == 567
|
|
assert _parse_views(None) is None
|
|
assert _parse_views("nope") is None
|
|
|
|
|
|
def test_parse_float():
|
|
assert _parse_float("2.7") == 2.7
|
|
assert _parse_float(3) == 3.0
|
|
assert _parse_float(None) is None
|
|
assert _parse_float("bad") is None
|
|
|
|
|
|
def test_extract_price_changes_normalizes():
|
|
offer = {
|
|
"priceChanges": [
|
|
{"changeTime": "2026-04-01T00:00:00Z", "price": 5000000, "diffPercent": -2.5},
|
|
]
|
|
}
|
|
result = _extract_price_changes(offer, {})
|
|
assert len(result) == 1
|
|
assert result[0]["change_time"] == "2026-04-01T00:00:00Z"
|
|
assert result[0]["price_rub"] == 5000000
|
|
assert result[0]["diff_percent"] == -2.5
|
|
|
|
|
|
def test_extract_price_changes_empty():
|
|
assert _extract_price_changes({}, {}) == []
|
|
assert _extract_price_changes({"priceChanges": None}, {}) == []
|
|
|
|
|
|
def test_extract_agent_profile_from_agent():
|
|
offer = {"agent": {"id": 999, "companyName": "ACME", "isPro": True, "skills": ["x"]}}
|
|
profile = _extract_agent_profile(offer)
|
|
assert profile is not None
|
|
assert profile["ext_agent_id"] == 999
|
|
assert profile["company_name"] == "ACME"
|
|
assert profile["is_pro"] is True
|
|
|
|
|
|
def test_extract_agent_profile_from_user_fallback():
|
|
offer = {"user": {"cianUserId": 111, "companyName": "Sole"}}
|
|
profile = _extract_agent_profile(offer)
|
|
assert profile is not None
|
|
assert profile["ext_agent_id"] == 111
|
|
assert profile["company_name"] == "Sole"
|
|
|
|
|
|
def test_extract_agent_profile_none_when_empty():
|
|
assert _extract_agent_profile({}) is None
|
|
|
|
|
|
def test_dataclass_defaults():
|
|
e = DetailEnrichment()
|
|
assert e.cian_id is None
|
|
assert e.price_changes == []
|
|
assert e.raw_sister_states == {}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_detail_returns_none_on_no_state(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"app.services.scrapers.cian_detail.extract_state",
|
|
lambda html, mfe, key: None,
|
|
)
|
|
session = MagicMock()
|
|
response = MagicMock()
|
|
response.status_code = 200
|
|
response.text = "<html></html>"
|
|
session.get = AsyncMock(return_value=response)
|
|
session.close = AsyncMock()
|
|
result = await fetch_detail("https://ekb.cian.ru/sale/flat/123/", session=session)
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_detail_returns_none_on_http_error(monkeypatch):
|
|
session = MagicMock()
|
|
response = MagicMock()
|
|
response.status_code = 403
|
|
response.text = ""
|
|
session.get = AsyncMock(return_value=response)
|
|
session.close = AsyncMock()
|
|
result = await fetch_detail("https://ekb.cian.ru/sale/flat/123/", session=session)
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_detail_parses_core_fields(monkeypatch):
|
|
fake_state = {
|
|
"offerData": {
|
|
"offer": {
|
|
"cianId": 42,
|
|
"windowsViewType": "yard",
|
|
"separateWcsCount": 1,
|
|
"combinedWcsCount": 0,
|
|
"ceilingHeight": 2.7,
|
|
"repairType": "cosmetic",
|
|
}
|
|
},
|
|
"stats": {
|
|
"totalViewsFormattedString": "1 234",
|
|
"todayViewsFormattedString": "12",
|
|
},
|
|
}
|
|
monkeypatch.setattr(
|
|
"app.services.scrapers.cian_detail.extract_state",
|
|
lambda html, mfe, key: fake_state,
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.services.scrapers.cian_detail.extract_all_states",
|
|
lambda html: {"frontend-offer-card": {"defaultState": fake_state}},
|
|
)
|
|
session = MagicMock()
|
|
response = MagicMock()
|
|
response.status_code = 200
|
|
response.text = "<html></html>"
|
|
session.get = AsyncMock(return_value=response)
|
|
session.close = AsyncMock()
|
|
result = await fetch_detail("https://ekb.cian.ru/sale/flat/42/", session=session)
|
|
assert result is not None
|
|
assert result.cian_id == 42
|
|
assert result.windows_view_type == "yard"
|
|
assert result.separate_wcs_count == 1
|
|
assert result.ceiling_height == 2.7
|
|
assert result.repair_type == "cosmetic"
|
|
assert result.views_total == 1234
|
|
assert result.views_today == 12
|