From 8849b7449eef3516ad07e5e6e004dcdb96145318 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 17 Sep 2026 21:30:12 +0000 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20doc/doc.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/doc.md | 438 ----------------------------------------------------- 1 file changed, 438 deletions(-) diff --git a/doc/doc.md b/doc/doc.md index 12ef929..5c142cf 100644 --- a/doc/doc.md +++ b/doc/doc.md @@ -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-токена.