Files
2026-09-17 21:31:09 +00:00

25 KiB

🔑 Подготовка и авторизация

По условиям задания запрещено использовать личную учетную запись для получения OAuth-токена.

  1. Зарегистрируйте отдельный тестовый аккаунт на Яндексе или воспользуйтесь Полигоном API Яндекс Диска.
  2. Получите OAuth-токен с правами доступа к Яндекс Диску (cloud_api:disk.read, cloud_api:disk.write или полный доступ к Диску).
  3. Скопируйте полученный токен.

🚀 Локальный запуск

1. Клонирование репозитория

git clone [https://github.com/](https://github.com/)<your-username>/yandex-disk-api-tests.git
cd yandex-disk-api-tests

2. Создание и активация виртуального окружения

  • Linux / macOS:
python3 -m venv .venv
source .venv/bin/activate

  • Windows (PowerShell):
python -m venv .venv
.venv\\Scripts\\Activate.ps1

3. Установка зависимостей

pip install --upgrade pip
pip install -r requirements.txt

4. Настройка переменных окружения

Создайте файл .env на основе примера .env.example:

cp .env.example .env

Откройте файл .env и укажите ваш токен:

YANDEX_DISK_TOKEN=ваш_oauth_токен_здесь

5. Запуск тестов

  • Запуск всех тестов с подробным выводом:
pytest

  • Запуск только CRUD-тестов:
pytest -m crud

  • Запуск только негативных тестов:
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_токен>
  1. После сохранения секрета тесты будут автоматически запускаться во вкладке Actions. """ }

Структура репозитория

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

pytest>=8.0.0
requests>=2.31.0
python-dotenv>=1.0.0
allure-pytest>=2.13.0

.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

# OAuth-токен от тестовой учетной записи Яндекс Диска / Полигона API
# Получить токен: https://yandex.ru/dev/disk/poligon/
YANDEX_DISK_TOKEN=y0_AgAAAA...

pytest.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

"""
Клиент для взаимодействия с 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

"""
Конфигурация фикстур 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

"""
Набор автоматических тестов для проверки 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

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

# 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 Яндекс Диска или создайте приложение в Яндекс OAuth с правами на Яндекс Диск.
  3. Скопируйте полученный OAuth-токен.

🚀 Локальный запуск

1. Клонирование репозитория

git clone [https://github.com/](https://github.com/)<your-username>/yandex-disk-api-tests.git
cd yandex-disk-api-tests

2. Развертывание виртуального окружения

  • Linux / macOS:
python3 -m venv .venv
source .venv/bin/activate

  • Windows (PowerShell):
python -m venv .venv
.venv\Scripts\Activate.ps1

3. Установка зависимостей

pip install --upgrade pip
pip install -r requirements.txt

4. Настройка переменных окружения

Создайте файл .env на основе шаблона:

cp .env.example .env

Укажите ваш OAuth-токен в файле .env:

YANDEX_DISK_TOKEN=ваш_токен_здесь

5. Запуск автотестов

  • Запуск всех тестов:
pytest

  • Запуск только CRUD-тестов:
pytest -m crud

  • Запуск только негативных тестов:
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