## 🔑 Подготовка и авторизация По условиям задания запрещено использовать личную учетную запись для получения 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**. """ } ### Структура репозитория ```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 ```