E2E Test Scenarios

Живой список ручных сценариев которые надо прогнать после новой реализации, прежде чем считать фичу принятой. Каждый сценарий привязан к коммиту/фазе. Когда сценарий пройден — отмечаем ✅ verified <date> рядом с заголовком, не удаляя сам сценарий.

Зачем

Локально/в CI юнит-тесты ловят регрессии в коде, но интеграция с PayKeeper / реальным VPS-окружением требует ручной проверки. Этот файл — единая шпаргалка где смотреть «что мы обещали проверить и не проверили».

Окружение

  • Тестовый VPS: erp-test.nirbi.ru (185.152.93.77)
  • ЛК PayKeeper sandbox: koala-test (см. reference_paykeeper_sandbox.md в memory)
  • БД access: sshpass -p '<vps-pass>' ssh root@185.152.93.77 'docker exec -it erp-postgres psql -U erp -d <db-name>'

PayKeeper — возвраты, Фаза 1 (полный возврат e2e)

Готово в коде: order-service c2ded1a, paykeeper-adapter b9dd569 (deployed 2026-04-27)

Что проверяем: при нажатии «Вернуть» в админке для PK-оплаченного заказа цепочка идёт до самого PK, статус закрывается webhook’ом или (после Фазы 2) poll’ом.

Сценарий 1.1 — Полный возврат успешный путь

  1. Создать тестовый PK-заказ в paykeeper_adapter_db / order_db:
    • Через UI POS-кассу с PK-ТТ (если работает) — нормальный flow.
    • Или SQL-инсерт + POST /internal/orders/{id}/request-payment (форсированно, как раньше).
  2. Оплатить через invoice URL (или эмулировать оплату в koala-test ЛК PK).
  3. Убедиться что orders.pk_payment_id и orders.paid_at заполнены, orders.status='paid'.
  4. В админке открыть карточку заказа → нажать «Запросить возврат» → ввести сумму = order.total → подтвердить.
  5. Ожидаем (Order Service):
    • В Kafka топике order.refund_requested есть событие с is_full_refund=true, pk_payment_id=<...>.
    • В БД: SELECT status FROM refund_records WHERE order_id='...';started (не done).
  6. Ожидаем (paykeeper-adapter):
    • В логах: OrderEventsConsumer received order.refund_requestedenqueued reverse_paymentReverse payment success.
    • В БД: SELECT * FROM pk_outbox WHERE op_type='reverse_payment' ORDER BY id DESC LIMIT 1;status='done'.
  7. Ожидаем (PK ЛК koala-test):
    • Платёж переходит в статус refundingrefunded.
    • Если refund webhook от PK включён → у нас в paykeeper_refunds.status='done' и RefundRecord.status='done'.
    • Если webhook НЕ включён (текущее состояние тестового ЛК) → останется started до Фазы 2.

Сценарий 1.2 — Частичный возврат для PK-заказа должен блокироваться

  1. Тот же PK-оплаченный заказ.
  2. В админке нажать «Запросить возврат» → ввести сумму меньше order.total.
  3. Ожидаем: ошибка 422 PARTIAL_REFUND_NOT_SUPPORTED. Никаких записей в БД, никаких событий.

Сценарий 1.3 — Частичный возврат для не-PK заказа должен работать

  1. Заказ оплачен локально (cash/card в POS), orders.pk_payment_id IS NULL.
  2. Запросить возврат с суммой меньше order.total.
  3. Ожидаем: RefundRecord.status='done' сразу, paykeeper_refunds не создаётся, order.refunded событие.

Сценарий 1.5 — Поведение при отсутствии refund webhook (Фаза 2)

Готово в коде: paykeeper-adapter 03684ba (deployed 2026-04-27)

Что проверяем: poll-fallback RefundCheckScheduler подхватывает started-возвраты и закрывает их без webhook’а.

  1. Тестовый PK-заказ (как в 1.1).
  2. Webhook URL для refund’а в ЛК PK не настроен (это и есть текущее состояние koala-test).
  3. Нажать «Вернуть» в админке с полной суммой.
  4. Ожидаем сразу:
    • paykeeper_refunds — новая запись с status='started', initiated_by='admin', payment_id заполнено (создаётся в PkOutboxWorker.executeReversePayment).
    • В ЛК PK платёж ушёл в refunding.
  5. Ждём 3-5 минут (poll-delay 3 мин + один тик scheduler’а):
    • Логи адаптера: Reconciled refund <id> via poll → done (pk_refund_id=...).
    • В БД paykeeper_refunds — наша запись теперь status='done', pk_refund_id заполнен.
    • В Kafka paykeeper.payment.refunded — событие.
    • В Order Service RefundRecord.status='done'.
  6. Сценарий timeout (negative): если PK по какой-то причине не подтвердил возврат, через 30 минут scheduler помечает запись status='failed' с reason “timeout after 30 min”.

Сценарий 1.6 — Webhook и poll одновременно — нет дублей

Что проверяем: если refund webhook от PK прилетит к нам когда уже есть started-запись, обновляется именно она, а не создаётся вторая.

  1. Тестовый PK-заказ + возврат запущен (paykeeper_refunds имеет одну started-запись).
  2. Эмулировать вручную refund webhook (или включить его у PK):
    • curl -X POST -d 'id=<pk_payment_id>&sum=...&sign=<correct_md5>' https://erp-test.nirbi.ru/pk-webhooks/refund/<account_id>.
  3. Ожидаем:
    • В paykeeper_refunds для этого payment всё ещё одна запись (а не две).
    • Её status обновлён на done, initiated_by остался admin (не перезаписан в pk_external).
    • Если scheduler следом проверит — он не должен ничего делать (запись уже не started).

Сценарий 1.7 — Частичный возврат с item picker (Фаза 3)

Готово в коде: order-service cec31f7, admin 2e6bf47 (deployed 2026-04-27)

Что проверяем: при частичном PK-возврате админка собирает корзину выбранных позиций, и она доходит до PK как refund_cart для корректного 54-ФЗ чека.

  1. Тестовый PK-заказ из ≥2 позиций (например, 2× «Латте», 1× «Круассан»).
  2. Оплачен через PK, статус paid.
  3. В админке открыть карточку → «Запросить возврат» → переключить на «Частичный».
  4. Ожидаем UI:
    • Появляется список позиций с qty-инпутами (вместо свободного ввода суммы).
    • Под списком — running total «Итого к возврату».
    • Кнопка submit отключена пока не выбрана хотя бы одна позиция.
  5. Выбрать 1× Круассан (qty=1). Сумма автоматически = unit_price круассана.
  6. Submit.
  7. Ожидаем (Order Service):
    • В БД refund_requests — новая запись со status='pending', refund_cart JSONB c одним элементом (name=“Круассан”, qty=1, vat_rate=“vat20”, payment_subject=“goods”, payment_type=“full”).
  8. Ожидаем (после POS confirm):
    • В БД refund_records.status='started'.
    • В Kafka order.refund_requested payload содержит is_full_refund=false и непустой refund_cart.
    • В Adapter: pk_outbox запись op_type='reverse_payment', в payload — тот же cart.
    • В PK API: запрос POST /change/payment/reverse/ с partial=true и сериализованным cart.
  9. Ожидаем (PK ЛК):
    • Платёж переходит в partially_refunded.
    • В разделе «Чеки» появляется чек возврата с одной позицией = «Круассан».
  10. Дальше (poll или webhook):
    • paykeeper_refunds.status='done', is_full_refund=false.
    • refund_records.status='done'.

Сценарий 1.8 — Частичный возврат PK без cart блокируется

Что проверяем: backend ловит partial-PK без cart.

  1. PK-оплаченный заказ.
  2. Дёрнуть BFF curl-ом без refund_cart:
    curl -X POST .../api/v1/orders/{id}/refund-requests \
      -d '{"amount": <half_total>, "reason": "..."}'
  3. Ожидаем: 422 REFUND_CART_REQUIRED. Никаких записей в БД.

Сценарий 1.4 — Внешний возврат через ЛК PK (initiated_by=pk_external)

Проверить что isFullRefund теперь считается корректно (не хардкод).

  1. PK-оплаченный заказ. Зайти в ЛК PK koala-test → сделать частичный возврат руками через интерфейс PK.
  2. PK шлёт refund webhook к нам.
  3. Ожидаем:
    • В paykeeper_refunds запись с is_full_refund=false (раньше всегда было true).
    • В Kafka paykeeper.payment.refunded payload: is_full_refund=false.

Шаблон для будущих сценариев

## <Фича> — <Фаза>
 
**Готово в коде**: <repo> `<commit>`, <repo> `<commit>` (deployed YYYY-MM-DD)
 
**Что проверяем**: <одно предложение>.
 
### Сценарий N.1 — <короткое название>
 
1. <шаг>
2. <шаг>
3. **Ожидаем:** <что должно произойти>

Что устарело

(сюда переносить сценарии после того как фичу отрефакторили и они стали неактуальны)