Files
enduro-trails/tests/web/test_track_download.py
claude-bot 721b33a2f6
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 6s
CI / lint (pull_request) Successful in 4s
CI / build (push) Successful in 2s
CI / test (pull_request) Successful in 6s
CI / build (pull_request) Successful in 4s
fix(gps-tracks): address ET-011 review — JS UI tests + flat 403 contract
Закрывает findings из docs/work-items/ET-011/12-review.md (REQUEST_CHANGES,
попытка 3/3):

P1-01 — добавлены поведенческие JS unit-тесты UI download-flow
  - tests/web/track_download.test.js — 28 кейсов (node --test):
      • _parseFilenameFromCD — RFC 5987 приоритет, plain fallback,
        битый percent-encoding, null/empty (REQ-F-05.2, AC-2 UI)
      • _handleDownloadError — 400/403/404/413/5xx тосты, defensive
        при отсутствии showToast, поддержка flat (ADR-015 §G) и legacy
        wrapped 403-форм (REQ-F-05.4, AC-7 UI)
      • _renderTrackPopupHtml — наличие кнопки, aria-label «Скачать GPX»,
        data-track-id, отсутствие при невалидном id, регрессия прочих
        полей (REQ-F-01, AC-1)
  - tests/web/test_track_download.py — pytest-обёртка (статические
    проверки + запуск Node-раннера), исполняется в обычном pytest tests/
  - 04b-ui-test-cases.md: AC-13 (mobile-bbox) явно маркирован как
    MANUAL release-smoke (Playwright-раннер в проекте не настроен;
    альтернатива согласована reviewer'ом в P1-01).

P2-01 — устранено расхождение «doc vs runtime» по контракту 403
  - endpoint.py: HTTPException(detail={...}) → JSONResponse(content={...}),
    чтобы FastAPI не оборачивал dict во второй слой «detail». Контракт
    теперь совпадает с ADR-015 §G и ADR-014 §6:
        {"detail":"source_forbidden","external_urls":[...]}
  - test_gps_tracks_download.py IT-05: упрощено — body уже плоский,
    без двухуровневого `body.get("detail", body)` workaround.
  - gps_tracks.js::_handleDownloadError: flat-форма стала приоритетной,
    wrapped-форма оставлена как defensive fallback (с комментарием).

Регрессия: 89/89 API-тестов + 24/24 предыдущих JS-тестов + 28 новых
JS-тестов download-flow проходят. ruff check — clean.

Refs: ET-011

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 23:01:19 +00:00

94 lines
3.7 KiB
Python

"""ET-011 — pytest-обёртка для JS unit-тестов download-UI.
Контекст: test-plan §E2E-01..E2E-04 предусматривал Playwright-спеку
`tests/web/test_track_download.spec.ts`, но в проекте нет настроенного
Playwright-раннера. Reviewer ET-011 (12-review.md, P1-01) разрешил закрыть
UI-сторону AC-1 / AC-2 / AC-7 поведенческими JS unit-тестами через
`node --test`. AC-13 (mobile-bbox) оставлен как manual smoke
(см. 04b-ui-test-cases.md TC-UI-02).
Этот файл — pytest-точка-входа, запускающая Node-раннер. Так JS-тесты
исполняются в обычном `pytest tests/` без отдельных шагов в Makefile/CI.
Запуск JS-тестов напрямую:
node --test tests/web/track_download.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" / "track_download.test.js"
def _read(path: Path) -> str:
assert path.is_file(), f"не найден {path}"
return path.read_text(encoding="utf-8")
# ─── Статические проверки: ET-011 артефакты на месте ─────────────────────────
def test_download_helpers_defined_in_gps_tracks_js():
"""ET-011: новые функции download-UI объявлены в gps_tracks.js."""
js = _read(GPS_TRACKS_JS)
for symbol in (
"function _parseFilenameFromCD(",
"function _handleDownloadError(",
"async function _downloadPublicTrack(",
):
assert symbol in js, (
f"ET-011: символ `{symbol}` не найден в src/web/gps_tracks.js"
)
def test_popup_renders_download_button_markup():
"""AC-1: _renderTrackPopupHtml содержит маркап кнопки «Скачать GPX»."""
js = _read(GPS_TRACKS_JS)
# Существенные куски, по которым держится UI-контракт
assert 'aria-label="Скачать GPX"' in js, (
"AC-1: aria-label='Скачать GPX' отсутствует в gps_tracks.js"
)
assert "track-popup-download-btn" in js, (
"AC-1: CSS-класс кнопки track-popup-download-btn отсутствует"
)
assert "data-track-id=" in js, (
"ADR-014 §3.b: data-track-id для делегированного клика отсутствует"
)
def test_js_test_file_exists():
"""JS-тест присутствует в репозитории — иначе субтесты ниже бессмыслены."""
assert JS_TEST.is_file(), f"не найден JS-тест {JS_TEST}"
# ─── Поведенческие JS unit-тесты через Node ──────────────────────────────────
node_required = pytest.mark.skipif(
which("node") is None,
reason="node не установлен — поведенческие JS unit-тесты пропущены",
)
@node_required
def test_js_track_download_unit_tests_pass():
"""ET-011 P1-01: AC-1 / AC-2 / AC-7 (UI) — JS-тесты download-flow."""
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-тесты track_download упали (код {result.returncode}):\n"
f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
)