diff --git a/test-case/README.md b/test-case/README.md new file mode 100644 index 0000000..5953b5c --- /dev/null +++ b/test-case/README.md @@ -0,0 +1,158 @@ +### 1. Модульные тесты + + 1.1. Кейсы проверки валидации card_number + +| Правила валидации | Входные данные | Код ошибки | Ожидаемый результат | +|-------------------|----------------|------------|---------------------| +| Поле отсутствует в JSON | ключ удален из объекта | 400 | Ошибка: поле card_number обязательное | +| Поле пустое | "" | 400 | Ошибка: поле card_number не должно быть пустым | +| Граничное значение: 15 цифр | "123456789012345" | 400 | Ошибка: поле card_number должно содержать 16 цифр | +| Граничное значение: 17 цифр | "12345678901234567" | 400 | Ошибка: поле card_number должно содержать 16 цифр +| Поле содержит буквы | "123456789012345a" | 400 | Ошибка: поле card_number должно содержать только цифры | +| Поле содержит спецсимволы | "1234-5678-9012-3456" | 400 | Ошибка: поле card_number должно содержать только цифры | +| Попытка SQL-инъекции | "1234567890123456'; DROP TABLE orders;--" | 400 | Ошибка: поле card_number должно содержать только цифры | +| Граничное значение: ровно 16 цифр | "1234567890123456" |200 | Передача данных дальше | + + 1.2. Кейсы проверки валидации card_holder + +| Правила валидации | Входные данные | Код ошибки | Ожидаемый результат | +|-------------------|----------------|------------|---------------------| +| Поле отсутствует в JSON | ключ удален из объекта | 400 | Ошибка: поле card_holder обязательное | +| Поле пустое | "" | 400 | Ошибка: поле card_holder не должно быть пустым | +| Поле содержит цифры | "SEMEN SEMEN0V1CH" | 400 | Ошибка: поле card_holder должно содержать только латинские буквы | +| Поле содержит кириллицу | "ИВАН ИВАНОВ" | 400 | Ошибка: допускаются только латинские символы | +| Поле содержит запрещенные спецсимволы | "IVAN_IVANOV" | 400 | Ошибка: допускаются только буквы, пробел и дефис | +| Поле содержит запрещенные спецсимволы | "IVAN! IVANOV" | 400 | Ошибка: допускаются только буквы, пробел и дефис | +| Два пробела подряд | "IVAN IVANOV" | 400 | Ошибка: двойные пробелы не допускаются | +| Начинается с пробела | " IVAN IVANOV" | 400 | Ошибка: неверный формат card_holder | +| Заканчивается пробелом | "IVAN IVANOV " | 400 | Ошибка: неверный формат card_holder | +| Отсутствует фамилия | "IVAN" | 400 | Ошибка: необходимо указать имя и фамилию | +| Попытка XSS | "" | 400 | Ошибка: неверный формат card_holder | +| Граничное значение: минимально допустимое имя и фамилия | "A B" | 200 | Передача данных дальше | +| Допустимое использование дефиса | "PAVLOV-SHEREMETEV NICOLAS" | 200 | Передача данных дальше | +| Корректное значение | "IVAN IVANOV" | 200 | Передача данных дальше | + + 1.3. Кейсы проверки валидации card_expiration + +| Правила валидации | Входные данные | Код ошибки | Ожидаемый результат | +| ------------------------------------ | ---------------------- | ---------- | -------------------------------------------------- | +| Поле отсутствует в JSON | ключ удален из объекта | 400 | Ошибка: поле card_expiration обязательное | +| Поле пустое | "" | 400 | Ошибка: поле card_expiration не должно быть пустым | +| Содержит буквы | "AA/26" | 400 | Ошибка: формат должен быть MM/YY | +| Неверный разделитель | "12-26" | 400 | Ошибка: формат должен быть MM/YY | +| Неверный разделитель | "12.26" | 400 | Ошибка: формат должен быть MM/YY | +| Граничное значение: месяц 00 | "00/26" | 400 | Ошибка: неверное значение месяца | +| Граничное значение: месяц 13 | "13/26" | 400 | Ошибка: неверное значение месяца | +| Граничное значение: предыдущий месяц | "05/26" | 400 | Ошибка: срок действия карты истек | +| Граничное значение: текущий месяц | "06/26" | 200 | Передача данных дальше | +| Граничное значение: следующий месяц | "07/26" | 200 | Передача данных дальше | +| Корректная дата в будущем | "12/28" | 200 | Передача данных дальше | + + 1.4. Кейсы проверки валидации cvv + +| Правила валидации | Входные данные | Код ошибки | Ожидаемый результат | +| --------------------------- | ---------------------- | ---------- | ------------------------------------------ | +| Поле отсутствует в JSON | ключ удален из объекта | 400 | Ошибка: поле cvv обязательное | +| Поле пустое | "" | 400 | Ошибка: поле cvv не должно быть пустым | +| Граничное значение: 2 цифры | "99" | 400 | Ошибка: cvv должен содержать ровно 3 цифры | +| Граничное значение: 3 цифры | "999" | 200 | Передача данных дальше | +| Граничное значение: 4 цифры | "9999" | 400 | Ошибка: cvv должен содержать ровно 3 цифры | +| Содержит буквы | "99A" | 400 | Ошибка: cvv должен содержать только цифры | +| Содержит спецсимволы | "99#" | 400 | Ошибка: cvv должен содержать только цифры | +| Попытка SQL-инъекции | "'--" | 400 | Ошибка: cvv должен содержать только цифры | + + 1.5. Кейсы проверки валидации order_number + +| Правила валидации | Входные данные | Код ошибки | Ожидаемый результат | +| ------------------------------- | ---------------------- | ---------- | ----------------------------------------------------------- | +| Поле отсутствует в JSON | ключ удален из объекта | 400 | Ошибка: поле order_number обязательное | +| Поле пустое | "" | 400 | Ошибка: поле order_number не должно быть пустым | +| Граничное значение: 1 символ | "1" | 200 | Передача данных дальше | +| Граничное значение: 15 символов | "ORDER1234567890" | 200 | Передача данных дальше | +| Граничное значение: 16 символов | "ORDER12345678901" | 200 | Передача данных дальше | +| Граничное значение: 17 символов | "ORDER123456789012" | 400 | Ошибка: длина order_number не должна превышать 16 символов | +| Допустимые спецсимволы | "ORD-123/2026" | 200 | Передача данных дальше | + + 1.6. Кейсы проверки валидации sum + +| Правила валидации | Входные данные | Код ошибки | Ожидаемый результат | +| ------------------------------------------------ | ---------------------- | ---------- | ------------------------------------------- | +| Поле отсутствует в JSON | ключ удален из объекта | 400 | Ошибка: поле sum обязательное | +| Поле пустое | "" | 400 | Ошибка: поле sum не должно быть пустым | +| Граничное значение: ноль | "0" | 400 | Ошибка: сумма должна быть больше нуля | +| Граничное значение: ноль копеек | "0,00" | 400 | Ошибка: сумма должна быть больше нуля | +| Граничное значение: минимальная допустимая сумма | "0,01" | 200 | Передача данных дальше | +| Отрицательное значение | "-15,50" | 400 | Ошибка: сумма должна быть больше нуля | +| Разделитель через точку | "100.50" | 400 | Ошибка: используйте запятую как разделитель | +| Содержит буквы | "100,5A" | 400 | Ошибка: неверный формат суммы | +| Содержит несколько запятых | "100,50,10" | 400 | Ошибка: неверный формат суммы | +| Попытка SQL-инъекции | "100,50';--" | 400 | Ошибка: неверный формат суммы | +| Корректная сумма | "250,00" | 200 | Передача данных дальше | + + 1.7. Кейсы проверки взамиодействия с сервисом А (сервис А замокан) + +| Тестовая ситуация | Действие бэкенда | Ожидаемый результат| +|-------------------|----------------------|--------------------| +| Сервис А вернул 403 | Получает 403, прерывает цепочку, не вызывает репозиторий | Оплата отменена платежным шлюзом | +| Сервис А вернул 200 | Получает 200, производит конвертацию sum из "666,00" во float 666.0, вызывает репозиторий | Зависит от репозитория | +| Падение сервиса А | Ловит исключение подключения. Возвращает 500 ощибку | Ошибка: платежный шлюз недоступен | + + 1.8. Кейсы проверки взамиодействия с репозиторием (репозиторий замокан) + +| Тестовая ситуация | Действие бэкенда | Ожидаемый результат| +|-------------------|----------------------|--------------------| +| setOrderIsPaid() вернул true | Формирует успешный ответ | Успех: Заказ оплачен | +| Репозиторий выбросил исключение | Перехватывает и логирует ошибку, возвращает 500 | Ошибка: сервис временно недоступен | +| При ошибке сервиса A репозиторий не вызывается | Метод setOrderIsPaid не должен быть вызван, ошибка 403 | Бэкенд транслирует ошибку банка | + +1.9. Кейсы проверки идемпотентности и повторов + +| Тестовая ситуация | Действие бэкенда | Ожидаемый результат| +|-------------------|----------------------|--------------------| +| Повторный запрос для уже оплаченного заказа | Репозиторий при проверке статуса возвращает, что заказ уже имеет статус оплачен | Возвращается статус 200. Сообщение: заказ оплачен. Сервис А не вызывается | +| Запрос в момент, когда первый платеж еще обрабатывается (двойноq клик) | В хранилище блокировок (к примеру, с помощью Redis) уже существует активный ключ | Ошибка 400. Сообщение: подождите оплата в процессе. Сервис А не вызывается | +| Повтор запроса после падения Сервиса А | Сервиса А на первые два запроса возвращает HTTP 503. На третий запрос возвращает HTTP 200 | Бэкенд совершил ровно 3 попытки запроса к API Сервиса А перед возвратом ответа | + + +### 2. Интеграционные тесты + + 2.1. Проверка связки «фронт-бэк»: + 2.1.1 Счастливый путь: При отправке с фронта формы с валидными данными, фронт получает от бэка HTTP 200 и перенаправляет пользователя на страницу «Оплата успешно завершена». + 2.1.2. Ошибка валидации: + - пользователь вводит 2 цифры cvv, фронт получает от бэка HTTP 400 и подсвечивает поле «CVV» красной рамкой, выводя под ним сообщение «Неверный формат кода». + - пользователь вводит card_number = 123, фронт получает от бэка HTTP 400 и подсвечивает поле «Номер карты» красной рамкой, выводя под ним сообщение «Неверный номер карты». + - пользователь вводит card_expiration = 15/25, фронт получает от бэка HTTP 400 и подсвечивает поле «Срок» красной рамкой, выводя под ним сообщение «Неверная дата действия карты». + 2.1.3. Отказ банка: Если бэк принудительно отвечает ошибкой списания, фронт выводит модальное окно: «Не удалось списать данные с карты, проверьте реквизиты». + + 2.2. Проверяем связку «бэк-репозиторий»: + 2.2.1. Счастливый путь: При получении валидных данных бэкендом, он успешно конвертирует строку суммы с запятой во float и вызывает реальный метод setOrderIsPaid. + 2.2.2. Несовпадение данных: Бэкенд вызывает метод setOrderIsPaid для номера заказа, сумма которого в БД отличается от переданной в запросе. Проверяем, что репозиторий выбрасывает ожидаемое исключение. + + 2.3. Проверяем связку «бэк-сервис А»: + 2.3.1. Счастливый путь: Бэк отправляет запрос с валидными реквизитами "успешной" тестовой карты в API Сервиса А. Проверяем, что бэкенд корректно парсит ответ и интерпретирует его как HTTP 200. + 2.3.2. Недостаточно средств: Бэк отправляет запрос с реквизитами невалидной тестовой карты в API. Проверяем, что бэкенд корректно принимает HTTP 403 от сервиса и не падает с фатальной ошибкой. + 2.3.3. Сервис A недоступен. Проверяем, что бэкенд корректно возвращает 500 ошибку. + 2.3.4. Бэк отправляет запрос на списание средств по заказу. Ожидание: HTTP-клиент бэкенда обязан прикрепить к запросу заголовок Idempotency-Key. Проверяем исходящий запрос на наличие этого заголовка. Это гарантирует, что если бэк сделает авто-повтор из-за моргнувшей сети, Сервис А узнает запрос и не спишет деньги второй раз. + + 2.4. Проверяем связку «бэк - кэш Redis» (реализация может использовать Redis, БД или иной механизм блокировок) + 2.4.1. Проверка атомарного ключа: Бэкенд одновременно получает два идентичных запроса на оплату заказа. Ожидание: Тест проверяет, что в Redis создается ключ. Первый поток успевает выполнить операцию, второй поток мгновенно получает от Redis false и бэк возвращает 409 ошибку, защищая систему от состояния гонки. + + +### 3. Системные тесты + + 3.1. Счастливый путь: Пользователь открывает страницу оплаты, вводит корректные данные тестовой карты, нажимает «Оплатить». Фронт показывает спиннер загрузки. Запрос проходит через бэк, Сервиса А списывает виртуальные средства, БД обновляет статус. Пользователь видит экран «Заказ №... успешно оплачен». + + 3.2. Отказ в проведении операции: Пользователь вводит реквизиты тестовой карты с истекшим сроком действия или заблокированной карты. Система отрабатывает полный цикл (запрос уходит в Сервис А, возвращается 403, БД не обновляется). После загрузки фронт отображает уведомление: «Не удалось списать данные с карты, проверьте реквизиты», поля ввода остаются заполненными (кроме CVV) для повторной попытки. + + 3.3. Ошибка сохранения в БД. Пользователь проходит оплату успешно. Сервис A возвращает 200. Repository выбрасывает исключение. Проверяем, что пользователю показано сообщение о технической ошибке, информация об ошибке залогирована. + + 3.4. Полностью невалидные данные. Пользователь отправляет форму с пустыми полями. Проверяем, что все ошибочные поля подсвечены, отображаются сообщения валидации, запрос на сервис A не отправляется. + + 3.5. Симуляция двойного клика. Автотест открывает форму, вводит валидные данные. С помощью скрипта автотест делает два быстрых клика по кнопке «Оплатить» до того, как фронтенд успеет заблокировать кнопку интерфейса. Оба запроса уходят в сеть. Первый запрос проходит успешно (БД обновляется, статус 200). Второй запрос возвращает 409. Пользователь в итоге видит только одно успешное сообщение, в БД зафиксирована ровно одна оплата. + +### 4. Решение проблемы удешевления тестирования + + 4.1. Способы решения: + - любого адекватного платежного шлюза есть тестовый контур и набор тестовых карт (к примеру, в платежном шлюзе Сбербанка). Каждая карта жестко привязана к определенному ответу системы. + - если нет тестового API, для системных тестов поднимается внутренний микросервис-заглушка, который эмулирует ответы Сервиса А на основе заранее заданных правил. + - на проде можно создать заказ стоимость за минимальную стоимость и провести покупку со своей карты. Сразу после успешного списания бэкенд выполняет автоматический API-запрос на полный возврат операции.