test(ET-008): 132/132 pass, back-to:dev for F-04/F-05/web-tests
This commit is contained in:
247
docs/work-items/ET-008/13-test-report.md
Normal file
247
docs/work-items/ET-008/13-test-report.md
Normal file
@@ -0,0 +1,247 @@
|
||||
---
|
||||
type: test-report
|
||||
work_item_id: ET-008
|
||||
title: "Test Report: GPS-треки с публичных платформ на карте"
|
||||
version: 1
|
||||
status: fail
|
||||
created_at: 2026-06-01
|
||||
updated_at: 2026-06-01
|
||||
authors:
|
||||
- "agent:tester"
|
||||
tested_branch: feature/ET-008-gps
|
||||
tested_commits:
|
||||
- edbe9a3 "fix(gps-tracks): normalise GeoJSON props, add health fields, OSM meta fetch, z-order fix"
|
||||
- 3734b98 "feat(ET-008): GPS tracks pipeline, API, frontend layer"
|
||||
verdict: back-to:dev
|
||||
---
|
||||
|
||||
# Test Report — ET-008: GPS-треки с публичных платформ на карте
|
||||
|
||||
## Вердикт: **back-to:dev**
|
||||
|
||||
Две P1-находки из `12-review.md` закрыты **частично** — тесты `make test`
|
||||
зелёные, но контракт health endpoint и корректность z-order fix не
|
||||
соответствуют ТЗ и AC. JS-тесты frontend отсутствуют. Деплой на тест-стенд
|
||||
не произведён → E2E и UI тесты пройти невозможно.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 1 — Проверка окружения
|
||||
|
||||
| Endpoint | Статус |
|
||||
|---|---|
|
||||
| `GET /enduro/api/health` | ✅ `{"status":"ok","db_exists":true}` |
|
||||
| `GET /enduro/api/gps-tracks/health` | ❌ `404 Not Found` — ET-008 не задеплоен |
|
||||
| `GET /enduro/api/gps-tracks?bbox=…` | ❌ `404 Not Found` — ET-008 не задеплоен |
|
||||
| `GET /enduro/api/tiles/10/…mvt` | ✅ `200 OK` (существующий эндпойнт) |
|
||||
|
||||
**Вывод:** тест-среда работает, но ветка `feature/ET-008-gps` не выкачена
|
||||
на стенд — проверки API и E2E через браузер провести невозможно.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 2 — Функциональные тесты (`make test → pytest`)
|
||||
|
||||
```
|
||||
cd src/api && python -m pytest ../../tests/ -v
|
||||
```
|
||||
|
||||
**Результат: 132 passed, 0 failed, 7 warnings**
|
||||
|
||||
| Сюита | Тестов | Результат |
|
||||
|---|---|---|
|
||||
| `test_gps_tracks_dedup.py` | 8 | ✅ PASS |
|
||||
| `test_gps_tracks_endpoint.py` | 13 | ✅ PASS |
|
||||
| `test_gps_tracks_mvt.py` | 8 | ✅ PASS |
|
||||
| `test_gps_tracks_sources_osm.py` | 16 | ✅ PASS |
|
||||
| `test_routing_barriers.py` | 7 | ✅ PASS |
|
||||
| `test_base_layer.py` + `test_gpx_upload.py` + `test_poi_toggle.py` + `test_unit_toggle.py` | 80 | ✅ PASS |
|
||||
|
||||
Предупреждения (7 шт.): `DeprecationWarning` в `mapbox_vector_tile.encode`
|
||||
— не критично, библиотека внешняя.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 3 — E2E тесты (Playwright)
|
||||
|
||||
**SKIP** — Playwright не установлен в окружении
|
||||
(`/home/slin/tools/ui-test/run_tests.js` отсутствует, `playwright`
|
||||
в `$PATH` не найден). Папка `tests/e2e/` содержит только шаблон
|
||||
`TEST_CASES_TEMPLATE.md` без реализованных сценариев.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 4 — UI / Visual тесты
|
||||
|
||||
**SKIP** — Раннер `/home/slin/tools/ui-test/run_tests.js` не найден
|
||||
(`Error: Cannot find module`). ET-008 не задеплоен на тест-среду
|
||||
`https://openclaw.mva154.duckdns.org/enduro/`, поэтому скриншоты
|
||||
TC-UI-01…TC-UI-20 не сделаны.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 5 — Проверка фиксов из `12-review.md` (code inspection + тесты)
|
||||
|
||||
### Итоговая таблица
|
||||
|
||||
| Finding (review) | Severity | Статус | Вердикт |
|
||||
|---|---|---|---|
|
||||
| F-01: GeoJSON properties не совместимы с MVT | P0 | ✅ Исправлено | PASS |
|
||||
| F-02: `length_m` вместо `length_km` в GeoJSON | P1 | ✅ Исправлено | PASS |
|
||||
| F-03: OSM batch-fetch метаданных не реализован | P1 | ✅ Исправлено | PASS |
|
||||
| F-04: Health endpoint несовместим с REQ-F-12/AC-06 | P1 | ⚠️ Частично | FAIL |
|
||||
| F-05: Z-order `gps-tracks-layer` vs `gpx-layer-*` | P1 | ⚠️ Частично | WARN |
|
||||
| F-06: Нет валидации площади bbox | P2 | ❌ Не исправлено | — |
|
||||
| F-07: Дефолт источников включает disabled-источники | P2 | ❌ Не исправлено | — |
|
||||
| F-08: LRU-кэш на самом деле FIFO | P2 | ❌ Не исправлено | — |
|
||||
| F-09…F-12: P3-находки | P3 | ❌ Не исправлены | — |
|
||||
| `tests/web/gps_tracks.test.js` отсутствует | — | ❌ Не создан | FAIL |
|
||||
|
||||
---
|
||||
|
||||
### F-01 [P0] → **ИСПРАВЛЕНО** ✅
|
||||
|
||||
Коммит `edbe9a3` добавил в `_row_to_geojson_feature()`:
|
||||
```python
|
||||
"activity": activity_type, # alias для MVT-совместимости
|
||||
"source": first_source, # alias для MVT-совместимости
|
||||
```
|
||||
Тест `test_f01_f02_geojson_normalised_properties` проходит. GeoJSON-
|
||||
features на z ≥ 12 теперь несут поля `activity` и `source` → `applyGpsFilter()`
|
||||
работает корректно.
|
||||
|
||||
---
|
||||
|
||||
### F-02 [P1] → **ИСПРАВЛЕНО** ✅
|
||||
|
||||
В `_row_to_geojson_feature()`:
|
||||
```python
|
||||
length_km = round(length_m / 1000, 2)
|
||||
...
|
||||
"length_km": length_km,
|
||||
```
|
||||
Тест `test_f01_f02_geojson_normalised_properties` проверяет наличие и
|
||||
корректность `length_km`.
|
||||
|
||||
---
|
||||
|
||||
### F-03 [P1] → **ИСПРАВЛЕНО** ✅
|
||||
|
||||
В `src/api/gps_tracks/sources/osm.py` реализован `_batch_fetch_gpx_meta()`:
|
||||
- накапливает gpx_ids из страницы трекпойнтов;
|
||||
- batch_size=20, параллельные запросы через `asyncio.gather`;
|
||||
- `_parse_gpx_meta_response()` парсит ответ через defusedxml и маппит
|
||||
теги через `OsmParser.MAPPING`;
|
||||
- результат записывается в `track.activity_type`, `name`, `description`, `user`.
|
||||
|
||||
Тесты `test_u45_*` (5 новых) проходят, включая проверку маппингов
|
||||
`'enduro' → 'enduro'`, `'motorcycle' → 'moto'`, неизвестный тег → `None`.
|
||||
|
||||
---
|
||||
|
||||
### F-04 [P1] → **ЧАСТИЧНО ИСПРАВЛЕНО** ⚠️ → **FAIL**
|
||||
|
||||
**Что добавлено:** `db_size_mb`, `tracks_by_source`, `tile_cache_size` —
|
||||
теперь присутствуют. Тест `test_f04_health_extended_fields` проходит.
|
||||
|
||||
**Что осталось не исправлено:**
|
||||
|
||||
| Поле в коде | Требование REQ-F-12 / AC-06 | Статус |
|
||||
|---|---|---|
|
||||
| `total_tracks` | `tracks_total` | ❌ имя не совпадает |
|
||||
| `by_activity` | `tracks_by_activity` | ❌ имя не совпадает |
|
||||
| `recent_pipeline_runs` (list 10) | `last_pipeline_run` (object) | ❌ тип и имя не совпадают |
|
||||
|
||||
Тест `test_i40_health_endpoint` (строки 316–319) фиксирован на **неверной**
|
||||
схеме и проходит, маскируя несоответствие. AC-06 Scenario «Полный отчёт»
|
||||
не пройдёт при любой автоматической проверке по ТЗ.
|
||||
|
||||
**Пример нарушения AC-06:**
|
||||
```
|
||||
# AC-06 ожидает:
|
||||
assert "tracks_total" in data # ← FAIL: ключ "total_tracks"
|
||||
assert "tracks_by_activity" in data # ← FAIL: ключ "by_activity"
|
||||
assert isinstance(data["last_pipeline_run"], dict) # ← FAIL: список
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### F-05 [P1] → **ЧАСТИЧНО ИСПРАВЛЕНО** ⚠️ → **WARN**
|
||||
|
||||
`_findGpsInsertPosition` в коммите `edbe9a3`:
|
||||
```js
|
||||
const routeLayer = style.layers.find(l =>
|
||||
l.id === 'route-line' ||
|
||||
l.id.startsWith('route-') ||
|
||||
l.id.startsWith('gpx-layer-')
|
||||
);
|
||||
return routeLayer ? routeLayer.id : undefined;
|
||||
```
|
||||
|
||||
**Проблема:** используется один `find` с `OR` вместо приоритетного поиска.
|
||||
Рекомендованный fix из review:
|
||||
```js
|
||||
const gpxLayer = style.layers.find(l => l.id.startsWith('gpx-layer'));
|
||||
if (gpxLayer) return gpxLayer.id;
|
||||
const routeLayer = style.layers.find(l => l.id === 'route-line' || ...);
|
||||
return routeLayer ? routeLayer.id : undefined;
|
||||
```
|
||||
|
||||
**Риск:** если в массиве `style.layers` `route-line` стоит перед
|
||||
`gpx-layer-*` (edge case — например, маршрут был построен до загрузки
|
||||
GPX), `find` вернёт `route-line`. Тогда `gps-tracks-layer` вставляется
|
||||
ниже `route-line`, но потенциально выше `gpx-layer-*`, нарушая AC-10
|
||||
Scenario «личный трек визуально выше публичных».
|
||||
|
||||
В обычном случае (GPX загружен, маршрут не построен) работает корректно.
|
||||
Unit-тест на edge case не добавлен.
|
||||
|
||||
**Вердикт: WARN** — не блокирует типичный сценарий, но AC-10 не покрыт.
|
||||
|
||||
---
|
||||
|
||||
### `tests/web/gps_tracks.test.js` → **ОТСУТСТВУЕТ** ❌ → **FAIL**
|
||||
|
||||
Файл не создан. Он был обязательным по review и test-plan:
|
||||
- `04-test-plan.yaml`: тест-сюита `unit-color-palette` (U-60…U-62), `integration-web-layer` (I-50…I-57)
|
||||
- `12-review.md`: «Был бы первой защитой от F-01. Добавить в test-plan.»
|
||||
|
||||
Без frontend unit-тестов нарушения типа F-01 не будут пойманы в CI.
|
||||
|
||||
---
|
||||
|
||||
## Оставшиеся открытые P2/P3 из review (не блокируют, follow-up)
|
||||
|
||||
| Finding | Описание | Рекомендация |
|
||||
|---|---|---|
|
||||
| F-06 [P2] | Нет валидации площади bbox | Добавить max area check (например, 100 deg²) |
|
||||
| F-07 [P2] | Дефолт sources включает disabled | Инициализировать из `/api/gps-tracks/health.tracks_by_source.keys()` |
|
||||
| F-08 [P2] | LRU-кэш — на самом деле FIFO | Переписать на `OrderedDict.move_to_end` |
|
||||
| F-09 [P3] | `save_user_field` в YAML не читается кодом | Обработать в upsert |
|
||||
| F-10 [P3] | Лишний `import pytest_asyncio` | Убрать |
|
||||
| F-11 [P3] | `MockRow(dict)` вместо `sqlite3.Row` | Рефактор в тестах |
|
||||
| F-12 [P3] | Лишняя проверка `"source_priority" in existing.keys()` | Упростить |
|
||||
|
||||
---
|
||||
|
||||
## Что нужно исправить для прохождения тестирования
|
||||
|
||||
**Минимум для re-test (P1):**
|
||||
|
||||
1. **F-04**: Переименовать поля health endpoint:
|
||||
- `total_tracks` → `tracks_total`
|
||||
- `by_activity` → `tracks_by_activity`
|
||||
- `recent_pipeline_runs` (list) → `last_pipeline_run` (object, последний запуск
|
||||
с агрегацией `sources_ok`/`sources_error`)
|
||||
- Обновить тесты `test_i40_*` под правильную схему.
|
||||
|
||||
2. **F-05**: Переписать `_findGpsInsertPosition` с приоритетным поиском
|
||||
(сначала `gpx-layer-*`, затем `route-*`). Добавить unit-тест на edge case.
|
||||
|
||||
3. **Создать `tests/web/gps_tracks.test.js`** с покрытием:
|
||||
- `applyGpsFilter` с GeoJSON-feature (`activity_type`) не фильтрует лишнее
|
||||
- `_buildColorExpression` — осмысленные цвета для GeoJSON и MVT features
|
||||
- `_findGpsInsertPosition` — приоритет gpx-layer
|
||||
|
||||
4. **Задеплоить ветку на тест-стенд** и повторить smoke-тесты API +
|
||||
запустить Playwright UI-тесты TC-UI-01…TC-UI-20.
|
||||
Reference in New Issue
Block a user