# Acceptance Criteria: Скачивание трека из popup на карте

**Work Item:** ET-011

Формат: Given–When–Then. Каждый AC связан с REQ из `02-trz.md`.

---

## AC-1 — Кнопка появляется в popup трека

**Given** на карте включён слой «Публичные треки» (ET-008) и в видимой
области есть треки

**When** пользователь тапает по линии трека и видит popup

**Then** в popup, помимо имеющихся полей (название, активность, длина и т.д.),
**должна присутствовать кнопка «Скачать»** (иконка ⬇ + tooltip «Скачать GPX»)

**Покрывает:** REQ-F-01

## AC-2 — Скачивание GPX

**Given** popup трека открыт и в нём есть кнопка «Скачать»

**When** пользователь нажимает на кнопку «Скачать»

**Then**
- Браузер инициирует скачивание файла с расширением `.gpx`.
- Имя файла основано на `tracks.name` (см. AC-4).
- Содержимое — валидный GPX 1.1 (см. AC-5).
- Popup при этом не закрывается (или закрывается консистентно по
  решению архитектора).

**Покрывает:** REQ-F-02, REQ-F-03, REQ-F-05

## AC-3 — Backend-эндпоинт возвращает 200

**Given** в БД есть трек с `id=42`

**When** клиент делает `GET /api/gps-tracks/42/download`

**Then**
- Статус 200.
- `Content-Type: application/gpx+xml`.
- `Content-Disposition: attachment; filename="…"; filename*=UTF-8''…`.
- Тело — XML, начинается с `<?xml version="1.0"`, корневой элемент
  `<gpx version="1.1" …>`.

**Покрывает:** REQ-F-02

## AC-4 — Имя файла

**Given** трек называется `По грязи к Чёрному озеру 100км`

**When** клиент скачивает этот трек

**Then**
- `Content-Disposition` содержит `filename*=UTF-8''<urlencoded>.gpx`,
  где `<urlencoded>` — percent-encoded UTF-8 имя трека.
- ASCII-fallback `filename="…"` пустых символов не содержит, длина ≤ 80.
- В случае пустого `tracks.name` имя файла — `track-<id>.gpx`.
- Запрещённые символы (`/ \ : * ? " < > |`) заменены на `_`.

**Покрывает:** REQ-F-04, REQ-NF-05

## AC-5 — Валидность GPX

**Given** скачанный GPX-файл

**When** валидируется по схеме `http://www.topografix.com/GPX/1/1/gpx.xsd`
утилитой `xmllint --schema gpx.xsd file.gpx --noout`

**Then** валидация проходит без ошибок и предупреждений

**Покрывает:** REQ-NF-03

## AC-6 — Импорт в GPS-софт

**Given** GPX-файл, скачанный по AC-2

**When** файл открывается в OsmAnd / Garmin BaseCamp / QGIS / gpx.studio

**Then** трек отображается полностью (число точек совпадает с
`tracks.points_count`), без ошибок парсинга

**Покрывает:** REQ-F-03 (косвенно — через схему GPX 1.1)

> **Тестирование:** AC-6 проверяется вручную как часть smoke-тестов
> приёмки. Автоматизируется опосредованно через AC-5 (валидация по
> схеме).

## AC-7 — Несуществующий трек

**Given** в БД нет трека с `id=99999999`

**When** клиент делает `GET /api/gps-tracks/99999999/download`

**Then** статус 404, JSON `{"detail": "track_not_found"}` (или аналог)

**Покрывает:** REQ-F-02

## AC-8 — Невалидный формат

**Given** запрос `GET /api/gps-tracks/42/download?format=fit`

**When** обработка достигает валидации параметра

**Then** статус 400, тело содержит человекочитаемое описание ошибки

**Покрывает:** REQ-F-02

## AC-9 — Защита от patho-треков

**Given** в БД есть трек с `points_count = 300000`

**When** клиент делает `GET /api/gps-tracks/<id>/download`

**Then** статус 413 `Payload Too Large`

**Покрывает:** REQ-NF-02

## AC-10 — Метаданные источника в GPX

**Given** трек с `sources=["osm"]` и `external_urls=["https://www.openstreetmap.org/way/123"]`

**When** GPX скачан

**Then**
- В `<metadata>` присутствует `<link href="https://www.openstreetmap.org/way/123"><text>Источник: osm</text></link>`.
- Присутствует `<copyright author="Enduro Trails"><license>https://www.openstreetmap.org/copyright</license></copyright>`.

**Покрывает:** REQ-F-03

## AC-11 — Лицензионный фильтр (если Q-1 = «только OSM»)

> Активируется только если Owner закроет Q-1 как ограничительный.

**Given** трек с `sources=["wikiloc"]` (не в whitelist)

**When** клиент делает GET `/api/gps-tracks/<id>/download`

**Then**
- Статус 403.
- Frontend показывает toast «Источник запрещает скачивание…».
- Если `external_urls` непустой — в toast/popup есть ссылка на
  внешний источник.

**Покрывает:** REQ-F-06

## AC-12 — Производительность

**Given** трек с 50 000 точек

**When** клиент делает GET `/api/gps-tracks/<id>/download`

**Then** время от запроса до окончания заголовков ≤ 300 ms (P95 на
test-среде, 4 worker uvicorn)

**Покрывает:** REQ-NF-01

## AC-13 — Mobile UX

**Given** viewport 375×667 (iPhone SE), включён слой публичных треков

**When** пользователь тапает трек

**Then**
- Popup помещается на экране (max-width 300px уже задан).
- Кнопка «Скачать» видна без скролла.
- Тапабельная зона ≥ 32×32 CSS px.

**Покрывает:** REQ-NF-04

## AC-14 — Tooltip / a11y

**Given** popup с кнопкой «Скачать» открыт

**When** screen-reader пользователь фокусируется на кнопке (Tab)

**Then** объявляется текст «Скачать GPX» (через `aria-label` или
текстовый узел)

**Покрывает:** REQ-F-01

## AC-15 — Существующее поведение не сломано

**Given** релиз ET-011 задеплоен

**When** пользователь
- тапает трек → видит popup со всеми старыми полями
- открывает `sheet-gpx` для своих загруженных GPX
- использует слой публичных треков (фильтры, цвета)
- скачивает построенный маршрут через кнопку в `sheet-route`

**Then** все эти потоки работают как прежде, регрессий нет

**Покрывает:** Регрессия (общий принцип, не привязан к одному REQ)
