Repository navigation
[Testing] Roadmap автоматического покрытия: PHPStan в CI, PHPUnit, интеграционные тесты #429
Description
Описание
Roadmap по автоматическому покрытию MiniShop3. Дополняет уже открытый #395 (PHPUnit-каркас + первые unit-тесты на чистые функции) — здесь общая картина: что покрываем, в каком порядке, с приоритетом по риску.
Контекст. После #394 (CI-гейт: php -l + smoke + ESLint) на PR теперь есть автопроверка, включён branch protection на beta. Но текущее покрытие — 5 standalone smoke-скриптов на чистые функции (charset-нормализация, URL email-verify, CSV option parse, direct-filter-контракт). Реальные баги последних 2 месяцев (repeater save-drop, inline-edit type mismatch, null payload semantics) сидели в stateful-логике с БД, которую smoke-тесты в принципе не достают.
Уровень 0 — PHPStan в CI (highest priority, дешёвый)
PHPStan уже настроен в проекте (phpstan.neon + phpstan-baseline.neon, level 5). Мы гоняем его вручную на каждом ревью — это главный ловец регрессий типов/сигнатур. Он не в CI-гейте из #394 (автор явно оставил follow-up).
- Добавить job
phpstanв.github/workflows/ci.ymlрядом сPHP lint + smoke. -
composer stanскрипт →phpstan analyse -c phpstan.neon --memory-limit=1G. - Baseline остаётся — падаем только на новых ошибках, не на исторических 168.
- После стабилизации — добавить в required checks branch protection.
Почему первым: нулевая инфраструктурная стоимость (всё уже настроено), максимальный ROI — ловит именно тот класс ошибок, что мы вылавливаем руками.
Уровень 1 — PHPUnit каркас + чистые функции (см. #395)
Отслеживается в #395. Кратко: подключить PHPUnit 11, перенести 5 существующих smoke в нормальные тест-кейсы с автодискавери и человеческими assertions. Smoke-раннер остаётся для CI-совместимости, пока не мигрируем полностью.
Кандидаты (чистые функции, без MODX):
Utils::parseImportedOptionValue(есть smoke)EmailVerificationService::buildDefaultVerifyRequestUrl(есть smoke)ms3Phinx*charset/collation-функции изphinx.phpOptionColumnSpec::isValidOptionKey/isValidFieldName(feat(grid): add option field type for category products grid #154 — SQL-injection whitelist, критично)RepeaterFieldServicenormalize/validate pipeline (feat(extra-fields): repeater field type (ms3-repeater) #301 — чистая логика без БД)GridEditorReferenceRegistry::isAllowlistedEndpoint(feat(grid): add select/combo editor types for category products inline edit #157 — SSRF-защита, критично для безопасности)
Статус: закрыт через #395 / #433; RepeaterFieldService — #493.
Уровень 2 — интеграционные тесты stateful-логики (главная ценность, дорого)
Здесь живут реальные баги. Требует тестовой БД (SQLite in-memory или MySQL service в CI) + bootstrap MODX-lite или мок xPDO. Это большой инфраструктурный трек — отдельные PR по одному домену.
Приоритет по «частоте багов за последние релизы»:
- Options sync (
OptionService/OptionSyncService/ProductDataService::saveOptions) — При копировании товара через «Дублировать ресурс» теряются значения опций #257, [Bug] Не удаляется опция из товара #199, feat(extra-fields): repeater field type (ms3-repeater) #301, fix(import): split multi-value option columns in CSV import #312 все крутились тут. Save/removeOther-семантика, multi-value, repeater exclusion. - Order flow (
OrderService,OrderDraftManager,OrderFinalizeService,clampComputedTotal) — [Feature] Отрицательная стоимость доставки и оплаты #211, [Feature] Пересчет стоимости заказа при смене способа доставки в админке #212, Защита total от ухода в минус + UX-маркер для скидок в настройках доставки/оплаты #265, fix(order): multiply draft weight by product count #403. Расчёт стоимости, веса, negative-clamp, draft → final. - Product data payload (
ProductDataPayloadTrait,updateProductDatawhitelist) — [Feature] кастомные поля msProductData в процессор create product #297, fix(product): сохранение Data и extra fields в процессорах Create/Update #298, feat(extra-fields): repeater field type (ms3-repeater) #301, fix(api): per-field null payload semantics in Manager API (#289) #310. Null-семантика, extra-field whitelist, silent-drop регрессии. - Customer auth / token (
AuthManager,TokenService) — [Bug] Не работает авторизация покупателя #285, fix(manager): prevent search autofill after customer edit (#286) #319, fix(customer): stop checkout token takeover via email #391, fix(customer): reject expired API tokens instead of silent renew #396, fix(api): omit customer secrets from profile/add responses #428. Session/token lifecycle, expired-token, takeover-защита. - Cart (
Cartfacade + services) — базовый флоу add/change/remove, пока почти без покрытия.
Статус: starter + follow-up в #493 (SQLite Fake xPDO / draft store; не полный MySQL service в CI):
- Options sync —
OptionSyncServiceTest - Order flow — clamp / draft cost /
OrderFinalizeServiceTest - Product payload — trait +
UpdateProductDataPermissionsTest - Auth/token —
ResolveApiTokenTest - Cart — pure helpers +
CartFacadeDraftStoreTest
Уровень 3 — Vue/JS (низкий приоритет сейчас)
ESLint уже в CI. Юнит-тесты компонентов (Vitest) — только если появится регрессионная боль во фронте. Composables из #397 (useGridFilterParams, useCategoryProductsInlineEdit) — хорошие кандидаты, они чистые.
Статус: Vitest + happy-dom в #493 (useGridFilterParams, useCategoryProductsInlineEdit, formatLocalDateYmd); CI step в job vueManager lint.
Метрика готовности к stable
Связано с внутренним критерием перехода beta → stable. «Install reliability» и «строгий patch-контракт» частично защищаются уровнями 0-1; но уверенность в отсутствии data-loss даёт только уровень 2. Без интеграционного покрытия горячих путей мы каждый релиз полагаемся на ручное прокликивание на dev.
Что предлагаю по порядку
- Сейчас: уровень 0 (PHPStan в CI) — один PR, дёшево, огромный ROI.
- Далее: уровень 1 ([Feature] PHPUnit: каркас + 2–3 unit-теста на чистые функции #395) — каркас + перенос smoke + добавить security-критичные чистые функции (OptionColumnSpec, GridEditorReferenceRegistry).
- Затем итеративно: уровень 2 по одному домену, начиная с options sync и order flow.
- Уровень 3 — по потребности.
Activity
feat(ci): PHPStan gate + PHPUnit scaffold for pure helpers (#433)
Ibochkarev commentedon Aug 1, 2026
Прогресс по roadmap (не полное закрытие):
Уже на beta: Level 0 (job
PHPStan+composer stan+ baseline), Level 1 каркас PHPUnit (#433 / #395), security-критичные pure helpers.PR: #493 — PHPUnit для
RepeaterFieldService(пробел Level 1) +clampComputedTotal(starter Level 2).Осталось:
PHPStanв required checks branch protection наbeta(сейчас толькоPHP lint + smoke+vueManager lint).Refs #493
Ibochkarev commentedon Aug 1, 2026
Level 0 — done:
PHPStanдобавлен в required checks наbeta(рядом сPHP lint + smokeиvueManager lint).Ibochkarev commentedon Aug 1, 2026
Level 2 добавлен в #493:
OptionSyncServiceTest(SQLite memory)ClampComputedTotalTest+OrderDraftCostClampTestProductDataPayloadTraitTestResolveApiTokenTestCartItemManagerPureTestcomposer test→ 93 tests OK. CI green. Follow-up: finalize / cart facade с более глубоким Fake draft.test(vueManager): add Vitest for grid filter and inline-edit composables
Ibochkarev commentedon Aug 1, 2026
Level 3 в #493:
npm test)useGridFilterParams,useCategoryProductsInlineEdit,formatLocalDateYmdvueManager lint(имя job не меняли — branch protection)Roadmap 0–3 закрыт в рамках этого PR (Refs #429).
Ibochkarev commentedon Aug 1, 2026
Level 2 follow-up в #493:
OrderFinalizeServiceTest— nf / already finalized / validation / happy path (skip_validation + costs + status)CartFacadeDraftStoreTest— add → change → remove на in-memory draft storeUpdateProductDataPermissionsTest— save_document / policy / 404 / negative price / whitelist dropcomposer test→ 104 tests OK.Ibochkarev commentedon Aug 1, 2026
Roadmap 0–3 — выполнено
composer stan+ baseline; required check наbeta(PHPStanрядом сPHP lint + smokeиvueManager lint)RepeaterFieldServiceв #493PR: #493
Дальнейшее углубление (MySQL service в CI, полный AuthManager lifecycle) — отдельные issues по потребности.
test: roadmap modx-pro#429 Levels 0–3 (PHPStan gate, PHPUnit/Vitest c…