Repository navigation
[Feature] Web API: интеграционный test suite для headless Nuxt journey #574
Description
Описание функции
Интеграционный test suite для Web API (api.php → /api/v1), который гоняет headless Nuxt customer journey на уровне HTTP-контракта: Router + middleware + controllers + envelope. Не domain facades и не source-scan.
Сейчас в репозитории много unit/smoke и несколько Level-2 сервисов. Почти нет тестов, которые проходят storefront flow так же, как Nuxt.
Проблема, которую решает
Регрессии в TokenMiddleware, Response::normalizeResponse/getData(), CORS, cart→order bind и submit ломают Nuxt, а CI остаётся зелёным: тесты проверяют SQLite-facade или static helpers, не wire-контракт.
Нужен один suite «headless journey», который падает, если изменился JSON/HTTP публичного API.
Текущее покрытие (проверено по journey)
Легенда: HTTP = Router::dispatch / Web API envelope. Domain = facade/service без Web boundary. Static = helpers / source-scan / DTO.
| # | Сценарий Nuxt | Покрытие сейчас | Тип |
|---|---|---|---|
| 1 | Public catalog GET /product/list |
ProductCatalogServiceTest — limit/offset/sort helpers |
Static |
| 2 | Product details GET /product/get/{id} |
Нет HTTP; нет DB catalog get | — |
| 3 | Add to cart POST /cart/add |
CartFacadeDraftStoreTest → Cart facade + SQLite products |
Domain |
| 4 | Change qty POST /cart/change |
тот же facade test | Domain |
| 5 | Change option POST /cart/change-option |
только наличие метода в CartCustomerFacadeStructureTest; runtime test нет |
Static |
| 6 | Retrieve cart GET /cart/get |
нет HTTP; форма cart/status не контракт-тестится (#570) |
— |
| 7 | Register POST /customer/register |
CustomerAuthEndpointsTest / Routes / ProcessorsDto |
HTTP* stub processors |
| 8 | Login POST /customer/login |
то же + AuthManagerLifecycleTest (token rotate, SQLite/MySQL) |
Domain + HTTP* stubs |
| 9 | Set address | нет Web: order/address/set, customer/addresses* |
— |
| 10 | Select delivery | нет Web set delivery_id; pairing — DeliveryPaymentAvailabilityTest source-scan |
Static |
| 11 | Select payment | нет Web set payment_id |
— |
| 12 | Calculate costs GET /order/cost* |
OrderCostEngineTest, manager recalculator — не Web OrderController |
Domain |
| 13 | Create/draft order | cart draft via facade; ProgrammaticOrderServiceTest = sessionless, не storefront |
Domain |
| 14 | Submit POST /order/submit |
OrderFinalizeServiceTest domain finalize — не Web submit + redirect |
Domain |
| 15 | Retrieve order GET /customer/orders* |
CustomerOrderServiceDtoTest DTO/params — не HTTP |
Static |
* CustomerAuthEndpointsTest делает Router::loadRoutes(web.php) + dispatch, но runProcessor заглушен. Это mapping auth routes/status codes, не полный login+DB+cart transfer.
Что ещё есть (не journey)
- Token public routes / refresh removed / session revoke — source + policy ([Bug] REST API: cart/get недоступен анонимно, рассинхрон publicRoutes и хардкод строк вместо лексикона #408/[Bug] Web API: /token/refresh возвращает success, хотя не реализован #334/fix(auth): session bypass после revoke API-токенов + Web logout #462).
CorsConfigNormalizeTest— pure config ([Bug] CORS: дефолт allowed_origins=* вместе с allow_credentials=true #335), не HTTP preflight.ResponseFromProcessorTest,RateLimitMiddlewareTest— изолированные куски.- Manager ACL Router dispatch — mgr, не storefront.
- Smoke:
composer test:smoke→ standalonetests/*Test.php; PHPUnit →tests/Unit+tests/Integration.
Сквозного headless HTTP journey нет. Domain coverage cart/order/auth кусками есть. Для Nuxt gate этого мало.
Предлагаемое решение
Архитектура suite
tests/Integration/WebApi/
HeadlessStorefrontJourneyTest.php # happy path 1→15 (где endpoints существуют)
WebApiTestCase.php # bootstrap Router + fixtures + token helpers
support/… # fixtures / HTTP client wrapper
Фазы:
- Phase A: паттерн
CustomerAuthEndpointsTest—Router+web.php+ stubbed services/DI для cart/order/product, assert HTTP status + envelope keys. Быстро в CI. - Phase B: MySQL/SQLite fixtures как
AuthManagerMysqlLifecycleTest/ cart SQLite — draft rows, published product, delivery/payment members. - Phase C (optional): HTTP к
api.phpчерез built-in server — nightly, не блокер merge.
Минимум для закрытия issue: Phase A+B на PHPUnit (composer test или composer test:web-api).
Auth handling
GET /customer/token/getили TokenMiddleware auto-mint → сохранить token.Authorization: Bearerи отдельно cookiems3_token.- Guest token → register/login → новый token + cart transfer (см. [Bug] Login/Register: rebind guest API-токена без ротации (token fixation) #412 / [Feature] Web API: контракт customer auth для Nuxt SSR (me / TTL / cookie+Bearer) #571).
- Cabinet addresses/orders:
customer_id > 0.
Database fixtures (Phase B)
- 1 published product (+ option keys для change-option).
- Active delivery + payment +
msDeliveryMember. - Customer credentials для login/register.
- Cleanup per test / transaction.
Полный MODX manager bootstrap не обязателен, если xPDO/SQLite harness уже принят в Integration/.
CORS
- Unit уже есть (
CorsConfigNormalizeTest). - В suite: один preflight
OPTIONSчерез Router/middleware → 200 + headers при allowlisted origin. Кейс empty origins /*+credentials ([Bug] CORS: дефолт allowed_origins=* вместе с allow_credentials=true #335). - Не дублировать весь CORS matrix в journey.
Happy path (assert envelope)
Где endpoint существует:
- HTTP status (не только
successв body — см. [Feature] Web API: единый контракт ошибок и HTTP-статусов для TypeScript/Nuxt #572getData()). success/datashape.- Cart:
status.total_*после add/change ([Feature] Web API: нормализовать контракт ответа корзины (cart/get) для headless #570). - Cost:
cart_cost/delivery_cost/payment_cost/costпосле set delivery/payment. - Submit:
data.redirectили payment redirect key. - Customer orders: list/get после submit (если status позволяет).
Error cases (минимум)
| Кейс | Ожидание |
|---|---|
| product get unknown id | 404 envelope |
| cart without token (если не auto-mint) | 401 |
| expired/invalid Bearer | 401 (ms3_err_token_*) |
| change-option empty options | 400 |
| order submit empty cart / missing fields | 400 + message |
| payment not linked to delivery | business error (#374) |
| customer orders as guest | 401 |
| rate limit (optional isolated) | 429 |
Чего suite не должен ждать
В web.php пока нет public delivery/list, payment/list, category API (#563–#569). Journey ставит delivery_id/payment_id из fixture IDs (как Fenom form), с пометкой «discovery later».
Затронутые места
tests/Integration/WebApi/*(новые)composer.jsonscripts /scripts/ci-php.sh/ CI workflow- Переиспользовать stubs:
WebApiModxStub,CustomerAuthPdoStore,OrderProductSqliteStoreгде возможно - Не дублировать mgr ACL tests
Альтернативные варианты
- Только расширять facade tests. Не ловит Web controller /
getData()/ middleware. - Browser e2e (Playwright). Дорого. Не заменяет API contract suite.
- Полагаться на docs ([Feature] Docs: полный справочник Web API для Nuxt/TypeScript (сверка с web.php) #573). Docs не gate.
Примеры использования
cd core/components/minishop3
composer test:web-api
# или: vendor/bin/phpunit --testsuite WebApiNuxt-команда перед релизом витрины: зелёный journey = контракт /api/v1 не сломан.
Обратная совместимость
- Новые тесты не меняют runtime API.
- Flaky MySQL:
@group mysql/ skip без DSN (как существующие Mysql integration).
Критерии приёмки
- PHPUnit (или эквивалент в CI) suite бьёт Web routes через Router/api boundary.
- Happy path: product list+get → cart add/change/(change-option) → get cart → token/login → set address + delivery_id + payment_id → order/cost → submit → customer order get (или явный skip с reason).
- Assertы на HTTP status +
success/data(не только domain arrays). - Bearer и хотя бы один cookie auth path.
- Error cases: 401 token, 404 product, validation/business на submit или set.
- CORS preflight или config+middleware smoke в том же suite/adjacent test.
- CI: suite в
composer ci:phpили отдельный required job; skip policy для MySQL задокументирован. - README/tests: как запустить локально.
- Не требует несуществующих endpoints [Feature] Web API: публичный Category API для headless (list / get / tree) #563–[Feature] Web API: публичный список методов оплаты (payment/list) #569.
Дополнительный контекст
- Ближайший образец HTTP Web:
CustomerAuthEndpointsTest.php. - Domain cart:
Integration/Cart/CartFacadeDraftStoreTest.php(нет change-option). - Domain submit:
OrderFinalizeServiceTest≠POST /order/submit. - Related: [Feature] Web API: нормализовать контракт ответа корзины (cart/get) для headless #570 cart contract, [Feature] Web API: контракт customer auth для Nuxt SSR (me / TTL / cookie+Bearer) #571 auth, [Feature] Web API: единый контракт ошибок и HTTP-статусов для TypeScript/Nuxt #572 errors/HTTP, [Feature] Docs: полный справочник Web API для Nuxt/TypeScript (сверка с web.php) #573 docs, [Feature] Публичный sessionless API для программного создания заказов #507 programmatic orders (другой вход).
Activity
Ibochkarev commentedon Aug 16, 2026
Follow-up после #563 / PR #578
Публичные
GET /api/v1/category/list|get/{id}|treeуже в PR. В Phase B fixtures journey нужны runtime MODX (или xPDO harness) rows:msCategory(+ parent chain для breadcrumbs/tree)alias/uri/context_key/hidemenuварианты (навигация vsinclude_hidden)parent(и позжеmsCategoryMember, если подъедет [Feature] Web API: расширенные фильтры product/list для headless #564)Happy path каталога в suite:
category/list→category/get/{id}→product/list?parent=…→ дальше cart как сейчас.AC «Не требует несуществующих endpoints #563–#569» для category устарел: category можно включить в journey после merge #578. Delivery/payment discovery (#568/#569) по-прежнему через fixture IDs.
Alias resolve (#580, если номер другой — см. linked issue) — отдельный сценарий после появления
GET …/get?alias|uri.Ibochkarev commentedon Aug 16, 2026
Уточнение: alias|uri resolve + cache — #579 (не #580).
test(web-api): add headless Nuxt journey suite via Router envelope
test(web-api): add headless Nuxt journey suite via Router envelope
test(web-api): add headless Nuxt journey suite via Router envelope
test(web-api): headless Nuxt journey suite через Router envelope (#595)
chore: sync beta with upstream modx-pro/MiniShop3 (#1)