```python import os import zipfile # Prepare the project files project_structure = { "requirements.txt": """pytest>=8.0.0 requests>=2.31.0 python-dotenv>=1.0.0 allure-pytest>=2.13.0 """, ".env.example": """# OAuth-токен от Яндекс Полигона / тестового аккаунта # Получить токен можно на странице полигона API Яндекс Диска: # https://yandex.ru/dev/disk/poligon/ YANDEX_DISK_TOKEN=y0_AgAAAA... """, ".gitignore": """# Python artifacts __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Virtual environments venv/ .venv/ env/ ENV/ # Environment variables & secrets .env .env.local # IDE & editors .vscode/ .idea/ *.swp *.swo # Testing & coverage .pytest_cache/ .coverage htmlcov/ allure-results/ allure-report/ .cache/ """, "pytest.ini": """[pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -v -s --tb=short markers = crud: CRUD operations tests negative: Negative tests """, "api/__init__.py": "", "api/disk_client.py": '''""" Модуль клиента для взаимодействия с REST API Яндекс Диска. Документация API: https://yandex.ru/dev/disk/api/concepts/about.html Полигон: https://cloud-api.yandex.net """ import requests from typing import Optional, Dict, Any class YandexDiskClient: """ HTTP-клиент-обёртка для выполнения запросов к API Яндекс Диска. Инкапсулирует базовые методы (GET, POST, PUT, DELETE) и авторизацию. """ BASE_URL = "https://cloud-api.yandex.net/v1/disk/resources" def __init__(self, token: str, timeout: int = 10): """ Инициализация сессии с предустановленными заголовками авторизации. :param token: OAuth-токен сервиса Яндекс :param timeout: Дефолтный таймаут для HTTP-запросов в секундах """ self.timeout = timeout self.session = requests.Session() # Все запросы к API Яндекс Диска требуют заголовок Authorization с префиксом OAuth self.session.headers.update({ "Authorization": f"OAuth {token}", "Accept": "application/json", "Content-Type": "application/json" }) def create_folder(self, path: str) -> requests.Response: """ PUT /v1/disk/resources Создание папки на Яндекс Диске. :param path: Путь к создаваемой папке (например, 'test_dir' или 'disk:/test_dir') :return: Объект ответа requests.Response (201 Created при успехе) """ url = self.BASE_URL params = {"path": path} return self.session.put(url, params=params, timeout=self.timeout) def get_resource_info(self, path: str, fields: Optional[str] = None) -> requests.Response: """ GET /v1/disk/resources Получение метаинформации о ресурсе (файле или папке). :param path: Путь к ресурсу на Диске :param fields: Список возвращаемых полей через запятую (для фильтрации ответа) :return: Объект ответа requests.Response (200 OK при успехе) """ url = self.BASE_URL params = {"path": path} if fields: params["fields"] = fields return self.session.get(url, params=params, timeout=self.timeout) def publish_resource(self, path: str) -> requests.Response: """ POST /v1/disk/resources/publish Публикация ресурса (создание публичной ссылки). :param path: Путь к ресурсу, который необходимо опубликовать :return: Объект ответа requests.Response (200 OK при успехе) """ url = f"{self.BASE_URL}/publish" params = {"path": path} return self.session.post(url, params=params, timeout=self.timeout) def unpublish_resource(self, path: str) -> requests.Response: """ PUT /v1/disk/resources/unpublish Закрытие публичного доступа к ресурсу. :param path: Путь к ресурсу, доступ к которому закрывается :return: Объект ответа requests.Response (200 OK при успехе) """ url = f"{self.BASE_URL}/unpublish" params = {"path": path} return self.session.put(url, params=params, timeout=self.timeout) def delete_resource(self, path: str, permanently: bool = True) -> requests.Response: """ DELETE /v1/disk/resources Удаление файла или папки. :param path: Путь к удаляемому ресурсу :param permanently: True — удалить безвозвратно мимо Корзины; False — переместить в Корзину :return: Объект ответа requests.Response (204 No Content при успешном синхронном удалении) """ url = self.BASE_URL params = { "path": path, "permanently": str(permanently).lower() } return self.session.delete(url, params=params, timeout=self.timeout) ''', "tests/__init__.py": "", "tests/conftest.py": '''""" Конфигурационный файл Pytest с фикстурами для инициализации клиента и тестовых данных. """ import os import uuid import pytest from dotenv import load_dotenv from api.disk_client import YandexDiskClient # Загрузка переменных окружения из .env файла load_dotenv() @pytest.fixture(scope="session") def auth_token() -> str: """ Фикстура получения токена авторизации. Проверяет наличие токена в переменных окружения. Если токен не задан, тесты пропускаются с информативным сообщением. """ token = os.getenv("YANDEX_DISK_TOKEN") if not token or token.strip() == "": pytest.skip( "YANDEX_DISK_TOKEN не найден в переменных окружения. " "Задайте его в .env файле или через export YANDEX_DISK_TOKEN=..." ) return token.strip() @pytest.fixture(scope="session") def disk_client(auth_token: str) -> YandexDiskClient: """ Фикстура создания экземпляра API-клиента на всю тестовую сессию. """ return YandexDiskClient(token=auth_token) @pytest.fixture(scope="function") def temp_folder(disk_client: YandexDiskClient): """ Фикстура создания изолированной временной папки для теста. Гарантирует удаление созданной папки после завершения теста (Teardown). """ unique_suffix = uuid.uuid4().hex[:8] folder_path = f"pytest_temp_{unique_suffix}" # Setup: создаем папку create_resp = disk_client.create_folder(folder_path) assert create_resp.status_code == 201, f"Не удалось подготовить фикстуру папки: {create_resp.text}" # Передаем управление в тест yield folder_path # Teardown: удаляем папку навсегда (мимо корзины) disk_client.delete_resource(folder_path, permanently=True) ''', "tests/test_disk_crud.py": '''""" Набор автотестов для проверки REST API Яндекс Диска. Покрывает методы: - PUT: создание папки (создание ресурса) - GET: получение метаданных ресурса - POST: публикация ресурса (открытие публичного доступа) - DELETE: удаление ресурса (очистка) А также негативные сценарии (дублирование, несуществующий ресурс). """ import uuid import pytest from api.disk_client import YandexDiskClient @pytest.mark.crud class TestYandexDiskCRUD: """Тесты на основные CRUD-операции с ресурсами Яндекс Диска.""" def test_create_folder_put_success(self, disk_client: YandexDiskClient): """ Проверка создания папки (PUT-запрос). Ожидаемый результат: HTTP 201 Created, в ответе присутствует ссылка на созданный ресурс (href). """ folder_name = f"dir_create_{uuid.uuid4().hex[:8]}" try: response = disk_client.create_folder(folder_name) assert response.status_code == 201, f"Ожидался 201, получено {response.status_code}: {response.text}" data = response.json() assert "href" in data, "В теле ответа отсутствует поле 'href'" assert "method" in data, "В теле ответа отсутствует поле 'method'" assert data["method"] == "GET" finally: # Очистка ресурса после теста disk_client.delete_resource(folder_name, permanently=True) def test_get_resource_info_get_success(self, disk_client: YandexDiskClient, temp_folder: str): """ Проверка получения метаданных ресурса (GET-запрос). Ожидаемый результат: HTTP 200 OK, тип ресурса 'dir', имя совпадает с созданным. """ response = disk_client.get_resource_info(temp_folder) assert response.status_code == 200, f"Ожидался 200, получено {response.status_code}: {response.text}" data = response.json() assert data.get("type") == "dir", f"Ожидался тип 'dir', получено {data.get('type')}" assert data.get("name") == temp_folder, f"Имя ресурса {data.get('name')} не совпадает с {temp_folder}" assert "created" in data, "Поле 'created' отсутствует в метаданных" def test_publish_resource_post_success(self, disk_client: YandexDiskClient, temp_folder: str): """ Проверка публикации ресурса (POST-запрос). Ожидаемый результат: HTTP 200 OK, у папки появляется публичный URL и ключ. """ response = disk_client.publish_resource(temp_folder) assert response.status_code == 200, f"Ожидался 200, получено {response.status_code}: {response.text}" # Проверяем метаданные папки после публикации через GET info_resp = disk_client.get_resource_info(temp_folder) assert info_resp.status_code == 200 info_data = info_resp.json() assert "public_url" in info_data, "После публикации поле 'public_url' отсутствует" assert "public_key" in info_data, "После публикации поле 'public_key' отсутствует" assert info_data["public_url"].startswith("https://"), "Публичный URL имеет некорректный формат" def test_delete_folder_delete_success(self, disk_client: YandexDiskClient): """ Проверка удаления ресурса (DELETE-запрос). Ожидаемый результат: HTTP 204 No Content при удалении, последующий GET возвращает 404 Not Found. """ folder_name = f"dir_delete_{uuid.uuid4().hex[:8]}" # Предварительно создаем папку для последующего удаления create_resp = disk_client.create_folder(folder_name) assert create_resp.status_code == 201 # Удаление delete_resp = disk_client.delete_resource(folder_name, permanently=True) assert delete_resp.status_code == 204, f"Ожидался 204, получено {delete_resp.status_code}: {delete_resp.text}" # Проверка отсутствия ресурса get_resp = disk_client.get_resource_info(folder_name) assert get_resp.status_code == 404, f"Ресурс все еще существует, статус {get_resp.status_code}" @pytest.mark.negative class TestYandexDiskNegative: """Негативные тесты для проверки корректности обработки ошибок API.""" def test_create_duplicate_folder_put_conflict(self, disk_client: YandexDiskClient, temp_folder: str): """ Повторное создание уже существующей папки. Ожидаемый результат: HTTP 409 Conflict. """ response = disk_client.create_folder(temp_folder) assert response.status_code == 409, f"Ожидался 409 Conflict, получено {response.status_code}" data = response.json() assert data.get("error") == "PlatformResourceAlreadyExists", f"Неожиданный код ошибки: {data.get('error')}" def test_get_non_existent_resource_get_not_found(self, disk_client: YandexDiskClient): """ Запрос метаданных несуществующего ресурса. Ожидаемый результат: HTTP 404 Not Found. """ non_existent_path = f"does_not_exist_{uuid.uuid4().hex}" response = disk_client.get_resource_info(non_existent_path) assert response.status_code == 404, f"Ожидался 404 Not Found, получено {response.status_code}" data = response.json() assert data.get("error") == "DiskNotFoundError", f"Неожиданный код ошибки: {data.get('error')}" def test_unauthorized_access(self): """ Выполнение запроса с недействительным токеном. Ожидаемый результат: HTTP 401 Unauthorized. """ invalid_client = YandexDiskClient(token="invalid_dummy_token_123") response = invalid_client.get_resource_info("any_folder") assert response.status_code == 401, f"Ожидался 401 Unauthorized, получено {response.status_code}" data = response.json() assert data.get("error") == "UnauthorizedError", f"Неожиданный код ошибки: {data.get('error')}" ''', ".github/workflows/tests.yml": """name: Run Yandex Disk API Tests on: push: branches: [ main, master ] pull_request: branches: [ main, master ] workflow_dispatch: jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python 3.11 uses: actions/setup-python@v5 with: python-version: "3.11" cache: "pip" - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Run pytest env: YANDEX_DISK_TOKEN: ${{ secrets.YANDEX_DISK_TOKEN }} run: | pytest -v """, "README.md": """# Yandex Disk REST API Automation Tests Проект автоматизированного тестирования сервиса **Яндекс Диск** на базе **Python**, **pytest** и **requests**. Проект разработан в соответствии с техническим заданием и проверяет работу ключевых методов REST API: - **PUT** — создание ресурса (папки) - **GET** — чтение метаданных ресурса - **POST** — публикация ресурса (получение публичной ссылки) - **DELETE** — удаление ресурса (очистка диска) Также включены негативные сценарии (попытка создания дубликата, обращение к несуществующему ресурсу, невалидный токен). --- ## 🛠 Стек технологий - **Python 3.10+** - **Pytest** — тестовый фреймворк, фикстуры, параметризация - **Requests** — HTTP-клиент - **Python-dotenv** — управление секретами и переменными окружения - **GitHub Actions** — CI/CD пайплайн для автоматического прогона тестов --- ## 📁 Структура проекта ```text yandex-disk-api-tests/ ├── .github/ │ └── workflows/ │ └── tests.yml # CI/CD пайплайн (GitHub Actions) ├── api/ │ ├── __init__.py │ └── disk_client.py # API-клиент (обёртка над requests с методами GET, POST, PUT, DELETE) ├── tests/ │ ├── __init__.py │ ├── conftest.py # Сессионные фикстуры и teardown временных ресурсов │ └── test_disk_crud.py # Позитивные и негативные тестовые сценарии ├── .env.example # Шаблон переменных окружения ├── .gitignore # Исключения для git ├── pytest.ini # Конфигурация запуска Pytest ├── requirements.txt # Список зависимостей └── README.md # Документация проекта ``` --- ## 🔑 Подготовка и авторизация По условиям задания запрещено использовать личную учетную запись для получения OAuth-токена. 1. Зарегистрируйте отдельный тестовый аккаунт на Яндексе или воспользуйтесь [Полигоном API Яндекс Диска](https://yandex.ru/dev/disk/poligon/). 2. Получите OAuth-токен с правами доступа к Яндекс Диску (`cloud_api:disk.read`, `cloud_api:disk.write` или полный доступ к Диску). 3. Скопируйте полученный токен. --- ## 🚀 Локальный запуск ### 1. Клонирование репозитория ```bash git clone [https://github.com/](https://github.com/)/yandex-disk-api-tests.git cd yandex-disk-api-tests ``` ### 2. Создание и активация виртуального окружения * **Linux / macOS:** ```bash python3 -m venv .venv source .venv/bin/activate ``` * **Windows (PowerShell):** ```powershell python -m venv .venv .venv\\Scripts\\Activate.ps1 ``` ### 3. Установка зависимостей ```bash pip install --upgrade pip pip install -r requirements.txt ``` ### 4. Настройка переменных окружения Создайте файл `.env` на основе примера `.env.example`: ```bash cp .env.example .env ``` Откройте файл `.env` и укажите ваш токен: ```ini YANDEX_DISK_TOKEN=ваш_oauth_токен_здесь ``` ### 5. Запуск тестов * Запуск всех тестов с подробным выводом: ```bash pytest ``` * Запуск только CRUD-тестов: ```bash pytest -m crud ``` * Запуск только негативных тестов: ```bash pytest -m negative ``` --- ## ⚙️ Настройка CI/CD (GitHub Actions) В репозитории настроен автоматический запуск тестов при каждом пуше и Pull Request в ветки `main` / `master`. Чтобы тесты выполнялись в GitHub Actions: 1. Перейдите в ваш репозиторий на GitHub: **Settings** -> **Secrets and variables** -> **Actions**. 2. Нажмите кнопку **New repository secret**. 3. Добавьте секрет: * **Name:** `YANDEX_DISK_TOKEN` * **Value:** `<ваш_oauth_токен>` 4. После сохранения секрета тесты будут автоматически запускаться во вкладке **Actions**. """ } # Write files locally and create a zip archive zip_filename = "yandex-disk-api-tests.zip" with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zip_file: for file_path, content in project_structure.items(): dir_name = os.path.dirname(file_path) if dir_name: os.makedirs(dir_name, exist_ok=True) with open(file_path, "w", encoding="utf-8") as f: f.write(content) zip_file.write(file_path) print(f"Successfully generated {zip_filename} with {len(project_structure)} files.") ``` ```text?code_stdout&code_event_index=1 Successfully generated yandex-disk-api-tests.zip with 11 files. ``` Готовый архив с проектом автотестов подготовлен: Ниже представлены исходные файлы, конфигурация и документация для создания и публикации репозитория на GitHub. --- ### Структура репозитория ```text yandex-disk-api-tests/ ├── .github/ │ └── workflows/ │ └── tests.yml # CI/CD пайплайн для GitHub Actions ├── api/ │ ├── __init__.py # Маркер пакета Python │ └── disk_client.py # HTTP-клиент (API Wrapper для Яндекс Диска) ├── tests/ │ ├── __init__.py # Маркер пакета Python │ ├── conftest.py # Сессионные фикстуры и teardown временных ресурсов │ └── test_disk_crud.py # Тестовые сценарии (GET, POST, PUT, DELETE + негативные) ├── .env.example # Пример файла с переменными окружения ├── .gitignore # Правила исключения временных файлов и секретов ├── pytest.ini # Глобальные параметры запуска Pytest ├── requirements.txt # Зависимости проекта └── README.md # Описание проекта и инструкция по запуску ``` --- ### 1. Зависимости и конфигурация #### `requirements.txt` ```text pytest>=8.0.0 requests>=2.31.0 python-dotenv>=1.0.0 allure-pytest>=2.13.0 ``` #### `.gitignore` ```gitignore # Секреты и локальные переменные окружения .env .env.local *.env # Виртуальные окружения Python venv/ .venv/ env/ ENV/ # Байткод и кэш Python __pycache__/ *.py[cod] *$py.class *.so build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Конфигурации IDE и редакторов .vscode/ .idea/ *.swp *.swo # Артефакты тестов и отчетов .pytest_cache/ .coverage htmlcov/ allure-results/ allure-report/ .cache/ ``` #### `.env.example` ```bash # OAuth-токен от тестовой учетной записи Яндекс Диска / Полигона API # Получить токен: https://yandex.ru/dev/disk/poligon/ YANDEX_DISK_TOKEN=y0_AgAAAA... ``` #### `pytest.ini` ```ini [pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -v -s --tb=short markers = crud: основные CRUD-сценарии API (GET, POST, PUT, DELETE) negative: негативные тесты на обработку ошибок ``` --- ### 2. Исходный код клиента и тестов #### `api/__init__.py` *(Пустой файл)* #### `api/disk_client.py` ```python """ Клиент для взаимодействия с REST API Яндекс Диска v1. Официальная документация: https://yandex.ru/dev/disk/api/concepts/about.html Полигон API: https://cloud-api.yandex.net """ from typing import Optional import requests class YandexDiskClient: """ HTTP-клиент-обёртка для отправки запросов к API Яндекс Диска. Инкапсулирует методы GET, POST, PUT, DELETE, управление авторизацией и таймаутами. """ BASE_URL = "https://cloud-api.yandex.net/v1/disk/resources" def __init__(self, token: str, timeout: int = 10): """ Инициализирует клиент с преднастроенной сессией и OAuth-заголовком. :param token: OAuth-токен Яндекс Диска. :param timeout: Максимальное время ожидания ответа сервиса (в секундах). """ self.timeout = timeout self.session = requests.Session() # Яндекс Диск требует передавать токен в формате "OAuth <токен>" self.session.headers.update({ "Authorization": f"OAuth {token}", "Accept": "application/json", "Content-Type": "application/json" }) def create_folder(self, path: str) -> requests.Response: """ PUT /v1/disk/resources Создает новую папку на Яндекс Диске. :param path: Путь к создаваемой папке (например, 'test_folder'). :return: requests.Response (201 Created при успехе). """ params = {"path": path} return self.session.put(self.BASE_URL, params=params, timeout=self.timeout) def get_resource_info(self, path: str, fields: Optional[str] = None) -> requests.Response: """ GET /v1/disk/resources Возвращает метаданные файла или папки. :param path: Путь к запрашиваемому ресурсу. :param fields: Список запрашиваемых полей через запятую (для фильтрации). :return: requests.Response (200 OK при успехе). """ params = {"path": path} if fields: params["fields"] = fields return self.session.get(self.BASE_URL, params=params, timeout=self.timeout) def publish_resource(self, path: str) -> requests.Response: """ POST /v1/disk/resources/publish Публикует ресурс (делает доступным по публичной ссылке). :param path: Путь к публикуемому ресурсу. :return: requests.Response (200 OK при успехе). """ url = f"{self.BASE_URL}/publish" params = {"path": path} return self.session.post(url, params=params, timeout=self.timeout) def unpublish_resource(self, path: str) -> requests.Response: """ PUT /v1/disk/resources/unpublish Закрывает публичный доступ к ранее опубликованному ресурсу. :param path: Путь к ресурсу. :return: requests.Response (200 OK при успехе). """ url = f"{self.BASE_URL}/unpublish" params = {"path": path} return self.session.put(url, params=params, timeout=self.timeout) def delete_resource(self, path: str, permanently: bool = True) -> requests.Response: """ DELETE /v1/disk/resources Удаляет файл или папку. :param path: Путь к удаляемому ресурсу. :param permanently: True — удалить безвозвратно мимо Корзины; False — отправить в Корзину. :return: requests.Response (204 No Content при синхронном удалении). """ params = { "path": path, "permanently": str(permanently).lower() } return self.session.delete(self.BASE_URL, params=params, timeout=self.timeout) ``` #### `tests/__init__.py` *(Пустой файл)* #### `tests/conftest.py` ```python """ Конфигурация фикстур pytest для интеграционного тестирования. """ import os import uuid import pytest from dotenv import load_dotenv from api.disk_client import YandexDiskClient # Загружаем переменные окружения из файла .env (если он существует) load_dotenv() @pytest.fixture(scope="session") def auth_token() -> str: """ Сессионная фикстура получения OAuth-токена. Если токен не передан, прерывает прогон тестов с понятным сообщением. """ token = os.getenv("YANDEX_DISK_TOKEN") if not token or not token.strip(): pytest.skip( "Пропуск тестов: переменная окружения YANDEX_DISK_TOKEN не задана. " "Задайте её в файле .env или через экспорт окружения." ) return token.strip() @pytest.fixture(scope="session") def disk_client(auth_token: str) -> YandexDiskClient: """ Создает один общий экземпляр YandexDiskClient на всю тестовую сессию. """ return YandexDiskClient(token=auth_token) @pytest.fixture(scope="function") def temp_folder(disk_client: YandexDiskClient): """ Фикстура изолированной папки для теста с автоматической очисткой (Teardown). Создает директорию перед тестом и гарантированно удаляет её после. """ folder_name = f"pytest_fixture_{uuid.uuid4().hex[:8]}" # SETUP: Создание папки create_resp = disk_client.create_folder(folder_name) assert create_resp.status_code == 201, ( f"Ошибка инициализации фикстуры папки: {create_resp.status_code}, {create_resp.text}" ) # Передача управления в тест yield folder_name # TEARDOWN: Гарантированная очистка созданного ресурса disk_client.delete_resource(folder_name, permanently=True) ``` #### `tests/test_disk_crud.py` ```python """ Набор автоматических тестов для проверки REST API Яндекс Диска. Покрывает: - PUT: создание новой папки; - GET: получение метаданных ресурса; - POST: публикация ресурса и генерация публичной ссылки; - DELETE: удаление ресурса мимо корзины; - Негативные сценарии: дублирование, 404, неверный токен. """ import uuid import pytest from api.disk_client import YandexDiskClient @pytest.mark.crud class TestYandexDiskCRUD: """Тестирование успешных CRUD операций.""" def test_create_folder_put_success(self, disk_client: YandexDiskClient): """ Проверка метода PUT: создание новой папки. Ожидаемый статус: 201 Created. В теле ответа присутствует поле 'href'. """ folder_name = f"dir_create_{uuid.uuid4().hex[:8]}" try: response = disk_client.create_folder(folder_name) assert response.status_code == 201, f"Ожидался 201, получен {response.status_code}: {response.text}" data = response.json() assert "href" in data, "Ответ не содержит обязательное поле 'href'" assert data.get("method") == "GET", "Метод перехода по ссылке должен быть GET" finally: # Очищаем ресурс даже в случае падения ассерта disk_client.delete_resource(folder_name, permanently=True) def test_get_resource_info_get_success(self, disk_client: YandexDiskClient, temp_folder: str): """ Проверка метода GET: чтение метаданных существующей папки. Ожидаемый статус: 200 OK. Тип ресурса — 'dir', имя совпадает с запрошенным. """ response = disk_client.get_resource_info(temp_folder) assert response.status_code == 200, f"Ожидался 200, получен {response.status_code}: {response.text}" data = response.json() assert data.get("type") == "dir", f"Ожидался тип 'dir', получен {data.get('type')}" assert data.get("name") == temp_folder, f"Имя ресурса не совпадает: {data.get('name')} != {temp_folder}" assert "created" in data, "Метаданные должны содержать дату создания ('created')" def test_publish_resource_post_success(self, disk_client: YandexDiskClient, temp_folder: str): """ Проверка метода POST: публикация ресурса (открытие публичного доступа). Ожидаемый статус: 200 OK. У ресурса появляется публичный URL. """ response = disk_client.publish_resource(temp_folder) assert response.status_code == 200, f"Ожидался 200, получен {response.status_code}: {response.text}" # Запрашиваем актуальные метаданные через GET для верификации публикации info_resp = disk_client.get_resource_info(temp_folder) assert info_resp.status_code == 200 info_data = info_resp.json() assert "public_url" in info_data, "После публикации отсутствует ключ 'public_url'" assert info_data["public_url"].startswith("https://"), "Некорректный формат публичной ссылки" def test_delete_folder_delete_success(self, disk_client: YandexDiskClient): """ Проверка метода DELETE: удаление ресурса. Ожидаемый статус: 204 No Content. Последующий GET-запрос должен возвращать 404 Not Found. """ folder_name = f"dir_delete_{uuid.uuid4().hex[:8]}" # Шаг 1: Создаем ресурс create_resp = disk_client.create_folder(folder_name) assert create_resp.status_code == 201 # Шаг 2: Удаляем созданный ресурс delete_resp = disk_client.delete_resource(folder_name, permanently=True) assert delete_resp.status_code == 204, f"Ожидался 204, получен {delete_resp.status_code}: {delete_resp.text}" # Шаг 3: Проверяем, что ресурс действительно отсутствует get_resp = disk_client.get_resource_info(folder_name) assert get_resp.status_code == 404, f"Ресурс не удален: получен статус {get_resp.status_code}" @pytest.mark.negative class TestYandexDiskNegative: """Негативные сценарии для проверки валидации ошибок API.""" def test_create_duplicate_folder_conflict(self, disk_client: YandexDiskClient, temp_folder: str): """ Повторное создание уже существующей папки. Ожидаемый статус: 409 Conflict. """ response = disk_client.create_folder(temp_folder) assert response.status_code == 409, f"Ожидался 409, получен {response.status_code}" data = response.json() assert data.get("error") == "PlatformResourceAlreadyExists" def test_get_non_existent_resource_not_found(self, disk_client: YandexDiskClient): """ Запрос метаданных несуществующего ресурса. Ожидаемый статус: 404 Not Found. """ non_existent_path = f"dir_not_exist_{uuid.uuid4().hex}" response = disk_client.get_resource_info(non_existent_path) assert response.status_code == 404, f"Ожидался 404, получен {response.status_code}" data = response.json() assert data.get("error") == "DiskNotFoundError" def test_unauthorized_request(self): """ Отправка запроса с невалидным токеном авторизации. Ожидаемый статус: 401 Unauthorized. """ unauth_client = YandexDiskClient(token="invalid_dummy_token_999") response = unauth_client.get_resource_info("any_test_folder") assert response.status_code == 401, f"Ожидался 401, получен {response.status_code}" data = response.json() assert data.get("error") == "UnauthorizedError" ``` --- ### 3. CI/CD конфигурация #### `.github/workflows/tests.yml` ```yaml name: Run Yandex Disk API Tests on: push: branches: [ main, master ] pull_request: branches: [ main, master ] workflow_dispatch: jobs: test: name: API Automation Tests runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v4 - name: Set up Python 3.11 uses: actions/setup-python@v5 with: python-version: "3.11" cache: "pip" - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Run Pytest env: YANDEX_DISK_TOKEN: ${{ secrets.YANDEX_DISK_TOKEN }} run: | pytest -v ``` --- ### 4. Оформление для GitHub: `README.md` ```markdown # Yandex Disk REST API Automation Tests Проект автоматизированного тестирования сервиса **Яндекс Диск** на базе **Python**, **pytest** и **requests**. Проект проверяет работу ключевых HTTP-методов REST API: - **PUT** — создание папки (создание ресурса); - **GET** — получение метаданных созданного ресурса; - **POST** — публикация ресурса (открытие публичного доступа); - **DELETE** — удаление ресурса мимо Корзины; - **Негативные сценарии** — дублирование ресурса (409), поиск отсутствующего ресурса (404), невалидный токен (401). --- ## 🛠 Стек технологий - **Python 3.10+** - **pytest** — тестовый фреймворк, фикстуры setup/teardown, маркировка тестов - **requests** — HTTP-клиент с управлением сессиями - **python-dotenv** — чтение конфигураций и токенов из `.env` - **GitHub Actions** — автоматический прогон тестов при пуше/PR --- ## 📁 Структура проекта ```text yandex-disk-api-tests/ ├── .github/ │ └── workflows/ │ └── tests.yml # CI/CD пайплайн (GitHub Actions) ├── api/ │ ├── __init__.py │ └── disk_client.py # Клиент Яндекс Диска (GET, POST, PUT, DELETE) ├── tests/ │ ├── __init__.py │ ├── conftest.py # Сессионные фикстуры и изоляция тестовых данных │ └── test_disk_crud.py # Тестовые сценарии ├── .env.example # Пример переменных окружения ├── .gitignore # Исключения версионирования ├── pytest.ini # Настройки запуска pytest ├── requirements.txt # Внешние зависимости └── README.md # Документация ``` --- ## 🔑 Подготовка токена авторизации > ⚠️ По требованию задания запрещено использовать личную учетную запись для получения OAuth-токена. 1. Войдите в отдельный тестовый аккаунт на Яндексе. 2. Перейдите в [Полигон API Яндекс Диска](https://yandex.ru/dev/disk/poligon/) или создайте приложение в [Яндекс OAuth](https://oauth.yandex.ru/) с правами на Яндекс Диск. 3. Скопируйте полученный OAuth-токен. --- ## 🚀 Локальный запуск ### 1. Клонирование репозитория ```bash git clone [https://github.com/](https://github.com/)/yandex-disk-api-tests.git cd yandex-disk-api-tests ``` ### 2. Развертывание виртуального окружения * **Linux / macOS:** ```bash python3 -m venv .venv source .venv/bin/activate ``` * **Windows (PowerShell):** ```powershell python -m venv .venv .venv\Scripts\Activate.ps1 ``` ### 3. Установка зависимостей ```bash pip install --upgrade pip pip install -r requirements.txt ``` ### 4. Настройка переменных окружения Создайте файл `.env` на основе шаблона: ```bash cp .env.example .env ``` Укажите ваш OAuth-токен в файле `.env`: ```ini YANDEX_DISK_TOKEN=ваш_токен_здесь ``` ### 5. Запуск автотестов * Запуск всех тестов: ```bash pytest ``` * Запуск только CRUD-тестов: ```bash pytest -m crud ``` * Запуск только негативных тестов: ```bash pytest -m negative ``` --- ## ⚙️ Настройка CI/CD (GitHub Actions) В проекте настроен автоматический запуск тестов при каждом пуше в ветки `main`/`master`. Для работы тестов в GitHub Actions: 1. Перейдите в репозиторий: **Settings** → **Secrets and variables** → **Actions**. 2. Нажмите **New repository secret**. 3. Задайте имя: `YANDEX_DISK_TOKEN`, значение: `<ваш OAuth-токен>`. 4. Сохраните секрет. Теперь при каждом коммите тесты будут запускаться автоматически. ``` --- ### 5. Инструкция по публикации в GitHub Выполните следующие команды в терминале для отправки проекта в новый публичный репозиторий: ```bash # 1. Перейдите в папку проекта cd yandex-disk-api-tests # 2. Инициализируйте локальный репозиторий Git git init # 3. Добавьте файлы и сделайте первый коммит git add . git commit -m "feat: initial commit with Yandex Disk API tests and CI" # 4. Переименуйте ветку в main git branch -M main # 5. Привяжите удаленный репозиторий GitHub (замените URL на свой) git remote add origin https://github.com/<ваш-логин-github>/yandex-disk-api-tests.git # 6. Отправьте код на GitHub git push -u origin main ```