Files
Yandex-disk-API-test/doc/doc.md
T
2026-09-17 21:27:51 +00:00

1216 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
```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
```