Основные изменения: - Добавлены 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 — гайд по тестированию
300 lines
7.5 KiB
Python
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()
|