Files
enduro-trails/docs/work-items/ET-006/04-test-plan.yaml
claude-bot 6edf97fe79
Some checks failed
CI / lint (push) Failing after 4s
CI / test (push) Successful in 6s
CI / build (push) Has been skipped
analyst(ET): auto-commit from analyst run_id=59
2026-06-03 17:33:09 +00:00

319 lines
12 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: test-plan
work_item_id: ET-006
title: "Test Plan: Скачивание трека из popup на карте"
version: 1
status: approved
created_at: 2026-06-03
updated_at: 2026-06-03
authors:
- "agent:analyst"
---
# Test Plan — ET-006: Скачивание трека из popup на карте
# Структура: каждый тест-кейс ссылается на AC и на TRZ-требование.
# UI-тесты (Playwright визуальные) описаны в 04b-ui-test-cases.md.
suites:
unit:
framework: pytest
path: tests/unit/
cases:
- id: TC-UNIT-01
title: "gpx_builder.build_gpx — корректный корень и XML 1.0 декларация"
covers:
ac: [AC-04]
req: [REQ-F-04, REQ-F-06, REQ-F-10]
given: "TrackRow-фикстура с name='X', 3 точки, sources=['osm']"
when: "build_gpx(track_row) → bytes"
then:
- "первая строка начинается с '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'"
- "корневой элемент <gpx version='1.1' xmlns='http://www.topografix.com/GPX/1/1'"
- "содержит xsi:schemaLocation на GPX 1.1 XSD"
- "содержит атрибут creator='enduro-trails (...)'"
- "XML парсится без ошибок (ET.fromstring)"
- id: TC-UNIT-02
title: "gpx_builder — точки сохраняются с точностью 6 знаков и в исходном порядке"
covers:
ac: [AC-05]
req: [REQ-F-09]
given: "track с coords=[(37.123456789, 55.987654321), (37.111111, 55.222222), (37.999999, 55.000001)]"
when: "сериализация в GPX"
then:
- "в <trkseg> ровно 3 <trkpt>"
- "первый <trkpt lat='55.987654' lon='37.123457'/> (округление до 6 знаков)"
- "порядок элементов совпадает с входным"
- id: TC-UNIT-03
title: "gpx_builder — metadata содержит name/desc/author/time/link"
covers:
ac: [AC-06]
req: [REQ-F-07]
given: "track с name='Кольцо', activity_type='enduro', user='vasya', created_at='2025-08-14T09:30:00Z', external_urls=['https://example.com/a','https://example.com/b'], length_m=42500, sources=['osm','wikiloc']"
when: "build_gpx"
then:
- "metadata/name == 'Кольцо'"
- "metadata/desc содержит 'Эндуро' и '42.5' и 'osm' и 'wikiloc'"
- "metadata/author/name == 'vasya'"
- "metadata/time == '2025-08-14T09:30:00Z'"
- "metadata содержит две <link> в порядке external_urls"
- "trk/name == 'Кольцо', trk/type == 'enduro'"
- id: TC-UNIT-04
title: "filename builder — очистка запрещённых символов"
covers:
ac: [AC-07]
req: [REQ-F-05]
cases:
- input: { name: "OSM/Trail*?<2024>", id: 42 }
expect_ascii_match: "^[A-Za-z0-9._-]+\\.gpx$"
expect_utf8: "OSM-Trail-2024.gpx"
- input: { name: "", id: 7 }
expect_ascii: "track-7.gpx"
- input: { name: null, id: 7 }
expect_ascii: "track-7.gpx"
- input: { name: " ", id: 9 }
expect_ascii: "track-9.gpx"
- input: { name: "Очень-длинное-имя-".ljust(200, "X"), id: 1 }
expect_max_basename_len: 64
- id: TC-UNIT-05
title: "copyright — OSM ODbL"
covers:
ac: [AC-08]
req: [REQ-F-08]
given: "track.sources_json = '[\"osm\"]'"
when: "build_gpx"
then:
- "metadata/copyright@author == 'OpenStreetMap contributors'"
- "metadata/copyright/license == 'https://opendatacommons.org/licenses/odbl/'"
- id: TC-UNIT-06
title: "copyright — Wikiloc"
covers:
ac: [AC-09]
req: [REQ-F-08]
given: "track.sources_json = '[\"wikiloc\"]'"
when: "build_gpx"
then:
- "metadata/copyright@author == 'Wikiloc contributors'"
- "metadata/copyright/license содержит wikiloc.com/wikiloc/legalNotice"
- id: TC-UNIT-07
title: "popup HTML — кнопка не рендерится при отсутствии id"
covers:
ac: [AC-19]
req: [REQ-F-13]
framework_override: jest # JS unit
given: "_renderTrackPopupHtml({ name: 'X' }) // без id"
when: "вызывается функция"
then:
- "результат НЕ содержит класс 'track-popup-download'"
- "результат не содержит подстроки '/api/gps-tracks//'"
- id: TC-UNIT-08
title: "XML escaping — name с угловыми скобками и амперсандом"
covers:
ac: [AC-20]
req: [REQ-F-10, NF-05]
given: "track.name = '\"><script>alert(1)</script>'"
when: "build_gpx → bytes → fromstring(...)"
then:
- "ET.fromstring не выбрасывает исключение"
- "metadata/name.text == исходная строка"
- "в bytes нет тега <script (литерально)"
- id: TC-UNIT-09
title: "gpx_builder — copyright опущен если нет известного source"
covers:
ac: [AC-08]
req: [REQ-F-08]
given: "track.sources_json = '[\"unknown-source\"]'"
when: "build_gpx"
then:
- "metadata/copyright отсутствует"
- id: TC-UNIT-10
title: "popup HTML — кнопка содержит корректный basePath и id"
covers:
ac: [AC-01, AC-03]
req: [REQ-F-12]
framework_override: jest
given: "window.location.pathname = '/enduro/', feature.properties = { id: 42, name: 'X' }"
when: "_renderTrackPopupHtml(props)"
then:
- "html содержит href='/enduro/api/gps-tracks/42.gpx'"
- "html содержит 'Скачать GPX'"
- "html содержит атрибут download="
integration:
framework: pytest + TestClient
path: tests/integration/
cases:
- id: TC-INT-01
title: "GET .gpx — 200 OK + правильные headers"
covers:
ac: [AC-04]
req: [REQ-F-04]
setup: "в тестовую sqlite вставлен трек id=1 c name='Test', 5 точек"
when: "GET /api/gps-tracks/1.gpx"
then:
- "status == 200"
- "header Content-Type == 'application/gpx+xml; charset=utf-8'"
- "header Content-Disposition содержит 'attachment'"
- "header Content-Disposition содержит 'filename='"
- "header Content-Disposition содержит 'filename*=UTF-8'\"'\"''"
- "header Cache-Control == 'public, max-age=86400'"
- "header Access-Control-Allow-Origin == '*'"
- "body начинается с '<?xml'"
- id: TC-INT-02
title: "Content-Disposition — RFC 5987 UTF-8 для кириллицы"
covers:
ac: [AC-07]
req: [REQ-F-05]
setup: "трек с name='Кольцо вокруг Малинок'"
when: "GET /api/gps-tracks/{id}.gpx"
then:
- "header содержит filename='ascii-fallback'"
- "header содержит filename*=UTF-8''%D0%9A%D0%BE%D0%BB%D1%8C..."
- id: TC-INT-03
title: "GET .gpx — 404 при NULL geom"
covers:
ac: [AC-10]
req: [REQ-F-03]
setup: "трек id=2 с geom=NULL"
when: "GET /api/gps-tracks/2.gpx"
then:
- "status == 404"
- "body == {'detail': 'Track not found'}"
- id: TC-INT-04
title: "GET .gpx — 404 при несуществующем id"
covers:
ac: [AC-11]
req: [REQ-F-03]
when: "GET /api/gps-tracks/9999999.gpx"
then:
- "status == 404"
- id: TC-INT-05
title: "GET .gpx — 422 при не-int id"
covers:
ac: [AC-12]
req: [REQ-F-02]
when: "GET /api/gps-tracks/abc.gpx"
then:
- "status == 422"
- id: TC-INT-06
title: "Регрессия — соседние эндпоинты не сломаны"
covers:
ac: [AC-16]
req: [NF-08]
when:
- "GET /api/gps-tracks?bbox=37,55,38,56"
- "GET /api/gps-tracks/tiles/9/297/154.mvt"
- "GET /api/gps-tracks/health"
then:
- "все три отвечают 200 как до изменений"
- "формат ответа идентичен (диф = 0 байт vs baseline)"
- id: TC-INT-07
title: "Cache-Control header выставлен"
covers:
ac: [AC-18]
req: [NF-03]
when: "GET /api/gps-tracks/{id}.gpx → headers"
then:
- "Cache-Control == 'public, max-age=86400'"
- id: TC-INT-08
title: "GPX скачанный с сервера проходит XSD-валидацию"
covers:
ac: [AC-04]
req: [REQ-F-06]
setup: "произвольный трек id=1; локальная копия GPX 1.1 XSD в tests/fixtures/gpx11.xsd"
when: "GET .gpx → xmlschema.validate(body, xsd)"
then:
- "валидация успешна (нет ошибок)"
e2e:
framework: playwright
path: tests/e2e/
base_url: https://openclaw.mva154.duckdns.org/enduro/
cases:
- id: TC-E2E-01
title: "Download → upload roundtrip"
covers:
ac: [AC-13]
req: [REQ-F-04, REQ-F-09]
steps:
- "включить слой 'Публичные треки'"
- "зум до 14 на координатах с известными треками"
- "кликнуть на трек"
- "перехватить URL загрузки из href кнопки 'Скачать GPX'"
- "скачать файл через page.context().request().get(url)"
- "пройти через парсер gpx.js (загрузить через input[type=file] '#gpx-file')"
then:
- "появляется sheet-gpx с одним треком"
- "имя трека совпадает с popup.name"
- "длина совпадает (±1%) с popup.length_km"
- id: TC-E2E-02
title: "Сетевая ошибка → toast"
covers:
ac: [AC-15]
req: [REQ-F-17]
steps:
- "route('**/api/gps-tracks/*.gpx', route => route.fulfill({status:500}))"
- "включить слой, кликнуть на трек"
- "кликнуть 'Скачать GPX'"
then:
- "появляется toast 'Не удалось скачать трек'"
- "popup НЕ закрылся"
perf:
framework: pytest + httpx + statistics
path: tests/perf/
cases:
- id: TC-PERF-01
title: "GET .gpx — p95 latency"
covers:
ac: [AC-17]
req: [NF-01]
setup: "трек id=N с 5000 точек"
when: "100 sequential GET .gpx"
then:
- "p95 ≤ 500 ms"
- "максимум ≤ 1000 ms"
# Итоговые счётчики
totals:
unit: 10
integration: 8
e2e: 2
perf: 1
ui_visual: see 04b-ui-test-cases.md
# Тестовые фикстуры
fixtures:
- path: tests/fixtures/gps_tracks_test.sqlite
description: "тестовая БД с 5 треками: 1 osm, 1 wikiloc, 1 enduro_russia, 1 без имени, 1 с NULL geom"
- path: tests/fixtures/gpx11.xsd
description: "официальная XSD-схема GPX 1.1 (скачана с topografix.com при подготовке тестов)"
- path: tests/fixtures/perf_track_5000.json
description: "трек 5000 точек для perf-теста"