- test_post_deletion.py: user delete own, admin delete any, 403 for other's - test_pagination.py: navigation across pages, boundary on last page - test_errors.py: 404 nonexistent post, 404 for other user's draft - test_post_lifecycle.py: draft-to-publish via edit flow - test_post_ownership.py: user can edit own post - test_profile_and_theme.py: profile page rendering, theme toggle with localStorage - fix(web): remove infinite pagination for USER role (routes.py) - fix(e2e): stabilize all publish() calls with expect_navigation - fix(e2e): add _unique_title() to avoid slug collisions at scale - docs: update FEATURE_POST_LIFECYCLE.md and TEST_MODEL.md coverage
176 lines
5.6 KiB
Python
176 lines
5.6 KiB
Python
"""End-to-end tests for post deletion via web UI.
|
|
|
|
Tests that users can delete their own posts, admins can delete any post,
|
|
and regular users cannot delete 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_user_can_delete_own_post(
|
|
user_page: Page,
|
|
base_url: str,
|
|
) -> None:
|
|
"""Test that a user can delete their own post.
|
|
|
|
Steps:
|
|
1. User creates and publishes a post.
|
|
2. User opens the post detail page.
|
|
3. User clicks delete and confirms.
|
|
4. Verify redirect to home page.
|
|
5. Verify the post no longer appears in the list.
|
|
|
|
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_delete()
|
|
|
|
with user_page.expect_navigation(wait_until="networkidle"):
|
|
detail.delete()
|
|
current_url = user_page.url
|
|
assert current_url.rstrip("/").endswith("/web")
|
|
|
|
response = user_page.request.get(f"{base_url}/web/posts/{slug}")
|
|
assert response.status == 404
|
|
|
|
|
|
@pytest.mark.e2e
|
|
def test_admin_can_delete_any_post(
|
|
user_page: Page,
|
|
admin_page: Page,
|
|
base_url: str,
|
|
) -> None:
|
|
"""Test that admin can delete a post created by another user.
|
|
|
|
Steps:
|
|
1. User creates and publishes a post.
|
|
2. Admin opens the post detail page.
|
|
3. Admin clicks delete and confirms.
|
|
4. Verify redirect to home page.
|
|
5. Verify the post no longer appears in the list.
|
|
|
|
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_delete()
|
|
|
|
with admin_page.expect_navigation(wait_until="networkidle"):
|
|
admin_detail.delete()
|
|
current_url = admin_page.url
|
|
assert current_url.rstrip("/").endswith("/web")
|
|
|
|
response = admin_page.request.get(f"{base_url}/web/posts/{slug}")
|
|
assert response.status == 404
|
|
|
|
|
|
@pytest.mark.e2e
|
|
def test_user_cannot_delete_other_users_post(
|
|
user_page: Page,
|
|
user2_page: Page,
|
|
base_url: str,
|
|
) -> None:
|
|
"""Test that a regular user cannot delete another user's post.
|
|
|
|
Steps:
|
|
1. User creates and publishes a post.
|
|
2. User2 opens the post detail page.
|
|
3. Verify the delete button is not visible.
|
|
4. User2 attempts a direct POST to the delete endpoint.
|
|
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_delete()
|
|
|
|
response = user2_page.request.post(f"{base_url}/web/posts/{slug}/delete")
|
|
assert response.status == 403
|