"""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()