diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ad4b08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +venv/ +__pycache__/ +.pytest_cache/ +*.pyc +allure-results/ +allure-report/ +.coverage +htmlcov/ +.idea/ +.vscode/ +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md index 945c8b4..2ad60bd 100644 --- a/README.md +++ b/README.md @@ -1 +1,74 @@ # Diplom_2 +API-тесты Stellar Burgers +Описание + +В проекте реализованы автоматизированные тесты API сервиса Stellar Burgers. + +Тестирование выполняется с использованием: +pytest +requests +allure-pytest + +Отчёт формируется в Allure. + + Проверяемые сценарии + Создание пользователя + +Создание уникального пользователя +Создание пользователя, который уже зарегистрирован +Создание пользователя без одного из обязательных полей + +Логин пользователя + +Вход под существующим пользователем +Вход с неверным логином и паролем + + Создание заказа + +Создание заказа с авторизацией +Создание заказа без авторизации +Создание заказа с ингредиентами +Создание заказа без ингредиентов +Создание заказа с неверным хешем ингредиентов + + Структура проекта +Diplom_2 +├── src +│ ├── api.py +│ ├── config.py +│ └── helpers.py +├── tests +│ ├── conftest.py +│ ├── test_create_user.py +│ ├── test_login.py +│ └── test_orders.py +├── pytest.ini +├── requirements.txt +├── .gitignore +└── README.md + +Установка зависимостей + +python -m venv venv +source venv/Scripts/activate +pip install -r requirements.txt + +Запуск тестов +pytest + +Формирование отчёта Allure + +Сбор результатов +pytest --alluredir=allure-results + +Просмотр отчёта (если установлен Allure CLI) +allure serve allure-results + +Или создание HTML-отчёта: + +allure generate allure-results -o allure-report --clean + +Результат + +Все тесты проходят успешно. +Результаты выполнения тестов отображаются в отчёте Allure. \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..046e179 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +testpaths = tests +pythonpath = . +addopts = -v \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b947473 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pytest +requests +allure-pytest \ No newline at end of file diff --git a/src/api.py b/src/api.py new file mode 100644 index 0000000..64b4657 --- /dev/null +++ b/src/api.py @@ -0,0 +1,44 @@ +import allure +import requests + +from src.config import BASE_URL, REGISTER, LOGIN, INGREDIENTS, ORDERS, USER + + +class StellarApi: + def __init__(self): + self.session = requests.Session() + + @allure.step("POST {path}") + def post(self, path: str, json: dict | None = None, headers: dict | None = None): + return self.session.post(BASE_URL + path, json=json, headers=headers) + + @allure.step("GET {path}") + def get(self, path: str, headers: dict | None = None): + return self.session.get(BASE_URL + path, headers=headers) + + @allure.step("DELETE {path}") + def delete(self, path: str, headers: dict | None = None): + return self.session.delete(BASE_URL + path, headers=headers) + + @allure.step("Зарегистрировать пользователя") + def register(self, payload: dict): + return self.post(REGISTER, json=payload) + + @allure.step("Выполнить логин пользователя") + def login(self, payload: dict): + return self.post(LOGIN, json=payload) + + @allure.step("Получить список ингредиентов") + def get_ingredients(self): + return self.get(INGREDIENTS) + + @allure.step("Создать заказ") + def create_order(self, ingredients: list[str] | None = None, token: str | None = None): + headers = {"Authorization": token} if token else None + body = {"ingredients": ingredients} if ingredients is not None else {} + return self.post(ORDERS, json=body, headers=headers) + + @allure.step("Удалить пользователя") + def delete_user(self, token: str): + headers = {"Authorization": token} + return self.delete(USER, headers=headers) \ No newline at end of file diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..295f324 --- /dev/null +++ b/src/config.py @@ -0,0 +1,7 @@ +BASE_URL = "https://stellarburgers.education-services.ru/api" + +REGISTER = "/auth/register" +LOGIN = "/auth/login" +INGREDIENTS = "/ingredients" +ORDERS = "/orders" +USER = "/auth/user" \ No newline at end of file diff --git a/src/helpers.py b/src/helpers.py new file mode 100644 index 0000000..054a22a --- /dev/null +++ b/src/helpers.py @@ -0,0 +1,15 @@ +import random +import string + + +def rand_str(n: int = 10) -> str: + chars = string.ascii_letters + string.digits + return "".join(random.choice(chars) for _ in range(n)) + + +def generate_user() -> dict: + return { + "email": f"eva_{rand_str(8).lower()}@yandex.ru", + "password": rand_str(12), + "name": f"Eva_{rand_str(6)}", + } \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..ad263d1 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,81 @@ +import pytest +import allure + +from src.helpers import generate_user +from src.api import StellarApi + + +@pytest.fixture +def api(): + return StellarApi() + + +@pytest.fixture +def cleanup_user(api): + tokens = [] + + yield tokens + + for token in tokens: + with allure.step("Удаляем пользователя после теста"): + api.delete_user(token) + + +@pytest.fixture +def registered_user(api, cleanup_user): + user = generate_user() + + with allure.step("Регистрируем пользователя для предусловия теста"): + response = api.register(user) + + access_token = None + if response.status_code == 200: + body = response.json() + access_token = body.get("accessToken") + cleanup_user.append(access_token) + + return { + "email": user["email"], + "password": user["password"], + "name": user["name"], + "response": response, + "access_token": access_token, + } + + +@pytest.fixture +def token(api, registered_user): + with allure.step("Логинимся и получаем токен"): + response = api.login( + { + "email": registered_user["email"], + "password": registered_user["password"], + } + ) + + access_token = None + if response.status_code == 200: + body = response.json() + access_token = body.get("accessToken") + + return { + "response": response, + "access_token": access_token, + } + + +@pytest.fixture +def ingredient_ids(api): + with allure.step("Получаем список ингредиентов"): + response = api.get_ingredients() + + ids = [] + if response.status_code == 200: + body = response.json() + data = body.get("data", []) + ids = [item["_id"] for item in data] + + return { + "response": response, + "ids": ids, + } \ No newline at end of file diff --git a/tests/test_create_user.py b/tests/test_create_user.py new file mode 100644 index 0000000..eddf639 --- /dev/null +++ b/tests/test_create_user.py @@ -0,0 +1,64 @@ +import allure + +from src.helpers import generate_user + + +@allure.epic("Создание пользователя") +class TestCreateUser: + + @allure.title("Можно создать уникального пользователя") + def test_create_unique_user_success(self, api, cleanup_user): + user = generate_user() + + with allure.step("Регистрируем уникального пользователя"): + response = api.register(user) + + body = response.json() + + if response.status_code == 200 and "accessToken" in body: + cleanup_user.append(body["accessToken"]) + + assert response.status_code == 200 + assert body.get("success") is True + assert "accessToken" in body + assert "refreshToken" in body + assert body["user"]["email"] == user["email"] + assert body["user"]["name"] == user["name"] + + @allure.title("Нельзя создать двух одинаковых пользователей") + def test_create_duplicate_user_returns_error(self, api, cleanup_user): + user = generate_user() + + with allure.step("Регистрируем пользователя впервые"): + first_response = api.register(user) + + first_body = first_response.json() + + if first_response.status_code == 200 and "accessToken" in first_body: + cleanup_user.append(first_body["accessToken"]) + + with allure.step("Повторно отправляем запрос на регистрацию того же пользователя"): + response = api.register(user) + + body = response.json() + + assert response.status_code == 403 + assert body.get("success") is False + assert body.get("message") == "User already exists" + + @allure.title("Нельзя создать пользователя без обязательного поля") + def test_create_user_without_required_field_returns_error(self, api, cleanup_user): + user = generate_user() + user.pop("name") + + with allure.step("Отправляем запрос на регистрацию без поля name"): + response = api.register(user) + + body = response.json() + + if response.status_code == 200 and "accessToken" in body: + cleanup_user.append(body["accessToken"]) + + assert response.status_code == 403 + assert body.get("success") is False + assert body.get("message") == "Email, password and name are required fields" \ No newline at end of file diff --git a/tests/test_login.py b/tests/test_login.py new file mode 100644 index 0000000..797b6d3 --- /dev/null +++ b/tests/test_login.py @@ -0,0 +1,37 @@ +import allure + + +@allure.feature("Логин пользователя") +class TestLogin: + + @allure.title("Вход под существующим пользователем") + def test_login_existing_user(self, api, registered_user): + with allure.step("Логинимся под существующим пользователем"): + response = api.login( + { + "email": registered_user["email"], + "password": registered_user["password"], + } + ) + + body = response.json() + + assert response.status_code == 200 + assert body.get("success") is True + assert "accessToken" in body + assert "refreshToken" in body + + @allure.title("Вход с неверным логином и паролем") + def test_login_wrong_credentials(self, api): + with allure.step("Логинимся с неверными данными"): + response = api.login( + { + "email": "nope@yandex.ru", + "password": "wrong", + } + ) + + body = response.json() + + assert response.status_code == 401 + assert body.get("success") is False \ No newline at end of file diff --git a/tests/test_orders.py b/tests/test_orders.py new file mode 100644 index 0000000..84f3679 --- /dev/null +++ b/tests/test_orders.py @@ -0,0 +1,61 @@ +import allure + + +@allure.epic("Создание заказа") +class TestOrders: + + @allure.title("Создание заказа с авторизацией") + def test_create_order_with_auth(self, api, token, ingredient_ids): + response = api.create_order( + ingredients=ingredient_ids["ids"], + token=token["access_token"] + ) + + body = response.json() + + assert response.status_code == 200 + assert body.get("success") is True + + @allure.title("Создание заказа без авторизации") + def test_create_order_without_auth(self, api, ingredient_ids): + response = api.create_order( + ingredients=ingredient_ids["ids"], + token=None + ) + + body = response.json() + + assert response.status_code == 200 + assert body.get("success") is True + + @allure.title("Создание заказа с ингредиентами") + def test_create_order_with_ingredients(self, api, ingredient_ids): + response = api.create_order( + ingredients=ingredient_ids["ids"], + token=None + ) + + body = response.json() + + assert response.status_code == 200 + assert body.get("success") is True + + @allure.title("Создание заказа без ингредиентов") + def test_create_order_without_ingredients(self, api): + response = api.create_order() + + body = response.json() + + assert response.status_code == 400 + assert body.get("success") is False + + @allure.title("Создание заказа с неверным хешем") + def test_create_order_with_invalid_hash(self, api): + response = api.create_order( + ingredients=["invalid_hash"] + ) + + body = response.json() + + assert response.status_code == 400 + assert body.get("success") is False \ No newline at end of file