"""Тесты pure-парсера CBR KeyRate (offline, без живой сети).
Покрывают: парсинг (date, value), обработку tz-суффикса, сортировку,
пустой / битый / без-KR XML → []. Фикстуры — из реального формата
KeyRateXML-ответа DailyInfoWebServ (verified live 2026-06-02).
"""
from __future__ import annotations
from datetime import date
from app.services.scrapers.cbr_macro import parse_key_rate_xml
# Реальный shape KeyRateXML (без diffgram). 3 строки, descending по дате,
# tz-суффикс +03:00, точка как десятичный разделитель.
FIXTURE_KEYRATE_XML = (
''
''
""
''
""
''
"2023-09-15T00:00:00+03:0012.00"
"2023-08-15T00:00:00+03:0012.00"
"2023-08-14T00:00:00+03:008.50"
""
""
""
""
""
)
# Diffgram-вариант (метод KeyRate) — парсер должен его тоже понимать (находит KR
# где угодно), несмотря на schema-блок и namespace на KR.
FIXTURE_KEYRATE_DIFFGRAM = (
''
""
''
''
''
"2023-09-29T00:00:00+03:0013.00"
''
"2023-09-15T00:00:00+03:0012.00"
""
""
)
def test_parse_basic_rows() -> None:
"""3 KR-строки → 3 кортежа (date, float), отсортированы по возрастанию даты."""
rows = parse_key_rate_xml(FIXTURE_KEYRATE_XML)
assert rows == [
(date(2023, 8, 14), 8.5),
(date(2023, 8, 15), 12.0),
(date(2023, 9, 15), 12.0),
]
def test_parse_returns_sorted_ascending() -> None:
"""Вход descending — выход обязан быть ascending по дате."""
rows = parse_key_rate_xml(FIXTURE_KEYRATE_XML)
dates = [d for d, _ in rows]
assert dates == sorted(dates)
def test_parse_strips_timezone_suffix() -> None:
"""DT с tz-суффиксом '+03:00' парсится в чистую дату (без сдвига)."""
rows = parse_key_rate_xml(FIXTURE_KEYRATE_XML)
assert (date(2023, 8, 15), 12.0) in rows
# значение float, не строка
assert all(isinstance(v, float) for _, v in rows)
def test_parse_value_types() -> None:
"""Дробная ставка 8.50 → 8.5 (float)."""
rows = dict(parse_key_rate_xml(FIXTURE_KEYRATE_XML))
assert rows[date(2023, 8, 14)] == 8.5
def test_parse_diffgram_variant() -> None:
"""Diffgram-обёртка (метод KeyRate) тоже парсится — KR находится в любом
namespace / вложенности."""
rows = parse_key_rate_xml(FIXTURE_KEYRATE_DIFFGRAM)
assert rows == [
(date(2023, 9, 15), 12.0),
(date(2023, 9, 29), 13.0),
]
def test_parse_empty_string() -> None:
assert parse_key_rate_xml("") == []
assert parse_key_rate_xml(" ") == []
def test_parse_empty_response_no_kr() -> None:
"""Валидный, но пустой KeyRateXMLResponse (период без данных) → []."""
xml = (
''
''
""
)
assert parse_key_rate_xml(xml) == []
def test_parse_malformed_xml() -> None:
"""Битый XML → [] (не бросает)."""
assert parse_key_rate_xml("2023") == []
def test_parse_skips_kr_with_missing_fields() -> None:
"""KR без DT или без Rate пропускается; валидные остаются."""
xml = (
""
"2023-08-15T00:00:00+03:0012.00"
"2023-08-16T00:00:00+03:00" # нет Rate
"11.00" # нет DT
""
)
assert parse_key_rate_xml(xml) == [(date(2023, 8, 15), 12.0)]
def test_parse_dedups_by_date_last_wins() -> None:
"""Дубли по дате схлопываются — последний выигрывает."""
xml = (
""
"2023-08-15T00:00:00+03:0012.00"
"2023-08-15T00:00:00+03:0013.00"
""
)
assert parse_key_rate_xml(xml) == [(date(2023, 8, 15), 13.0)]