uploader-bot/tests/conftest.py

171 lines
6.2 KiB
Python
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.

import asyncio
import base64
import json
import os
import random
import string
from contextlib import asynccontextmanager
from dataclasses import asdict
from typing import Any, Dict, Generator, AsyncGenerator, Callable, Optional
import pytest
# Инициализация менеджера Ed25519, если доступен
try:
from app.core.crypto import init_ed25519_manager, get_ed25519_manager, ContentCipher
except Exception: # при статическом анализе или изолированном запуске тестов
init_ed25519_manager = None # type: ignore
get_ed25519_manager = None # type: ignore
ContentCipher = None # type: ignore
# FastAPI тест-клиент
try:
from fastapi import FastAPI
from fastapi.testclient import TestClient
# Основной FastAPI вход
from app.fastapi_main import app as fastapi_app # type: ignore
except Exception:
FastAPI = None # type: ignore
TestClient = None # type: ignore
fastapi_app = None # type: ignore
@pytest.fixture(scope="session", autouse=True)
def seed_random() -> None:
random.seed(1337)
@pytest.fixture(scope="session")
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
# pytest-asyncio: собственный event loop с session scope
loop = asyncio.new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="session")
def ed25519_manager() -> Any:
"""
Глобальный менеджер Ed25519 для подписей. Если доступна функция инициализации — вызываем.
"""
if init_ed25519_manager:
init_ed25519_manager()
if get_ed25519_manager:
return get_ed25519_manager()
class _Dummy: # fallback на случай отсутствия
public_key_hex = "00"*32
def sign_message(self, payload: Dict[str, Any]) -> str:
data = json.dumps(payload, sort_keys=True).encode("utf-8")
return base64.b64encode(data) .decode("ascii")
def verify_signature(self, payload: Dict[str, Any], signature: str, pub: str) -> bool:
try:
_ = base64.b64decode(signature.encode("ascii"))
return True
except Exception:
return False
return _Dummy()
@pytest.fixture(scope="session")
def content_cipher() -> Any:
"""
Экземпляр AES-256-GCM шифратора контента.
"""
if ContentCipher:
return ContentCipher()
class _DummyCipher:
KEY_SIZE = 32
NONCE_SIZE = 12
def generate_content_key(self, seed: Optional[bytes] = None) -> bytes:
return os.urandom(self.KEY_SIZE)
def encrypt_content(self, plaintext: bytes, key: bytes, metadata: Optional[Dict[str, Any]] = None,
associated_data: Optional[bytes] = None, sign_with_ed25519: bool = True) -> Dict[str, Any]:
# Псевдо-шифрование для fallback
ct = base64.b64encode(plaintext).decode("ascii")
nonce = base64.b64encode(b"\x00" * 12).decode("ascii")
tag = base64.b64encode(b"\x00" * 16).decode("ascii")
return {"ciphertext_b64": ct, "nonce_b64": nonce, "tag_b64": tag, "content_id": "deadbeef", "metadata": metadata or {}}
def decrypt_content(self, ciphertext_b64: str, nonce_b64: str, tag_b64: str, key: bytes,
associated_data: Optional[bytes] = None) -> bytes:
return base64.b64decode(ciphertext_b64.encode("ascii"))
def verify_content_integrity(self, encrypted_obj: Dict[str, Any], expected_metadata: Optional[Dict[str, Any]] = None,
verify_signature: bool = True):
return True, None
return _DummyCipher()
@pytest.fixture(scope="session")
def fastapi_client() -> Any:
"""
Тестовый HTTP клиент FastAPI. Если приложение недоступно — пропускаем API тесты.
"""
if fastapi_app is None or TestClient is None:
pytest.skip("FastAPI app is not importable in this environment")
return TestClient(fastapi_app)
@pytest.fixture
def temp_large_bytes() -> bytes:
"""
Большой буфер для нагрузочных тестов ( ~10 MiB ).
"""
size = 10 * 1024 * 1024
return os.urandom(size)
@pytest.fixture
def small_sample_bytes() -> bytes:
return b"The quick brown fox jumps over the lazy dog."
@pytest.fixture
def random_content_key(content_cipher) -> bytes:
return content_cipher.generate_content_key()
class MockTONManager:
"""
Мок TON NFT менеджера/клиента: имитирует выдачу/проверку лицензий.
"""
def __init__(self) -> None:
self._store: Dict[str, Dict[str, Any]] = {}
def issue_license(self, content_id: str, owner_address: str) -> Dict[str, Any]:
lic_id = "LIC_" + ''.join(random.choices(string.ascii_uppercase + string.digits, k=12))
nft_addr = "EQ" + ''.join(random.choices(string.ascii_letters + string.digits, k=40))
lic = {
"license_id": lic_id,
"content_id": content_id,
"owner_address": owner_address,
"nft_address": nft_addr,
}
self._store[lic_id] = lic
return lic
def get_license(self, license_id: str) -> Optional[Dict[str, Any]]:
return self._store.get(license_id)
def verify_access(self, license_id: str, content_id: str, owner_address: str) -> bool:
lic = self._store.get(license_id)
return bool(lic and lic["content_id"] == content_id and lic["owner_address"] == owner_address)
@pytest.fixture
def ton_mock() -> MockTONManager:
return MockTONManager()
class MockConverter:
"""
Мок конвертера: имитация успешной/ошибочной конвертации.
"""
def convert(self, content: bytes, fmt: str = "mp3") -> bytes:
if not content:
raise ValueError("empty content")
# Имитация преобразования: добавим префикс для отладки
return f"[converted:{fmt}]".encode("utf-8") + content
@pytest.fixture
def converter_mock() -> MockConverter:
return MockConverter()