"""Unit-тесты для ET-011 GPX-builder (`src/api/gps_tracks/export.py`). Покрывает test-plan: UT-01, UT-02, UT-03, UT-05. """ from __future__ import annotations import os import xml.etree.ElementTree as ET import pytest from lxml import etree as lxml_et from src.api.gps_tracks.export import build_gpx GPX_NS = "http://www.topografix.com/GPX/1/1" GPX = "{%s}" % GPX_NS _FIXTURES_DIR = os.path.join( os.path.dirname(__file__), "..", "fixtures", "gpx-1.1" ) _GPX_XSD_PATH = os.path.abspath(os.path.join(_FIXTURES_DIR, "gpx.xsd")) @pytest.fixture(scope="module") def gpx_schema() -> lxml_et.XMLSchema: """Загружает GPX 1.1 XSD-схему (см. tests/fixtures/gpx-1.1/gpx.xsd).""" if not os.path.exists(_GPX_XSD_PATH): pytest.skip(f"GPX XSD not found at {_GPX_XSD_PATH}") return lxml_et.XMLSchema(lxml_et.parse(_GPX_XSD_PATH)) def _validate_gpx(xml_str: str, schema: lxml_et.XMLSchema) -> None: """Валидирует GPX-строку по schema; падает с диагностикой при ошибке.""" doc = lxml_et.fromstring(xml_str.encode("utf-8")) schema.assertValid(doc) # ─── UT-01: корректная структура GPX 1.1 ────────────────────────────────── def test_ut01_build_gpx_basic_structure(): """UT-01: 5 точек, name/description/external_urls — все элементы на месте.""" xml_str = build_gpx( track_id=1, name="Test trail", description="A short description", activity_type="enduro", user="testuser", created_at="2024-05-12T10:00:00Z", sources=["osm"], external_urls=["https://www.openstreetmap.org/way/1"], coords=[ (37.60, 55.74), (37.61, 55.75), (37.62, 55.76), (37.63, 55.77), (37.64, 55.78), ], ) # ET-парсинг — используем тот же ElementTree namespace root = ET.fromstring(xml_str) assert root.tag == f"{GPX}gpx" assert root.attrib["version"] == "1.1" assert root.attrib["creator"] == "Enduro Trails" metadata = root.find(f"{GPX}metadata") assert metadata is not None name_el = metadata.find(f"{GPX}name") assert name_el is not None and name_el.text == "Test trail" link_el = metadata.find(f"{GPX}link") assert link_el is not None assert link_el.attrib["href"] == "https://www.openstreetmap.org/way/1" trks = root.findall(f"{GPX}trk") assert len(trks) == 1 trk = trks[0] segs = trk.findall(f"{GPX}trkseg") assert len(segs) == 1 pts = segs[0].findall(f"{GPX}trkpt") assert len(pts) == 5 for pt in pts: # lat/lon — float-парсебельные lat = float(pt.attrib["lat"]) lon = float(pt.attrib["lon"]) assert -90 <= lat <= 90 assert -180 <= lon <= 180 def test_ut01_metadata_link_text_includes_source(): """UT-01: text = 'Источник: '.""" xml_str = build_gpx( track_id=1, name="x", description=None, activity_type=None, user=None, created_at=None, sources=["osm"], external_urls=["https://www.openstreetmap.org/way/42"], coords=[(0.0, 0.0), (1.0, 1.0)], ) root = ET.fromstring(xml_str) link = root.find(f"{GPX}metadata/{GPX}link") text_el = link.find(f"{GPX}text") assert text_el is not None assert text_el.text == "Источник: osm" def test_ut01_osm_copyright_present(): """UT-01 / AC-10: для OSM-источника присутствует с OSM license.""" xml_str = build_gpx( track_id=1, name="osm track", description=None, activity_type=None, user=None, created_at=None, sources=["osm"], external_urls=["https://www.openstreetmap.org/way/123"], coords=[(0.0, 0.0), (1.0, 1.0)], ) root = ET.fromstring(xml_str) cr = root.find(f"{GPX}metadata/{GPX}copyright") assert cr is not None assert cr.attrib["author"] == "Enduro Trails" lic = cr.find(f"{GPX}license") assert lic is not None assert lic.text == "https://www.openstreetmap.org/copyright" # ─── UT-02: пустые / NULL поля ──────────────────────────────────────────── def test_ut02_empty_fields_no_elements(): """UT-02: ,