Files
blog.pyaqa.ru/tests/e2e/conftest.py
Sergey Vanyushkin 46cc06b596 feat: RBAC E2E тесты и фикс admin-прав для редактирования постов
Основные изменения:
- Добавлены E2E тесты для проверки ownership (TC-E2E-102/103):
  * test_admin_can_edit_any_post — admin может редактировать любой пост
  * test_user_cannot_edit_other_users_post — user не может редактировать чужой пост
- Исправлены use cases (UpdatePost, DeletePost, PublishPost) — добавлена проверка роли admin
- Обновлены web routes и API routes для передачи роли в use cases
- Добавлены unit тесты для admin-сценариев

Реструктуризация тестов:
- Удалены старые API тесты (tests/api/) — требуют переработки
- Удалены старые integration тесты (tests/integration/)
- Переработаны E2E тесты: удалены старые, добавлены новые с POM
- Добавлена документация тестов: FEATURE_*.md, TEST_MODEL.md, AGENTS.md

Инфраструктура:
- Добавлен MockKeycloakClient для dev-режима
- Добавлены статические файлы: EasyMDE, Highlight.js, стили markdown
- Обновлены шаблоны: base.html, post_form.html, post_detail.html
- Обновлена DI конфигурация и провайдеры

Документация:
- tests/FEATURE_RBAC.md — матрица тестов RBAC
- tests/FEATURE_POST_LIFECYCLE.md — тесты жизненного цикла поста
- tests/FEATURE_DOMAIN_FOUNDATION.md — тесты доменного слоя
- tests/FEATURE_INFRASTRUCTURE.md — тесты инфраструктуры
- tests/TEST_MODEL.md — глобальная матрица покрытия
- app/presentation/web/AGENTS.md — гайд по Web UI
- tests/AGENTS.md — гайд по тестированию
2026-05-07 19:55:15 +03:00

300 lines
7.5 KiB
Python

"""E2E test configuration for blog application.
Provides DevAuthProvider for cookie-based dev authentication
and role-specific browser context fixtures.
"""
from __future__ import annotations
from collections.abc import Generator
from typing import TYPE_CHECKING, Any
import pytest
from pytfm.auth import AuthProvider, TestUser
if TYPE_CHECKING:
from playwright.sync_api import Browser, BrowserContext, Page
class DevAuthProvider(AuthProvider):
"""Authentication provider for blog dev mode.
Bypasses real Keycloak by generating dev-specific tokens
recognized by MockKeycloakClient.
Attributes:
_users: Mapping of usernames to test users.
"""
def __init__(self) -> None:
"""Initialize the dev auth provider."""
self._users: dict[str, TestUser] = {}
def create_user(
self,
username: str,
password: str,
email: str,
roles: list[str] | None = None,
) -> TestUser:
"""Create a test user mapped to a dev token role.
Args:
username: Login name (used as display name).
password: Password (ignored in dev mode).
email: Email address.
roles: List of roles. First role determines dev token.
Returns:
Created TestUser instance.
"""
role = (roles or ["user"])[0]
user = TestUser(
id=f"dev-{role}",
username=username,
email=email,
password=password,
roles=roles or ["user"],
)
self._users[username] = user
return user
def login(self, username: str, password: str) -> str:
"""Return dev token for the user's role.
Args:
username: User login name.
password: User password (ignored).
Returns:
Dev authentication token string.
Raises:
ValueError: If user does not exist.
"""
user = self._users.get(username)
if not user:
raise ValueError("User not found")
role = user.roles[0] if user.roles else "user"
return f"dev-token-{role}"
def build_auth_cookie(self, token: str, domain: str) -> dict[str, Any]:
"""Build access_token cookie for blog dev auth.
Args:
token: Dev authentication token.
domain: Cookie domain.
Returns:
Cookie dict compatible with Playwright.
"""
return {
"name": "access_token",
"value": token,
"domain": domain,
"path": "/",
"httpOnly": True,
"secure": False,
"sameSite": "Lax",
}
@pytest.fixture(scope="session")
def base_url() -> str:
"""Return the base URL for the blog application.
Returns:
Application base URL.
"""
return "http://127.0.0.1:8000"
@pytest.fixture(scope="session")
def pytfm_auth_provider() -> DevAuthProvider:
"""Return DevAuthProvider for blog dev mode.
Returns:
DevAuthProvider instance.
"""
return DevAuthProvider()
def _create_authenticated_context(
browser: Browser,
base_url: str,
pytfm_auth_provider: DevAuthProvider,
role: str,
) -> BrowserContext:
"""Create a browser context authenticated with a dev token role.
Args:
browser: Playwright Browser instance.
base_url: Application base URL.
pytfm_auth_provider: Dev auth provider.
role: Dev role (user, user2, admin, guest).
Returns:
Authenticated BrowserContext.
"""
user = pytfm_auth_provider.create_user(
username=f"e2e_{role}",
password="pass",
email=f"{role}@example.com",
roles=[role],
)
token = pytfm_auth_provider.login(user.username, user.password)
context = browser.new_context(
viewport={"width": 1280, "height": 720},
)
cookie_domain = base_url.replace("http://", "").replace("https://", "").split(":")[0]
cookie = pytfm_auth_provider.build_auth_cookie(token, cookie_domain)
context.add_cookies([cookie])
return context
@pytest.fixture
def user_context(
browser: Browser,
base_url: str,
pytfm_auth_provider: DevAuthProvider,
) -> Generator[BrowserContext, None, None]:
"""Create a browser context authenticated as a regular user.
Args:
browser: Playwright Browser instance.
base_url: Application base URL.
pytfm_auth_provider: Dev auth provider.
Yields:
Authenticated BrowserContext for user role.
"""
context = _create_authenticated_context(browser, base_url, pytfm_auth_provider, "user")
yield context
context.close()
@pytest.fixture
def user_page(user_context: BrowserContext) -> Generator[Page, None, None]:
"""Create a page authenticated as a regular user.
Args:
user_context: Authenticated browser context.
Yields:
Authenticated Playwright Page.
"""
page = user_context.new_page()
yield page
page.close()
@pytest.fixture
def admin_context(
browser: Browser,
base_url: str,
pytfm_auth_provider: DevAuthProvider,
) -> Generator[BrowserContext, None, None]:
"""Create a browser context authenticated as admin.
Args:
browser: Playwright Browser instance.
base_url: Application base URL.
pytfm_auth_provider: Dev auth provider.
Yields:
Authenticated BrowserContext for admin role.
"""
context = _create_authenticated_context(browser, base_url, pytfm_auth_provider, "admin")
yield context
context.close()
@pytest.fixture
def admin_page(admin_context: BrowserContext) -> Generator[Page, None, None]:
"""Create a page authenticated as admin.
Args:
admin_context: Authenticated browser context.
Yields:
Authenticated Playwright Page.
"""
page = admin_context.new_page()
yield page
page.close()
@pytest.fixture
def user2_context(
browser: Browser,
base_url: str,
pytfm_auth_provider: DevAuthProvider,
) -> Generator[BrowserContext, None, None]:
"""Create a browser context authenticated as a second regular user.
Args:
browser: Playwright Browser instance.
base_url: Application base URL.
pytfm_auth_provider: Dev auth provider.
Yields:
Authenticated BrowserContext for user2 role.
"""
context = _create_authenticated_context(browser, base_url, pytfm_auth_provider, "user2")
yield context
context.close()
@pytest.fixture
def user2_page(user2_context: BrowserContext) -> Generator[Page, None, None]:
"""Create a page authenticated as a second regular user.
Args:
user2_context: Authenticated browser context.
Yields:
Authenticated Playwright Page.
"""
page = user2_context.new_page()
yield page
page.close()
@pytest.fixture
def guest_context(
browser: Browser,
base_url: str,
) -> Generator[BrowserContext, None, None]:
"""Create an unauthenticated browser context.
Args:
browser: Playwright Browser instance.
base_url: Application base URL.
Yields:
Unauthenticated BrowserContext.
"""
context = browser.new_context(
viewport={"width": 1280, "height": 720},
)
yield context
context.close()
@pytest.fixture
def guest_page(guest_context: BrowserContext) -> Generator[Page, None, None]:
"""Create an unauthenticated page.
Args:
guest_context: Unauthenticated browser context.
Yields:
Unauthenticated Playwright Page.
"""
page = guest_context.new_page()
yield page
page.close()