--- type: test-plan work_item_id: ET-012 title: "Test Plan: Показывать пользовательские треки с зума z5" version: 1 status: draft created_at: 2026-06-04 updated_at: 2026-06-04 authors: - "agent:analyst" related: - "ET-008" - "ET-009" - "ET-011" scope_note: > ET-012 опускает порог видимости слоя публичных GPS-треков с z8 до z5. Изменения локализованы: - backend mvt.py: zoom-tier для z5/z6 (min_length, limit, tolerance); - frontend gps_tracks.js: константа GPS_TRACKS_MIN_ZOOM=5, line-width stops для z5 в основном слое и halo; - index.html: текст hint «Зум 5+». Тест-план фокусируется на: (1) корректности новых zoom-tier'ов в build_gps_mvt и _simplify_coords; (2) что endpoint отдаёт нормально-размерные MVT на z5-z7; (3) что клиент действительно показывает слой на z5-z7; (4) что регрессий ET-008/009/011 нет; (5) что производительность не уплыла. test_suites: - name: unit-mvt-zoom-tiers type: unit description: "Тиры min_length_m и limit в build_gps_mvt по зумам" cases: - id: UT-Z5-01 name: "z=5: треки < 10 км отфильтровываются" input: | Mock rows: 10 треков, длина = [500, 2000, 3000, 8000, 12000, 15000, 25000, 50000, 80000, 120000]. Вызов build_gps_mvt(rows, z=5, x=19, y=9). expected: | В MVT попадают только треки длиной >= 10000 м, т.е. ровно 6 features. - id: UT-Z5-02 name: "z=5: limit=1500" input: | Mock rows: 2000 треков длиной 15 км каждый (все пройдут min_length). expected: | В MVT попадают первые 1500 features, остальные отбрасываются. - id: UT-Z6-01 name: "z=6: треки < 5 км отфильтровываются" input: | Mock rows: 5 треков, длина = [1000, 3000, 5000, 7000, 10000]. expected: | В MVT 3 features (5000, 7000, 10000). - id: UT-Z6-02 name: "z=6: limit=2000" input: | Mock rows: 2500 треков длиной 6 км каждый. expected: | В MVT 2000 features. - id: UT-Z7-01 name: "z=7: регрессия — поведение до ET-012" input: | Mock rows: 4 трека [1000, 2000, 3000, 5000]. expected: | В MVT 3 features (2000, 3000, 5000), как раньше. - id: UT-Z8-01 name: "z=8: регрессия — нет min_length-фильтра" input: | Mock rows: 4 трека [500, 1000, 2000, 5000]. expected: | В MVT 4 features, limit=8000. - id: UT-Z12-01 name: "z=12: регрессия — limit=25000, без min_length" input: | Mock rows: 100 треков любой длины. expected: | В MVT 100 features. - name: unit-mvt-simplify type: unit description: "Tolerance Douglas-Peucker по зумам в _simplify_coords" cases: - id: UT-SIMP-Z5-01 name: "z=5: прямая линия 100 точек → ≤ 5 точек" input: | coords = [(37.0 + i*0.001, 55.0 + i*0.001) for i in range(100)] (приблизительно прямая ~10 км по диагонали) expected: | len(_simplify_coords(coords, 5)) <= 5 - id: UT-SIMP-Z5-02 name: "z=5: зигзаг с амплитудой < tolerance → 2 точки" input: | coords = зигзаг 100 точек, амплитуда 0.01° (~1 км) expected: | len(_simplify_coords(coords, 5)) == 2 (только концы) - id: UT-SIMP-Z6-01 name: "z=6: зигзаг 5 км → видны крупные пики" input: | coords = зигзаг 100 точек, амплитуда 0.05° (~5 км) expected: | len(_simplify_coords(coords, 6)) > 5 - id: UT-SIMP-Z7-01 name: "z=7: регрессия — tolerance = 0.008" input: | coords = зигзаг 100 точек, амплитуда 0.005° (~500 м) expected: | len(_simplify_coords(coords, 7)) близок к до-ET-012 значению (округлённо в пределах +/-1). - id: UT-SIMP-Z10-01 name: "z=10: регрессия — tolerance = 0.0005" input: | coords = зигзаг 100 точек, амплитуда 0.001° (~100 м) expected: | Поведение совпадает с до-ET-012 (контрольный snapshot). - id: UT-SIMP-Z12-01 name: "z=12: регрессия — без упрощения" input: | coords = 100 точек expected: | _simplify_coords(coords, 12) is coords (или эквивалент) - id: UT-SIMP-EDGE-01 name: "Слишком мало точек → возвращаем как есть" input: | coords = [(37.0, 55.0), (37.001, 55.001)] (2 точки) expected: | На любом zoom — функция возвращает [(37.0, 55.0), (37.001, 55.001)]. - id: UT-SIMP-EDGE-02 name: "DP схлопнул до < 2 точек → возвращаем оригинал" input: | coords = 100 одинаковых точек (вырожденный трек) expected: | Функция возвращает оригинальный coords, не пустой список. - name: integration-tile-endpoint type: integration description: "Endpoint /api/gps-tracks/tiles/{z}/{x}/{y}.mvt на z=5..7" cases: - id: IT-Z5-01 name: "Тайл z=5 над Москвой: 200, тело > 0, < 200 KB" input: | Test DB: 50 треков по ЦФО, длина 12..30 км каждый. GET /api/gps-tracks/tiles/5/19/9.mvt expected: | status 200, Content-Type 'application/x-protobuf', 0 < len(body) < 200_000 - id: IT-Z5-02 name: "Тайл z=5 с большой БД: limit держит размер" input: | Test DB: 200 треков по ЦФО, длина 12..30 км. GET /api/gps-tracks/tiles/5/19/9.mvt expected: | status 200, len(body) < 200_000, mapbox_vector_tile.decode(body)['gps_tracks']['features'] <= 1500 - id: IT-Z5-03 name: "Тайл z=5 над пустым регионом: пустое тело" input: | Test DB: те же 50 треков по ЦФО. GET /api/gps-tracks/tiles/5/4/12.mvt (Тихий океан) expected: | status 200, len(body) == 0 - id: IT-Z6-01 name: "Тайл z=6 над Москвой: больше фич, чем z=5" input: | Test DB: 100 треков, длина 4..20 км. GET /api/gps-tracks/tiles/6/38/19.mvt expected: | status 200, features_count(z=6) >= features_count(z=5) для того же региона, len(body) < 200_000 - id: IT-Z7-01 name: "Тайл z=7 над Москвой: регрессия + плюс короткие треки" input: | GET /api/gps-tracks/tiles/7/77/39.mvt с теми же 100 треками. expected: | status 200, features_count(z=7) >= features_count(z=6), features_count(z=7) <= 3000 - id: IT-CACHE-01 name: "LRU-кэш: второй запрос — X-Cache: HIT" input: | GET /api/gps-tracks/tiles/5/19/9.mvt дважды подряд. expected: | 1-й ответ: header X-Cache: MISS. 2-й ответ: header X-Cache: HIT, тело идентично 1-му. - id: IT-CACHE-02 name: "Сброс кэша через /cache/clear" input: | GET tiles/5/19/9.mvt → POST /api/gps-tracks/cache/clear → GET tiles/5/19/9.mvt expected: | 1-й ответ MISS, 2-й (после clear) MISS. - id: IT-REGRESS-Z8-01 name: "Регрессия z=8: контракт MVT не изменился" input: | GET /api/gps-tracks/tiles/8/154/79.mvt на тестовой БД. (Тайл-координаты выбраны над Москвой.) expected: | features_count(z=8) точно совпадает с snapshot до ET-012 (записывается в tests/fixtures/gps-tracks/mvt-z8-snapshot.json). - id: IT-REGRESS-Z10-01 name: "Регрессия z=10" input: | GET /api/gps-tracks/tiles/10/617/319.mvt expected: | features_count(z=10) совпадает с snapshot до ET-012. - id: IT-VALID-01 name: "z вне диапазона — 400" input: | GET /api/gps-tracks/tiles/-1/0/0.mvt и tiles/23/0/0.mvt expected: | status 400, detail 'Invalid z' - name: integration-api-geojson-cutoff type: integration description: "GeoJSON-слой не изменился" cases: - id: IT-GEO-01 name: "GET /api/gps-tracks?bbox=… работает как раньше" input: | GET /api/gps-tracks?bbox=37,55,38,56&limit=500 expected: | status 200, FeatureCollection с features, total_in_bbox, returned, truncated — контракт идентичен ET-009. - name: performance type: performance description: "Производительность build_gps_mvt на z=5" marker: "@pytest.mark.perf" cases: - id: PERF-Z5-01 name: "build_gps_mvt на z=5 при 500 треках" input: | Test DB: 500 треков длиной 12-25 км по ЦФО. 10 повторных вызовов build_gps_mvt(rows, 5, 19, 9). expected: | avg time <= 200 ms, p95 time <= 500 ms на CI-runner (метрика M-6). - id: PERF-Z5-02 name: "build_gps_mvt на z=5 при 5000 треках (стресс)" input: | Test DB: 5000 треков, разные длины. 5 повторных вызовов. expected: | p95 time <= 1500 ms. - id: PERF-ENDPOINT-01 name: "Endpoint p95 на z=5 (cold)" input: | 10 cold-запросов tile-endpoint (после cache clear) на test-БД. expected: | p95 <= 700 ms. - id: PERF-ENDPOINT-02 name: "Endpoint p95 на z=5 (hot, кэш)" input: | 100 повторных запросов одного тайла после прогрева. expected: | p95 <= 50 ms. - name: regression-existing type: regression description: "Регрессия ET-008 / ET-009 / ET-011" cases: - id: RG-08-01 name: "Все unit-тесты ET-008 проходят" input: "pytest tests/unit/test_gps_*.py -v (за исключением новых ET-012)" expected: "exit-code 0" - id: RG-09-01 name: "Все unit-тесты ET-009 (parser EnduroRussia/Wikiloc)" input: "pytest tests/unit/test_gps_tracks_enduro_russia.py tests/unit/test_gps_tracks_wikiloc.py -v" expected: "exit-code 0" - id: RG-11-01 name: "Тесты ET-011 download GPX" input: "pytest tests/integration/test_gps_download.py -v" expected: "exit-code 0" - id: RG-INT-01 name: "Все integration-тесты" input: "pytest tests/integration/ -v" expected: "exit-code 0" - name: ui-playwright type: ui description: "Playwright UI-тесты на test-среде" reference: "04b-ui-test-cases.md" cases: - id: UI-LINK-01 name: "См. 04b-ui-test-cases.md — TC-UI-01-Z5..TC-UI-12-Z5-Q" expected: "Каждый TC выполняется и check-visual подтверждается оператором." - name: manual-deploy-validation type: e2e description: "Ручная проверка в test-среде после деплоя" marker: "manual" cases: - id: E2E-DEPLOY-01 name: "Включить слой и поставить zoom=5" steps: - "Открыть https://openclaw.mva154.duckdns.org/enduro/" - "Open DevTools, в Console: localStorage.clear() для чистого старта" - "Click #terrain-toggle" - "Click #public-tracks-cb (включить)" - "В Console: window._map.setZoom(5); window._map.setCenter([37.6, 55.7])" - "Ждать 3 секунды" - "Visual: видны линии публичных треков" - "Зафиксировать скриншот в 14-deploy-log.md" - id: E2E-DEPLOY-02 name: "Network: размер тайла z=5" steps: - "В DevTools Network отфильтровать по 'tiles/5'" - "Проверить: каждый ответ ≤ 200 KB (Size column)" - "Зафиксировать в 14-deploy-log.md" - id: E2E-DEPLOY-03 name: "Уменьшить зум до z=4 — hint показывается" steps: - "window._map.setZoom(4)" - "Visual: hint 'Зум 5+' появился" - "На карте нет линий публичных треков" - id: E2E-DEPLOY-04 name: "Зум z=12 — переход на GeoJSON" steps: - "window._map.setZoom(12)" - "Wait 1.5s" - "В DevTools Network отфильтровать по '/api/gps-tracks?bbox'" - "Запрос ушёл, status 200" - "На карте видны линии, но из GeoJSON-source (gps-tracks-layer-geo)" - id: E2E-DEPLOY-05 name: "Регрессия: popup и скачивание GPX" steps: - "window._map.setZoom(8)" - "Кликнуть по треку из источника osm" - "Popup открылся, в нём есть кнопка 'Скачать GPX'" - "Клик по кнопке скачивает .gpx файл (ET-011 контракт)" test_data: fixtures_dir: "tests/fixtures/gps-tracks/" fixtures: - name: "mvt-z8-snapshot.json" description: "Snapshot число features в тайле z=8/154/79 над Москвой до ET-012 (для IT-REGRESS-Z8-01)" - name: "mvt-z10-snapshot.json" description: "Аналогично для z=10/617/319 (IT-REGRESS-Z10-01)" notes: - "Snapshot'ы создаются разово до начала разработки ET-012 на текущем состоянии test-БД и кладутся в репо." - "Для unit-тестов использовать sqlite3.Row mock — реальная БД не нужна." test_environment: unit: - "pytest tmp_path для временной sqlite (по необходимости)" - "Mock sqlite3.Row через unittest.mock или фабрика" integration: - "Test sqlite БД с фикстурными треками из existing ET-008/009 фабрик" - "FastAPI TestClient для endpoint вызовов" performance: - "Маркер @pytest.mark.perf, не в обычном CI" - "Запуск перед merge: pytest -m perf" e2e: - "Test-среда https://openclaw.mva154.duckdns.org/enduro/" - "Реальная БД после ET-009 прогона" - "UI-тесты — см. 04b-ui-test-cases.md (Playwright)" ci_gates: - "Unit-тесты UT-Z*-* и UT-SIMP-* — обязательны (AC-11, AC-12)" - "Integration IT-Z*-*, IT-CACHE-*, IT-REGRESS-* — обязательны (AC-13)" - "Регрессия RG-* — обязательна (AC-14)" - "Performance PERF-Z5-01 — обязателен перед merge (AC-19)" - "UI-тесты — запуск после деплоя, фиксация в 13-test-report.md" - "E2E-DEPLOY-* — ручные шаги в 14-deploy-log.md" ---