From 7c9cb37ecd7e227937378253056e6f64fc2c7db3 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 1 Jun 2026 14:24:27 +0000 Subject: [PATCH] test(ET-008): round 2 - F-04 partial, E2E blocked (not deployed) --- docs/work-items/ET-008/13-test-report.md | 285 +++++++++++------------ 1 file changed, 132 insertions(+), 153 deletions(-) diff --git a/docs/work-items/ET-008/13-test-report.md b/docs/work-items/ET-008/13-test-report.md index 5724b00..646474a 100644 --- a/docs/work-items/ET-008/13-test-report.md +++ b/docs/work-items/ET-008/13-test-report.md @@ -2,7 +2,7 @@ type: test-report work_item_id: ET-008 title: "Test Report: GPS-треки с публичных платформ на карте" -version: 1 +version: 2 status: fail created_at: 2026-06-01 updated_at: 2026-06-01 @@ -10,19 +10,21 @@ authors: - "agent:tester" tested_branch: feature/ET-008-gps tested_commits: + - ba356ae "fix(gps-tracks): rename health fields and fix layer insert priority (F-04, F-05)" - 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-треки с публичных платформ на карте +# Test Report — ET-008: GPS-треки с публичных платформ на карте (v2) ## Вердикт: **back-to:dev** -Две P1-находки из `12-review.md` закрыты **частично** — тесты `make test` -зелёные, но контракт health endpoint и корректность z-order fix не -соответствуют ТЗ и AC. JS-тесты frontend отсутствуют. Деплой на тест-стенд -не произведён → E2E и UI тесты пройти невозможно. +Со времени v1-отчёта появился коммит `ba356ae`: закрыты три оставшихся +P1-пункта (F-04, F-05, JS-тесты). Все 141 pytest и 22 JS unit-теста +проходят. Однако у `last_pipeline_run` структура по-прежнему не +соответствует REQ-F-12 (нет `regions`, `sources_ok`, `sources_error`), а +E2E/UI тесты не выполнимы — бэкенд ET-008 не задеплоен на тест-стенд. --- @@ -35,213 +37,190 @@ verdict: back-to:dev | `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 через браузер провести невозможно. +Фронтенд: HTML тест-стенда содержит `#public-tracks-cb` и `gps_tracks.js` — +статика задеплоена. Бэкенд-роуты `/api/gps-tracks/*` отвечают `404` → +API-смок и E2E невозможны. --- -## Шаг 2 — Функциональные тесты (`make test → pytest`) +## Шаг 2 — Функциональные тесты (`pytest tests/`) ``` -cd src/api && python -m pytest ../../tests/ -v +python -m pytest tests/ -v --tb=short ``` -**Результат: 132 passed, 0 failed, 7 warnings** +**Результат: 141 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_gps_tracks_endpoint.py` | 15 | ✅ PASS | +| `test_gps_tracks_mvt.py` | 9 | ✅ PASS | +| `test_gps_tracks_sources_osm.py` | 21 | ✅ 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 | +| `test_base_layer.py` + `test_gpx_upload.py` + прочие unit | 80 | ✅ PASS | +| `tests/web/test_gps_tracks.py` | 9 | ✅ PASS | -Предупреждения (7 шт.): `DeprecationWarning` в `mapbox_vector_tile.encode` -— не критично, библиотека внешняя. +Предупреждения (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` без реализованных сценариев. +**SKIP** — Playwright и `tests/e2e/` с реализованными сценариями ET-008 +отсутствуют. Смотри также Шаг 1: бэкенд-апи не поднят на стенде. --- ## Шаг 4 — UI / Visual тесты -**SKIP** — Раннер `/home/slin/tools/ui-test/run_tests.js` не найден -(`Error: Cannot find module`). ET-008 не задеплоен на тест-среду -`https://openclaw.mva154.duckdns.org/enduro/`, поэтому скриншоты +**SKIP** — `/home/slin/tools/ui-test/run_tests.js` не найден; бэкенд ET-008 +недоступен на `https://openclaw.mva154.duckdns.org/enduro/`. Скриншоты TC-UI-01…TC-UI-20 не сделаны. --- -## Шаг 5 — Проверка фиксов из `12-review.md` (code inspection + тесты) +## Шаг 5 — JS unit-тесты (`node --test`) -### Итоговая таблица - -| 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, +node --test tests/web/gps_tracks.test.js ``` -Тест `test_f01_f02_geojson_normalised_properties` проверяет наличие и -корректность `length_km`. ---- +**Результат: 22 passed, 0 failed** -### 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: `_findGpsInsertPosition` приоритетный поиск | 9 | ✅ PASS | +| Filters: начальное состояние `window.gpsTracksLayer` | 5 | ✅ PASS | +| Colors: палитра источников, активностей, fallback | 8 | ✅ PASS | --- -### F-05 [P1] → **ЧАСТИЧНО ИСПРАВЛЕНО** ⚠️ → **WARN** +## Шаг 6 — Верификация фиксов из `12-review.md` -`_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; +### Итоговая таблица (v2) + +| Finding | Severity | v1 | v2 | Вердикт | +|---|---|---|---|---| +| F-01: GeoJSON props несовместимы с MVT | P0 | PASS | PASS | ✅ PASS | +| F-02: `length_m` вместо `length_km` в GeoJSON | P1 | PASS | PASS | ✅ PASS | +| F-03: OSM batch-fetch не реализован | P1 | PASS | PASS | ✅ PASS | +| F-04: Health endpoint несовместим с REQ-F-12 | P1 | FAIL | ⚠️ WARN | WARN | +| F-05: Z-order vs `gpx-layer-*` | P1 | WARN | PASS | ✅ PASS | +| `tests/web/gps_tracks.test.js` отсутствует | — | FAIL | PASS (22 тест) | ✅ PASS | +| F-06: Нет валидации площади bbox | P2 | — | ❌ Не исправлено | follow-up | +| F-07: Дефолт sources включает disabled | P2 | — | ❌ Не исправлено | follow-up | +| F-08: LRU-кэш на самом деле FIFO | P2 | — | ❌ Не исправлено | follow-up | +| F-09…F-12: P3-находки | P3 | — | ❌ Не исправлены | follow-up | + +--- + +### F-04 [P1] → ⚠️ **WARN** (v1: FAIL) + +**Что исправлено в `ba356ae`:** + +| Поле (до) | Поле (после) | AC-06 | +|---|---|---| +| `total_tracks` | `tracks_total` | ✅ | +| `by_activity` | `tracks_by_activity` | ✅ | +| `recent_pipeline_runs` (list) | `last_pipeline_run` (object \| null) | ✅ ключ есть | +| — | `db_size_mb`, `tracks_by_source`, `tile_cache_size` добавлены | ✅ | + +**Что осталось не соответствует REQ-F-12:** + +`last_pipeline_run` возвращает сырую строку из `pipeline_runs`, а не +агрегированный объект, требуемый ТЗ: + +```python +# Имеется (сырая строка): +{ + "id": 1, + "started_at": "...", "finished_at": "...", + "region_id": "tsfo_plus_chuvashia", # ← скаляр, не список + "source_id": "osm", # ← скаляр, нет sources_ok/sources_error + "status": "ok", "tracks_new": 100, "tracks_updated": 0 +} + +# Требуется REQ-F-12 / AC-06 («объект с started/finished/regions/sources_ok/sources_error»): +{ + "started_at": "...", "finished_at": "...", + "regions": ["tsfo_plus_chuvashia"], # список + "sources_ok": ["osm", "enduro_russia"], # список + "sources_error": [{"source": "ttrails", ...}] # список +} ``` -**Проблема:** используется один `find` с `OR` вместо приоритетного поиска. -Рекомендованный fix из review: +Тест `test_i40_health_endpoint` проверяет только присутствие ключа `last_pipeline_run` +и не валидирует структуру → дефект маскируется. Клиентский и операторский +интерфейс, опирающийся на `regions`/`sources_ok`/`sources_error`, получит `null`/undefined. + +**Что нужно сделать:** +1. Агрегировать строки из `pipeline_runs` за последний цикл прогона в + структуру `{regions, sources_ok, sources_error}`. +2. Обновить тест `test_i40_health_endpoint` так, чтобы он проверял подполя + `last_pipeline_run`. + +--- + +### F-05 [P1] → ✅ **PASS** (v1: WARN) + +`_findGpsInsertPosition` в `ba356ae` переписана с явным приоритетом: + ```js -const gpxLayer = style.layers.find(l => l.id.startsWith('gpx-layer')); +// Priority 1: gpx-layer-* (ET-006 GPX file layers) +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; + +// Priority 2: route-* (ET-002 routing layers) +const routeLayer = style.layers.find(l => l.id.startsWith('route-')); +if (routeLayer) return routeLayer.id; ``` -**Риск:** если в массиве `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 не покрыт. +Проверено 9 unit-тестами в `gps_tracks.test.js`, включая edge case «route-* +перед gpx-layer-* в массиве слоёв» (ранее был риск нарушения AC-10). --- -### `tests/web/gps_tracks.test.js` → **ОТСУТСТВУЕТ** ❌ → **FAIL** +### `tests/web/gps_tracks.test.js` → ✅ **PASS** (v1: 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.» +Файл создан, 22 теста проходят. Покрытие: +- `_findGpsInsertPosition` — 9 case-ов (включая edge case приоритета). +- Состояние `window.gpsTracksLayer` при инициализации — 5 тестов. +- Палитра `GPS_SOURCE_COLORS`, `GPS_ACTIVITY_COLORS`, `GPS_FALLBACK_COLORS` + и `_buildColorExpression` — 8 тестов. -Без frontend unit-тестов нарушения типа F-01 не будут пойманы в CI. +**Новое наблюдение (не блокирует):** `applyGpsFilter` не покрыта тестом. +Функция вызывает `map.setFilter()` с выражением по `activity`/`source` — +именно этот код исправлял F-01. Рекомендуется добавить тест с mock-картой +и GeoJSON-feature `{activity_type:'enduro'}` для регрессии. --- -## Оставшиеся открытые P2/P3 из review (не блокируют, follow-up) +## Открытые P2/P3 — 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-06 [P2] | Нет проверки площади bbox | Добавить max area ≤ 100°² в `_parse_bbox()` | +| F-07 [P2] | Дефолт sources содержит disabled-источники | Инициализировать из `/api/gps-tracks/health.tracks_by_source` при отсутствии localStorage | +| 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-11 [P3] | `MockRow(dict)` вместо `sqlite3.Row` | Рефактор тестов | | F-12 [P3] | Лишняя проверка `"source_priority" in existing.keys()` | Упростить | --- -## Что нужно исправить для прохождения тестирования +## Что нужно для прохождения -**Минимум для re-test (P1):** +**Минимум для re-test:** -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_*` под правильную схему. +1. **F-04 `last_pipeline_run`** — агрегировать поля из `pipeline_runs` в + структуру `{started_at, finished_at, regions, sources_ok, sources_error}`. + Обновить `test_i40_health_endpoint` для проверки подполей. -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. +2. **Задеплоить ветку `feature/ET-008-gps` на тест-стенд** — поднять бэкенд + ET-008 на `https://openclaw.mva154.duckdns.org/enduro/` и запустить: + - Smoke-тест API: `GET /api/gps-tracks/health`, `GET /api/gps-tracks?bbox=…` + - UI-тесты TC-UI-01…TC-UI-20 через раннер (когда `run_tests.js` доступен) + - E2E Playwright: E-01, E-02 (pipeline smoke), E-10…E-12 (filters)