136 lines
4.5 KiB
Python
136 lines
4.5 KiB
Python
"""Tests для layout_tz_pdf renderer (Issue #113 PR D).
|
||
|
||
WeasyPrint requires native GTK/Pango/GObject shared libraries. These are present
|
||
in the Docker container (Linux) but absent on Windows dev machines. All tests in
|
||
this module are skipped automatically when the native libs are unavailable.
|
||
"""
|
||
|
||
import datetime as dt
|
||
|
||
import pytest
|
||
|
||
# Attempt to import the module under test; skip entire module if native libs missing.
|
||
try:
|
||
from app.services.exporters.layout_tz_pdf import render_layout_tz_pdf
|
||
except (OSError, ImportError) as _e: # GTK libs missing on Windows, or weasyprint not installed
|
||
pytest.skip(f"WeasyPrint deps missing: {_e}", allow_module_level=True)
|
||
|
||
from app.schemas.parcel import (
|
||
BestLayoutsResponse,
|
||
LayoutDataQuality,
|
||
LayoutTzMixRow,
|
||
LayoutTzRecommendation,
|
||
TopLayoutRow,
|
||
)
|
||
|
||
|
||
def _sample_response() -> BestLayoutsResponse:
|
||
return BestLayoutsResponse(
|
||
top_layouts=[
|
||
TopLayoutRow(
|
||
rank=1,
|
||
room_bucket="1",
|
||
area_bin="25-40",
|
||
signature="1__25-40",
|
||
competitor_obj_ids=[1234, 5678],
|
||
competitor_count=2,
|
||
total_sold_in_window=67,
|
||
velocity_per_month=8.4,
|
||
avg_price_per_m2_rub=148000.0,
|
||
avg_area_m2=38.5,
|
||
supply_units_in_radius=312,
|
||
sold_pct_of_supply=21.5,
|
||
),
|
||
TopLayoutRow(
|
||
rank=2,
|
||
room_bucket="studio",
|
||
area_bin="<25",
|
||
signature="studio__<25",
|
||
competitor_obj_ids=[1234],
|
||
competitor_count=1,
|
||
total_sold_in_window=40,
|
||
velocity_per_month=5.0,
|
||
avg_price_per_m2_rub=160000.0,
|
||
avg_area_m2=22.0,
|
||
supply_units_in_radius=100,
|
||
sold_pct_of_supply=40.0,
|
||
),
|
||
],
|
||
recommendation_for_tz=LayoutTzRecommendation(
|
||
rationale_text="Test rationale текст с кириллицей",
|
||
mix=[
|
||
LayoutTzMixRow(room_bucket="studio", pct=10, abs_units=30, avg_target_area_m2=22.0),
|
||
LayoutTzMixRow(room_bucket="1", pct=60, abs_units=180, avg_target_area_m2=38.5),
|
||
LayoutTzMixRow(room_bucket="2", pct=30, abs_units=90, avg_target_area_m2=55.0),
|
||
],
|
||
weighted_avg_price_per_m2_rub=152000.0,
|
||
based_on_obj_count=5,
|
||
based_on_total_deals=107,
|
||
data_window_start=dt.date(2026, 2, 1),
|
||
data_window_end=dt.date(2026, 5, 1),
|
||
),
|
||
data_quality=LayoutDataQuality(
|
||
objects_with_velocity_data=5,
|
||
objects_total_in_radius=8,
|
||
velocity_coverage_pct=62.5,
|
||
confidence="medium",
|
||
),
|
||
)
|
||
|
||
|
||
def test_pdf_renders_non_empty_bytes() -> None:
|
||
pdf = render_layout_tz_pdf(
|
||
_sample_response(),
|
||
cad_num="66:41:0204016:10",
|
||
radius_km=1.0,
|
||
time_window="last_quarter",
|
||
)
|
||
assert len(pdf) > 1000 # PDF минимум ~1KB
|
||
|
||
|
||
def test_pdf_starts_with_pdf_magic() -> None:
|
||
pdf = render_layout_tz_pdf(
|
||
_sample_response(),
|
||
cad_num="66:41:0204016:10",
|
||
radius_km=1.0,
|
||
time_window="last_quarter",
|
||
)
|
||
assert pdf[:4] == b"%PDF"
|
||
|
||
|
||
def test_pdf_renders_cyrillic_correctly() -> None:
|
||
"""Smoke — WeasyPrint должен handle кириллический rationale_text без UnicodeEncodeError."""
|
||
response = _sample_response()
|
||
pdf = render_layout_tz_pdf(
|
||
response,
|
||
cad_num="66:41:0303161:42",
|
||
radius_km=1.5,
|
||
time_window="last_year",
|
||
)
|
||
# Embedded text может быть compressed, но без exception = OK
|
||
assert len(pdf) > 1000
|
||
|
||
|
||
def test_pdf_handles_empty_top_layouts() -> None:
|
||
response = _sample_response()
|
||
response.top_layouts = []
|
||
pdf = render_layout_tz_pdf(
|
||
response,
|
||
cad_num="66:41:0204016:10",
|
||
radius_km=1.0,
|
||
time_window="last_quarter",
|
||
)
|
||
assert pdf[:4] == b"%PDF"
|
||
|
||
|
||
def test_pdf_handles_null_avg_price() -> None:
|
||
"""avg_price_per_m2_rub=None (ЖК не покрыт Objective) → должно рендериться как '—'."""
|
||
response = _sample_response()
|
||
response.top_layouts[0].avg_price_per_m2_rub = None
|
||
pdf = render_layout_tz_pdf(
|
||
response,
|
||
cad_num="66:41:0204016:10",
|
||
radius_km=1.0,
|
||
time_window="last_quarter",
|
||
)
|
||
assert pdf[:4] == b"%PDF"
|