"""ET-008 — тесты модуля публичных GPS-треков (gps_tracks.js + endpoint). Изменение ET-008 — модуль `src/web/gps_tracks.js` + FastAPI endpoint `/api/gps-tracks`. В CI исполняется только `pytest tests/`, поэтому файл покрывает фронтендовую часть двумя способами: 1. Статические проверки структуры gps_tracks.js — выполняются всегда. 2. Поведенческие JS unit-тесты — через встроенный тест-раннер Node (`node --test`). Если `node` отсутствует — часть помечается `skip`. API-тесты endpoint живут в tests/api/test_gps_tracks_endpoint.py. Запуск JS-тестов напрямую: node --test tests/web/gps_tracks.test.js """ from __future__ import annotations import subprocess from pathlib import Path from shutil import which import pytest REPO_ROOT = Path(__file__).resolve().parents[2] GPS_TRACKS_JS = REPO_ROOT / "src" / "web" / "gps_tracks.js" JS_TEST = REPO_ROOT / "tests" / "web" / "gps_tracks.test.js" def _read(path: Path) -> str: assert path.is_file(), f"не найден {path}" return path.read_text(encoding="utf-8") # ────────────────────────────────────────────────────────────────────────────── # Статические проверки gps_tracks.js # ────────────────────────────────────────────────────────────────────────────── def test_gps_tracks_module_exists(): """Модуль src/web/gps_tracks.js присутствует в репозитории.""" assert GPS_TRACKS_JS.is_file(), "не найден src/web/gps_tracks.js" def test_gps_tracks_find_insert_position_defined(): """_findGpsInsertPosition() объявлена в модуле.""" js = _read(GPS_TRACKS_JS) assert "function _findGpsInsertPosition(" in js, ( "_findGpsInsertPosition не объявлена в gps_tracks.js" ) def test_gps_tracks_find_insert_position_priority_gpx_first(): """F-05: поиск gpx-layer-* идёт до route-* (приоритет 1 > приоритет 2).""" js = _read(GPS_TRACKS_JS) fn_start = js.index("function _findGpsInsertPosition(") fn_end = js.index("\n}", fn_start) fn_body = js[fn_start:fn_end] gpx_pos = fn_body.find("gpx-layer-") route_pos = fn_body.find("route-") assert gpx_pos != -1, "gpx-layer- не найден в _findGpsInsertPosition" assert route_pos != -1, "route- не найден в _findGpsInsertPosition" assert gpx_pos < route_pos, ( "gpx-layer-* должен проверяться ДО route-* (приоритет 1 > приоритет 2)" ) def test_gps_tracks_find_insert_position_no_exact_route_line(): """F-05: старый точный match 'route-line' удалён, используется startsWith.""" js = _read(GPS_TRACKS_JS) fn_start = js.index("function _findGpsInsertPosition(") fn_end = js.index("\n}", fn_start) fn_body = js[fn_start:fn_end] assert "l.id === 'route-line'" not in fn_body, ( "старый точный матч route-line не должен присутствовать (F-05)" ) def test_gps_tracks_state_object_defined(): """window.gpsTracksLayer инициализируется в модуле.""" js = _read(GPS_TRACKS_JS) assert "window.gpsTracksLayer = {" in js, ( "gps_tracks.js не инициализирует window.gpsTracksLayer" ) def test_gps_tracks_source_colors_defined(): """GPS_SOURCE_COLORS объявлен для всех основных источников.""" js = _read(GPS_TRACKS_JS) for src in ("osm", "enduro_russia", "ttrails", "offmaps", "nakarte"): assert src in js, f"GPS_SOURCE_COLORS не содержит ключ {src}" def test_gps_tracks_activity_colors_defined(): """GPS_ACTIVITY_COLORS объявлен для всех 7 типов активности.""" js = _read(GPS_TRACKS_JS) for act in ("enduro", "moto", "offroad", "bicycle", "hike", "ski", "other"): assert act in js, f"GPS_ACTIVITY_COLORS не содержит ключ {act}" def test_gps_tracks_build_color_expression_defined(): """_buildColorExpression() объявлена в модуле.""" js = _read(GPS_TRACKS_JS) assert "function _buildColorExpression(" in js, ( "_buildColorExpression не объявлена в gps_tracks.js" ) # ────────────────────────────────────────────────────────────────────────────── # Поведенческие JS unit-тесты через Node # ────────────────────────────────────────────────────────────────────────────── node_required = pytest.mark.skipif( which("node") is None, reason="node не установлен — поведенческие JS unit-тесты пропущены", ) @node_required def test_js_unit_tests_pass(): """F-05 + filters + colors: behavioral JS-тесты gps_tracks.js через `node --test`.""" assert JS_TEST.is_file(), f"не найден JS-тест {JS_TEST}" node = which("node") result = subprocess.run( [node, "--test", str(JS_TEST)], capture_output=True, text=True, cwd=str(REPO_ROOT), ) assert result.returncode == 0, ( f"JS unit-тесты GPS-треков упали (код {result.returncode}):\n" f"{result.stdout}\n{result.stderr}" )