Files
Yandex-disk-API-test/doc/doc.md
T

1215 lines
44 KiB
Markdown
Raw Normal View History

2026-09-17 21:27:12 +00:00
```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/)<your-username>/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/)<your-username>/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
```