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