"""End-to-end tests for post ownership and RBAC policies. Tests that admin can edit any post and that regular users cannot edit posts they do not own. """ from __future__ import annotations import uuid import pytest from playwright.sync_api import Page from pytfm.generators import PostDataGenerator from tests.e2e.pages import HomePage, PostDetailPage, PostFormPage def _unique_title(base: str) -> str: """Append a short UUID to a title to avoid slug collisions.""" return f"{base} {uuid.uuid4().hex[:8]}" @pytest.mark.e2e def test_admin_can_edit_any_post( user_page: Page, admin_page: Page, base_url: str, ) -> None: """Test that admin can edit a post created by another user. Steps: 1. User creates and publishes a post. 2. Admin opens the post detail page. 3. Admin clicks edit, changes the title, and saves. 4. Verify the post detail shows the updated title. Args: user_page: Playwright page authenticated as regular user. admin_page: Playwright page authenticated as admin. base_url: Application base URL. """ generator = PostDataGenerator() post_data = generator.generate_post() title = _unique_title(str(post_data["title"])) content = str(post_data["content"]) tags = ", ".join(post_data["tags"]) home = HomePage(user_page, base_url) home.open() home.create_post() form = PostFormPage(user_page, base_url) form.fill_form(title, content, tags) with user_page.expect_navigation(wait_until="networkidle"): form.publish() current_url = user_page.url assert "new" not in current_url, f"Still on form page: {current_url}" slug = current_url.rstrip("/").split("/")[-1] user_page.wait_for_selector('[data-testid="post-detail-title"]') detail = PostDetailPage(user_page, base_url, slug) assert detail.get_title() == title admin_detail = PostDetailPage(admin_page, base_url, slug) admin_detail.open() assert admin_detail.can_edit() admin_detail.edit() admin_page.wait_for_url( lambda url: f"/web/posts/{slug}/edit" in url, timeout=15000, ) new_data = generator.generate_post() new_title = _unique_title(str(new_data["title"])) new_content = str(new_data["content"]) new_tags = ", ".join(new_data["tags"]) admin_form = PostFormPage(admin_page, base_url) admin_form.fill_form(new_title, new_content, new_tags) with admin_page.expect_navigation(wait_until="networkidle"): admin_form.publish() admin_page.wait_for_selector( '[data-testid="post-detail-title"]', timeout=15000, ) updated_title = admin_page.locator('[data-testid="post-detail-title"]').text_content() assert updated_title == new_title updated_status = admin_page.locator('[data-testid="post-detail-status"]').text_content() assert updated_status == "Published" @pytest.mark.e2e def test_user_can_edit_own_post( user_page: Page, base_url: str, ) -> None: """Test that a user can edit their own post. Steps: 1. User creates and publishes a post. 2. User opens the post detail page and clicks edit. 3. User changes the title and saves. 4. Verify the post detail shows the updated title. Args: user_page: Playwright page authenticated as regular user. base_url: Application base URL. """ generator = PostDataGenerator() post_data = generator.generate_post() title = _unique_title(str(post_data["title"])) content = str(post_data["content"]) tags = ", ".join(post_data["tags"]) home = HomePage(user_page, base_url) home.open() home.create_post() form = PostFormPage(user_page, base_url) form.fill_form(title, content, tags) with user_page.expect_navigation(wait_until="networkidle"): form.publish() current_url = user_page.url assert "new" not in current_url, f"Still on form page: {current_url}" slug = current_url.rstrip("/").split("/")[-1] user_page.wait_for_selector('[data-testid="post-detail-title"]') detail = PostDetailPage(user_page, base_url, slug) assert detail.get_title() == title assert detail.can_edit() detail.edit() user_page.wait_for_url( lambda url: f"/web/posts/{slug}/edit" in url, timeout=15000, ) new_data = generator.generate_post() new_title = _unique_title(str(new_data["title"])) new_content = str(new_data["content"]) new_tags = ", ".join(new_data["tags"]) edit_form = PostFormPage(user_page, base_url) edit_form.fill_form(new_title, new_content, new_tags) with user_page.expect_navigation(wait_until="networkidle"): edit_form.publish() user_page.wait_for_selector( '[data-testid="post-detail-title"]', timeout=15000, ) updated_title = user_page.locator('[data-testid="post-detail-title"]').text_content() assert updated_title == new_title updated_status = user_page.locator('[data-testid="post-detail-status"]').text_content() assert updated_status == "Published" @pytest.mark.e2e def test_user_cannot_edit_other_users_post( user_page: Page, user2_page: Page, base_url: str, ) -> None: """Test that a regular user cannot edit another user's post. Steps: 1. User creates and publishes a post. 2. User2 opens the post detail page. 3. Verify the edit button is not visible. 4. User2 attempts direct access to the edit URL. 5. Verify a 403 error page is returned. Args: user_page: Playwright page authenticated as the first regular user. user2_page: Playwright page authenticated as the second regular user. base_url: Application base URL. """ generator = PostDataGenerator() post_data = generator.generate_post() title = _unique_title(str(post_data["title"])) content = str(post_data["content"]) tags = ", ".join(post_data["tags"]) home = HomePage(user_page, base_url) home.open() home.create_post() form = PostFormPage(user_page, base_url) form.fill_form(title, content, tags) with user_page.expect_navigation(wait_until="networkidle"): form.publish() current_url = user_page.url assert "new" not in current_url, f"Still on form page: {current_url}" slug = current_url.rstrip("/").split("/")[-1] user_page.wait_for_selector('[data-testid="post-detail-title"]') detail = PostDetailPage(user_page, base_url, slug) assert detail.get_title() == title user2_detail = PostDetailPage(user2_page, base_url, slug) user2_detail.open() assert not user2_detail.can_edit() user2_page.goto(f"{base_url}/web/posts/{slug}/edit") user2_page.wait_for_selector('[data-testid="error-code"]', timeout=10000) error_code = user2_page.locator('[data-testid="error-code"]').text_content() assert error_code == "403"