Обновить doc/doc.md
This commit is contained in:
1 parent
246f1cc34c
commit
8849b7449e
1 file changed
-438
-438
@@ -1,441 +1,3 @@
|
||||
```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-токена.
|
||||
|
||||
Reference in new issue
Block a user