Files
blog.pyaqa.ru/tests/e2e/test_post_lifecycle.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

193 lines
6.5 KiB
Python

"""End-to-end tests for blog post lifecycle.
Tests the complete flow from post creation through publishing
and visibility verification across different user roles.
"""
from __future__ import annotations
import pytest
from playwright.sync_api import Page
from pytfm.generators import PostDataGenerator
from tests.e2e.pages import HomePage, PostDetailPage, PostFormPage
@pytest.mark.e2e
def test_user_creates_and_publishes_post_visible_to_guest_and_admin(
user_page: Page,
guest_page: Page,
admin_page: Page,
base_url: str,
) -> None:
"""Test positive scenario: user creates post, publishes it, and verifies visibility.
Steps:
1. Generate unique post data.
2. Authenticated user opens home, clicks "Write a Post".
3. Fills form and publishes the post.
4. Verifies redirect to detail page with "Published" status.
5. Verifies post appears on home page for the user.
6. Verifies post is visible to guest (unauthenticated).
7. Verifies post detail is accessible to guest.
8. Verifies post is visible to admin.
9. Verifies post detail is accessible to admin.
Args:
user_page: Playwright page authenticated as regular user.
guest_page: Unauthenticated Playwright page.
admin_page: Playwright page authenticated as admin.
base_url: Application base URL.
"""
generator = PostDataGenerator()
post_data = generator.generate_post()
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)
form.publish()
user_page.wait_for_url(
lambda url: "/web/posts/" in url and "new" not in url,
timeout=15000,
)
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.is_published()
home.open()
assert home.has_post_with_title(title)
guest_home = HomePage(guest_page, base_url)
guest_home.open()
assert guest_home.has_post_with_title(title)
guest_detail = PostDetailPage(guest_page, base_url, slug)
guest_detail.open()
assert guest_detail.get_title() == title
assert guest_detail.is_published()
admin_home = HomePage(admin_page, base_url)
admin_home.open()
assert admin_home.has_post_with_title(title)
admin_detail = PostDetailPage(admin_page, base_url, slug)
admin_detail.open()
assert admin_detail.get_title() == title
assert admin_detail.is_published()
@pytest.mark.e2e
def test_post_visibility_policies_across_users(
user_page: Page,
user2_page: Page,
guest_page: Page,
admin_page: Page,
base_url: str,
) -> None:
"""Test visibility policies: drafts vs published posts across roles.
Steps:
1. User creates a draft post.
2. User creates and publishes another post.
3. Verify user sees both posts on the home page.
4. Verify user2 sees only the published post.
5. Verify guest sees only the published post.
6. Verify admin sees both posts.
7. Verify user2 receives 404 when accessing the draft directly.
Args:
user_page: Playwright page authenticated as the first regular user.
user2_page: Playwright page authenticated as the second regular user.
guest_page: Unauthenticated Playwright page.
admin_page: Playwright page authenticated as admin.
base_url: Application base URL.
"""
generator = PostDataGenerator()
draft_data = generator.generate_post()
draft_title = str(draft_data["title"])
draft_content = str(draft_data["content"])
draft_tags = ", ".join(draft_data["tags"])
home = HomePage(user_page, base_url)
home.open()
home.create_post()
form = PostFormPage(user_page, base_url)
form.fill_form(draft_title, draft_content, draft_tags)
form.save_draft()
user_page.wait_for_url(
lambda url: "/web/posts/" in url and "new" not in url,
timeout=15000,
)
draft_url = user_page.url
assert "new" not in draft_url, f"Still on form page: {draft_url}"
draft_slug = draft_url.rstrip("/").split("/")[-1]
user_page.wait_for_selector('[data-testid="post-detail-title"]')
draft_detail = PostDetailPage(user_page, base_url, draft_slug)
assert draft_detail.get_title() == draft_title
assert not draft_detail.is_published()
published_data = generator.generate_post()
published_title = str(published_data["title"])
published_content = str(published_data["content"])
published_tags = ", ".join(published_data["tags"])
home.open()
home.create_post()
form = PostFormPage(user_page, base_url)
form.fill_form(published_title, published_content, published_tags)
form.publish()
user_page.wait_for_url(
lambda url: "/web/posts/" in url and "new" not in url,
timeout=15000,
)
published_url = user_page.url
assert "new" not in published_url, f"Still on form page: {published_url}"
published_slug = published_url.rstrip("/").split("/")[-1]
user_page.wait_for_selector('[data-testid="post-detail-title"]')
published_detail = PostDetailPage(user_page, base_url, published_slug)
assert published_detail.get_title() == published_title
assert published_detail.is_published()
home.open()
assert home.has_post_with_title(draft_title)
assert home.has_post_with_title(published_title)
user2_home = HomePage(user2_page, base_url)
user2_home.open()
assert user2_home.has_no_post_with_title(draft_title)
assert user2_home.has_post_with_title(published_title)
guest_home = HomePage(guest_page, base_url)
guest_home.open()
assert guest_home.has_no_post_with_title(draft_title)
assert guest_home.has_post_with_title(published_title)
admin_home = HomePage(admin_page, base_url)
admin_home.open()
assert admin_home.has_post_with_title(draft_title)
assert admin_home.has_post_with_title(published_title)
user2_page.goto(f"{base_url}/web/posts/{draft_slug}")
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 == "404"