gendesign/tradein-mvp/backend/tests/test_cian_state_parser.py
lekss361 994eba0a16
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
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 47s
Deploy Trade-In / build-backend (push) Successful in 1m34s
Deploy Trade-In / deploy (push) Successful in 1m8s
fix(tradein/cian): extract_all_states — discover new .concat() format, not just legacy .push() (#2438)
2026-07-04 21:03:32 +00:00

137 lines
6.3 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.

"""Тесты для `scraper_kit.cian_state_parser` (extract_state / extract_all_states).
Regression coverage for a bug found live 2026-07-04 while verifying #2435
(bti_data persistence): `extract_all_states()` only ever scanned the legacy
`.push({...})` `_cianConfig` format via `_RE_CIAN_PUSH`. Cian switched real pages
to `.concat([...])` in 2026-05 (#639) — `extract_state()` was updated to handle
both (concat-first, push-fallback), but `extract_all_states()` was never updated,
so on every current-format page it silently returned `{}` — starving both of its
consumers (`cian/detail.py`'s bti sister-state, `cian/newbuilding.py`'s sister-state)
of real data, confirmed live against a real offer page (200 OK, 595KB HTML) where
`extract_all_states()` returned zero top-level MFE keys despite `extract_state()`
successfully parsing `defaultState` from the very same HTML.
No test file for this module existed before (a stale `.pyc` from a deleted
pre-scraper_kit-migration test was the only trace) — this is new coverage, not
a rewrite.
"""
from __future__ import annotations
from scraper_kit.cian_state_parser import extract_all_states, extract_state
def _concat_push(mfe: str, entries: list[tuple[str, str]]) -> str:
"""Строит HTML-фрагмент в НОВОМ (2026-05, #639) `.concat([...])` формате.
`entries` — список (key, raw_json_value) пар, ужe сериализованных в JSON-текст
(не эскейпленных дважды — `.concat([...])` несёт валидный JSON-массив как есть).
"""
items = ",".join(f'{{"key":"{k}","value":{v},"priority":0}}' for k, v in entries)
return (
f"window._cianConfig['{mfe}'] = (window._cianConfig['{mfe}'] || [])" f".concat([{items}]);"
)
def _push(mfe: str, key: str, value: str) -> str:
"""Строит HTML-фрагмент в ЛЕГАСИ `.push({...})` формате."""
return f"window._cianConfig['{mfe}'].push({{key:'{key}', value:{value}, priority:0}});"
def test_extract_all_states_finds_concat_format_mfe_and_keys():
"""Regression: до фикса это возвращало {} на любой странице в новом формате."""
html = (
"<script>"
+ _concat_push(
"frontend-offer-card",
[
("defaultState", '{"offerData":{"offer":{"cianId":123}}}'),
("bti", '{"houseData":{"seriesName":"1-464","entrances":4}}'),
],
)
+ "</script>"
)
result = extract_all_states(html)
assert "frontend-offer-card" in result
offer_card = result["frontend-offer-card"]
assert set(offer_card.keys()) == {"defaultState", "bti"}
assert offer_card["bti"]["houseData"]["seriesName"] == "1-464"
assert offer_card["bti"]["houseData"]["entrances"] == 4
def test_extract_all_states_multiple_mfe_containers_concat_format():
"""Несколько разных mfe-контейнеров в одном html — все должны попасть в результат
(регрессия на реальный кейс: живая страница вернула 6 top-level mfe-ключей)."""
html = (
"<script>"
+ _concat_push("frontend-offer-card", [("defaultState", '{"a":1}')])
+ _concat_push("header-frontend", [("version", '"1.2.3"')])
+ _concat_push("frontend-footer", [("locale", '"ru"')])
+ "</script>"
)
result = extract_all_states(html)
assert set(result.keys()) == {"frontend-offer-card", "header-frontend", "frontend-footer"}
assert result["header-frontend"]["version"] == "1.2.3"
def test_extract_all_states_legacy_push_format_still_works():
"""Fallback-путь (старый .push() формат) не должен был сломаться фиксом."""
html = (
"<script>"
+ _push("frontend-offer-card", "defaultState", '{"offerData":{"offer":{"cianId":456}}}')
+ _push("frontend-offer-card", "bti", '{"houseData":{"flatCount":58}}')
+ "</script>"
)
result = extract_all_states(html)
assert "frontend-offer-card" in result
offer_card = result["frontend-offer-card"]
assert offer_card["defaultState"]["offerData"]["offer"]["cianId"] == 456
assert offer_card["bti"]["houseData"]["flatCount"] == 58
def test_extract_all_states_mixed_concat_and_push_prefers_concat():
"""Смешанный html (concat для одного mfe/key, push для другого/того же) —
concat не должен быть перезаписан push-фолбэком на тот же (mfe, key)."""
html = (
"<script>"
+ _concat_push("frontend-offer-card", [("bti", '{"houseData":{"entrances":9}}')])
+ _push("frontend-offer-card", "bti", '{"houseData":{"entrances":1}}')
+ _push("header-frontend", "version", '"9.9.9"')
+ "</script>"
)
result = extract_all_states(html)
# concat-значение (entrances=9) должно выиграть, push с тем же (mfe,key) — не перезаписывать
assert result["frontend-offer-card"]["bti"]["houseData"]["entrances"] == 9
# push-only mfe (header-frontend) всё равно должен попасть в результат
assert result["header-frontend"]["version"] == "9.9.9"
def test_extract_all_states_no_cianconfig_returns_empty_dict():
"""Страница без _cianConfig вообще (или сломанная) — пустой dict, не exception."""
assert extract_all_states("<html><body>no config here</body></html>") == {}
def test_extract_state_concat_format_matches_extract_all_states_value():
"""Sanity: extract_state() и extract_all_states() должны согласованно видеть
одно и то же значение для одного и того же (mfe, key) на concat-странице —
ключевой инвариант, который был нарушен багом (extract_state видел данные,
extract_all_states — нет)."""
html = (
"<script>"
+ _concat_push("frontend-offer-card", [("bti", '{"houseData":{"seriesName":"X"}}')])
+ "</script>"
)
single = extract_state(html, mfe="frontend-offer-card", key="bti")
all_states = extract_all_states(html)
assert single is not None
assert all_states["frontend-offer-card"]["bti"] == single