diff --git a/Test-Cases.md b/Test-Cases.md new file mode 100644 index 0000000..724393f --- /dev/null +++ b/Test-Cases.md @@ -0,0 +1,219 @@ +# Test Cases + +Все тесты запускаются внутри Docker-контейнера: + +``` +docker compose exec web python -m pytest +``` + +Общее покрытие: **~73%** (65+ тест-кейсов). + +--- + +## Юнит-тесты + +### `_is_weight` — определение весового товара (`test_tasks_vk_sync.py`) + +| ID | Входные данные | Ожидаемый результат | +|----|----------------|---------------------| +| U-01 | `"г"` | `True` | +| U-02 | `"г."` | `True` | +| U-03 | `"гр"` | `True` | +| U-04 | `"гр."` | `True` | +| U-05 | `"грамм"` | `True` | +| U-06 | `"граммов"` | `True` | +| U-07 | `" Г "` (пробелы, верхний регистр) | `True` | +| U-08 | `"кг"` | `False` | +| U-09 | `"шт"` | `False` | +| U-10 | `"л"` | `False` | +| U-11 | `None` | `False` | +| U-12 | `""` | `False` | + +### `_calc_price` — расчёт цены в копейках (`test_tasks_vk_sync.py`) + +| ID | Входные данные | VK_WEIGHT_PRICE_MULTIPLIER | Ожидаемый результат | +|----|----------------|---------------------------|---------------------| +| U-13 | `Decimal("150")`, `"шт"` | 10 | `15000` (150 руб × 100) | +| U-14 | `Decimal("50")`, `"г"` | 10 | `50000` (50 × 10 × 100) | +| U-15 | `None`, `"шт"` | — | `0` | +| U-16 | `Decimal("0")`, `"шт"` | — | `0` | + +### `_name_for_vk` — очистка названия (`test_tasks_vk_sync.py`) + +| ID | Входные данные | Ожидаемый результат | +|----|----------------|---------------------| +| U-17 | `"Чай; зелёный; Китай"` | `"Чай, зелёный, Китай"` | +| U-18 | `"Пуэр (выдержанный)"` | `"Пуэр (выдержанный)"` | + +### `_build_description` — формирование описания (`test_tasks_vk_sync.py`) + +| ID | Сценарий | Проверяется | +|----|----------|-------------| +| U-19 | Весовой товар, `"г"`, нет описания | Содержит `"10г"` и название | +| U-20 | Штучный товар, есть описание из Эвотора | Содержит текст описания | +| U-21 | Штучный товар, нет описания | Содержит название | + +### `_delete_orphans` — удаление устаревших товаров VK (`test_tasks_vk_sync.py`) + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| U-22 | VK-товар `"222"` не в `owned_ids` → удаляется | `results["deleted"] == 1`, `db.delete` вызван | +| U-23 | VK API возвращает `{"error": {...}}` | `results["errors"] == 1`, `db.delete` не вызван | +| U-24 | `owned_ids` пуст → все VK-товары — орфаны | `results["deleted"] == 1` | + +### `_fetch_stores` — загрузка магазинов из Эвотора (`test_tasks_catalog.py`) + +| ID | Формат ответа API | Ожидаемый результат | +|----|-------------------|---------------------| +| U-25 | Список `[{"id": "s1", ...}]` | Список магазинов | +| U-26 | Словарь `{"items": [...], "total": 1}` | Распаковывает `items` | + +### `_fetch_groups` — загрузка групп (`test_tasks_catalog.py`) + +| ID | HTTP-статус | Ожидаемый результат | +|----|-------------|---------------------| +| U-27 | `200` | Список групп | +| U-28 | `402` | `None` | +| U-29 | `403` | `None` | + +### `_fetch_products` — загрузка товаров (`test_tasks_catalog.py`) + +| ID | HTTP-статус | Ожидаемый результат | +|----|-------------|---------------------| +| U-30 | `200` | Список товаров | +| U-31 | `402` | `None` | +| U-32 | `403` | `None` | + +--- + +## Интеграционные тесты — задачи каталога (`test_tasks_catalog.py`) + +Используют SQLite in-memory БД через фикстуру `override_db`. + +| ID | Сценарий | Проверяется | +|----|----------|-------------| +| I-01 | `_sync_user` с новым магазином | `CachedStore` создан в БД | +| I-02 | `_sync_user` с товаром | `CachedProduct` создан, поля `group_evotor_id`, `allow_to_sell` верны | +| I-03 | `_fetch_stores` бросает исключение | Никаких записей в БД, задача завершается без ошибки | + +--- + +## Интеграционные тесты — маршруты каталога (`test_routes_catalog.py`) + +| ID | Маршрут | Сценарий | Ожидаемый результат | +|----|---------|----------|---------------------| +| I-04 | `GET /catalog/stores` | Без сессии | Редирект `303 → /login` | +| I-05 | `GET /catalog/stores` | Нет магазинов | `200`, текст «не загружены» | +| I-06 | `GET /catalog/stores` | 2 магазина | `200`, оба названия в ответе | +| I-07 | `GET /catalog/stores` | Магазин другого пользователя | Не отображается | +| I-08 | `GET /catalog/stores/{id}/groups` | Группа с 2 товарами | `200`, название группы, число `"2"` | +| I-09 | `GET /catalog/stores/{id}/groups` | Пустая группа | `200`, число `"0"` | +| I-10 | `GET /catalog/stores/{id}/groups` | Несуществующий магазин | Редирект `303` | +| I-11 | `GET /catalog/stores/{id}/products` | 2 товара | `200`, оба названия | +| I-12 | `GET /catalog/stores/{id}/products?group=g1` | Фильтр по группе | Только товары группы g1 | +| I-13 | `GET /catalog/stores/{id}/products` | Товар с группой | Название группы в ответе | +| I-14 | `GET /catalog/stores/{id}/products` | Товар без группы | `200`, товар отображается | +| I-15 | `POST /catalog/stores/{id}/toggle` | Первое отключение из 3 магазинов | `SyncFilter` include: только s2, s3 (не s1) | +| I-16 | `POST /catalog/stores/{id}/toggle` | Повторный toggle включает | s1 снова в списке include | +| I-17 | `POST /catalog/stores/{id}/toggle` | Без сессии | Редирект `303 → /login` | +| I-18 | `POST /catalog/stores/{id}/groups/{gid}/toggle` | Первое отключение группы g1 | include: только g2 (не g1) | +| I-19 | `POST /catalog/stores/{id}/groups/{gid}/toggle` | Повторный toggle включает g1 | g1 снова в include | + +--- + +## Интеграционные тесты — маршруты подключений (`test_routes_connections.py`) + +### Авторизация + +| ID | Маршрут | Ожидаемый результат | +|----|---------|---------------------| +| I-20 | `GET /connections` | Без сессии → `303 → /login` | + +### GET /connections + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-21 | Нет подключений | `200`, «Эвотор», «ВКонтакте», «Не подключено» | +| I-22 | Есть Эвотор-подключение | `200`, «Подключено», первые 8 символов токена | + +### POST /connections/evotor + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-23 | Новый токен | `303` с `success=1`, запись создана, `api_token` сгенерирован | +| I-24 | Обновление токена | Токен обновлён в БД | +| I-25 | Пустой токен | `200`, текст «обязателен» | + +### POST /connections/evotor/disconnect + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-26 | Отключение | `303`, запись удалена из БД | + +### POST /connections/vk (ручной токен) + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-27 | Валидный токен + group_id | `303` с `success=1`, запись создана | +| I-28 | Пустой токен | `200`, текст «обязателен» | + +### POST /connections/vk/disconnect + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-29 | Отключение | `303`, запись удалена | + +### GET /vk-auth — OAuth-редирект + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-30 | VK_CLIENT_ID настроен | `302` на `oauth.vk.com/authorize` с `client_id` и `response_type=token` | +| I-31 | VK_CLIENT_ID пуст | `303` с `error=vk_not_configured` | + +### GET /vk-callback + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-32 | Аутентифицирован | `200`, HTML с `access_token` и `fetch` | + +### POST /vk-callback/save + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-33 | Неверный state | `200`, `{"ok": false}`, сообщение содержит «state» | +| I-34 | Пустой токен | `200`, `{"ok": false}` | +| I-35 | Без аутентификации | `401` | + +### POST /connections/evotor/test + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-36 | Нет подключения | `200`, `{"ok": false}`, «не настроено» | +| I-37 | Успешный тест (2 магазина) | `200`, `{"ok": true}`, сообщение содержит `"2"` | +| I-38 | Неверный токен (401) | `200`, `{"ok": false}`, сообщение содержит `"401"` | + +### POST /connections/vk/test + +| ID | Сценарий | Ожидаемый результат | +|----|----------|---------------------| +| I-39 | Нет group_id | `200`, `{"ok": false}`, «сообщества» | +| I-40 | Успешный тест | `200`, `{"ok": true}`, название сообщества + «включён» | +| I-41 | VK API error 5 | `200`, `{"ok": false}`, содержит `"5"` | + +--- + +## Запуск тестов + +```bash +# Все тесты +docker compose exec web python -m pytest + +# Только юнит-тесты +docker compose exec web python -m pytest tests/test_tasks_vk_sync.py tests/test_tasks_catalog.py -v + +# Интеграционные тесты маршрутов +docker compose exec web python -m pytest tests/test_routes_connections.py tests/test_routes_catalog.py -v + +# С покрытием +docker compose exec web python -m pytest --cov=web --cov-report=term-missing +```