Files
blog.pyaqa.ru/AGENTS.md
Sergey Vanyushkin 4dede58d8f docs: добавлен TDD workflow в AGENTS.md и tests/AGENTS.md
- blog/AGENTS.md: раздел TDD Development Workflow с lifecycle фичи
- tests/AGENTS.md: правила TDD для тестов (RED/GREEN/REFACTOR, TC-ID формат)
- Описаны уровни тесткейсов: TC-UNIT, TC-API, TC-WEB, TC-E2E
- Добавлены правила коммита во все подпроекты
2026-05-07 21:18:52 +03:00

446 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Blog AGENTS.md
**Generated:** 2026-05-03 22:15 UTC
**Commit:** 41f2a3d
**Branch:** feature/tests
## Stack
- Python 3.13+, FastAPI, pydantic, uvicorn
- SQLAlchemy 2.0 (async), aiosqlite
- Package manager: `uv`
- CI: Woodpecker (lint, test, type on push/PR to `dev`)
## Commands
```bash
uv sync --group dev # Install all dev dependencies
uv run pytest # Run tests (coverage >= 70% required)
uv run pytest tests/unit/ # Run single test directory
uv run ruff check . --fix # Lint
uv run ruff format # Format
uv run isort . # Sort imports
uv run mypy . # Type check (strict mode)
uv run blog # Start dev server (port 8000)
```
## Pre-commit order
`ruff check --fix``ruff format``isort``mypy`
## DDD Architecture
### Layer Structure
```
app/
├── domain/ # Domain Layer - business logic, no dependencies
│ ├── entities/ # Domain entities (Post, User, etc.)
│ │ ├── base.py # Base entity class
│ │ └── post.py # Post entity with business logic
│ ├── value_objects/ # Value objects (Title, Content, Slug)
│ │ ├── base.py
│ │ ├── title.py
│ │ ├── content.py
│ │ └── slug.py
│ ├── repositories/ # Repository interfaces (abstract)
│ │ ├── base.py
│ │ └── post.py
│ └── exceptions.py # Domain exceptions
├── application/ # Application Layer - use cases
│ ├── dtos/ # Data Transfer Objects
│ │ └── post.py
│ ├── interfaces/ # Abstract interfaces (UoW)
│ │ └── unit_of_work.py
│ └── use_cases/ # Use cases (CQRS-like)
│ ├── create_post.py
│ ├── get_post.py
│ ├── update_post.py
│ ├── delete_post.py
│ ├── list_posts.py
│ └── publish_post.py
├── infrastructure/ # Infrastructure Layer - external concerns
│ ├── config/ # Configuration
│ │ └── settings.py
│ ├── database/ # Database connection & ORM models
│ │ ├── connection.py
│ │ └── models.py
│ ├── repositories/ # Repository implementations
│ │ ├── post.py # SQLAlchemyPostRepository
│ │ └── unit_of_work.py # SQLAlchemyUnitOfWork
│ ├── di/ # Dependency Injection
│ │ └── container.py
│ └── middleware/ # Exception handlers
│ └── error_handler.py
├── presentation/ # Presentation Layer - API
│ ├── api/ # FastAPI routes
│ │ ├── v1/ # API version 1
│ │ │ ├── __init__.py
│ │ │ └── posts.py # Posts endpoints
│ │ ├── deps.py # FastAPI dependencies
│ │ └── __init__.py
│ └── schemas/ # Pydantic schemas
│ └── post.py
└── main.py # Application entry point
tests/
├── unit/ # Unit tests (domain, use cases)
│ ├── domain/ # Domain layer tests
│ ├── application/ # Application layer tests
│ └── infrastructure/ # Infrastructure tests
├── integration/ # Integration tests (DB, repos)
├── api/ # API endpoint tests
└── e2e/ # End-to-end tests
```
## Where to Look
| Task | Location | Notes |
|------|----------|-------|
| Add a new use case | `app/application/use_cases/` | Follow naming: `{action}_post.py` |
| Add a new API endpoint | `app/presentation/api/v1/posts.py` | Or create new module in `v1/` |
| Add a new web page | `app/presentation/web/routes.py` | Integrate real use cases, not mocks |
| Add a domain entity | `app/domain/entities/` | Inherit from `BaseEntity`, add to `domain/__init__.py` |
| Add a repository method | `app/infrastructure/repositories/post.py` | Mirror in `app/domain/repositories/post.py` |
| Configure DI provider | `app/infrastructure/di/providers.py` | Add to existing provider class or create new one |
| Change database schema | `app/infrastructure/database/models.py` | Mirror changes in domain entity |
| Add/modify tests | `tests/unit/{layer}/` | Mirror `app/` structure exactly |
| Run linting | `uv run ruff check . --fix` | Pre-commit: ruff → ruff format → isort → mypy |
| Run tests | `uv run pytest` | Coverage auto-collected, HTML report at `htmlcov/` |
| Run type check | `uv run mypy .` | Strict mode; excludes `tests/e2e` |
## Code Map
| Symbol | Type | Location | Refs | Role |
|--------|------|----------|------|------|
| `app_factory` | Function | `app/main.py:50` | 3 | FastAPI app factory with DI lifespan |
| `SQLAlchemyPostRepository` | Class | `app/infrastructure/repositories/post.py:18` | 1 | Concrete repository implementation |
| `Post` | Class | `app/domain/entities/post.py:17` | 1 | Core domain entity |
| `PostRepository` | Class | `app/domain/repositories/post.py:13` | 1 | Repository interface |
| `CreatePostUseCase` | Class | `app/application/use_cases/create_post.py:14` | 1 | Use case for creating posts |
| `home` | Function | `app/presentation/web/routes.py:189` | 1 | Web home page route |
| `create_post` | Function | `app/presentation/api/v1/posts.py:35` | 1 | API create post endpoint |
## Key Conventions
### Dependency Rule
- Domain layer has **NO dependencies** on other layers
- Application layer depends only on Domain
- Infrastructure depends on Domain and Application
- Presentation depends on all other layers
### Testing
- **Unit tests**: Test domain logic without DB/external services
### Code Patterns
- Use **dataclasses** for entities and value objects
- Use **frozen dataclasses** for value objects (immutable)
- Use **Unit of Work** pattern for transactions
- Use **Repository** pattern for data access
- Use **Dependency Injection** via FastAPI's Depends()
## Anti-Patterns (This Project)
- **NO inline comments** — Self-documenting code only; Google-style docstrings required
- **NO type suppression** — Never use `typing.Any` casts or `# type: ignore` to bypass mypy strict mode
- **Dead code**: `create_container()` in `app/infrastructure/di/container.py` is defined but never used; `main.py` calls `make_async_container()` directly
- **Empty directories**: `app/domain/exceptions/` and `app/presentation/api/deps/` are empty dirs that co-exist with `.py` files of the same name — import ambiguity risk
- **Missing `__main__.py`**: `python -m app` fails; use `uv run blog` or `python app/main.py`
- **Stale config**: `pyproject.toml` excludes `tests/e2e` but the directory does not exist
- **Unused dependency**: `black` is in `[dependency-groups] lints` but never invoked; ruff format is used instead
- **Pre-commit excludes `__init__.py`**: All `__init__.py` files skip linting and import sorting
## AI Code Generation Requirements
### Documentation Standards
- **Write self-documenting code** - use clear, descriptive variable names and function names
- **NO inline comments** - code should be readable without explanatory comments
- **Google-style docstrings** are REQUIRED for all modules, classes, and functions
### Docstring Requirements
#### Modules
Every module must have a module-level docstring:
```python
"""Module for managing blog posts.
This module provides use cases for creating, updating, and deleting
blog posts in the application layer.
"""
```
#### Classes
Every class must have a detailed docstring:
```python
class CreatePostUseCase:
"""Use case for creating a new blog post.
This class encapsulates the business logic for creating posts,
including validation and slug generation.
Attributes:
uow: Unit of Work for database transactions.
slug_service: Service for generating URL-friendly slugs.
Example:
>>> use_case = CreatePostUseCase(uow, slug_service)
>>> result = await use_case.execute(dto)
"""
```
#### Functions/Methods
Every function must have a detailed docstring with Args, Returns, Raises:
```python
async def execute(self, dto: CreatePostDTO) -> PostDTO:
"""Execute the use case to create a new post.
Args:
dto: Data transfer object containing post creation data
including title, content, and author information.
Returns:
PostDTO: The created post data transfer object with
generated ID and slug.
Raises:
TitleValidationError: If the title is empty or too long.
ContentValidationError: If the content is empty.
DuplicateSlugError: If a post with the same slug exists.
Note:
This method is idempotent - calling it multiple times with
the same data will create separate posts with unique slugs.
"""
```
### Google-Style Docstring Format
Use the following sections as appropriate:
- `Args` - Parameter descriptions with types
- `Returns` - Return value description with type
- `Raises` - Exceptions that may be raised
- `Yields` - For generator functions
- `Example` - Usage examples
- `Note` - Additional important information
- `Warning` - Critical warnings
- `Attributes` - For class attributes
- `See Also` - References to related code
## UI Development Requirements
### HTML Templates (Jinja2)
- All HTML templates use **Jinja2** templating engine
- Templates are located in `app/presentation/templates/`
- Base template: `base.html` with theme support (light/dark)
### data-testid Attributes (REQUIRED)
**Every interactive and significant HTML element MUST have a `data-testid` attribute** for automated testing.
#### Required Elements:
- **Navigation**: `data-testid="nav-link-{name}"`, `data-testid="nav-logo"`
- **Buttons**: `data-testid="btn-{action}"` (e.g., `btn-create`, `btn-save`, `btn-delete`)
- **Forms**: `data-testid="form-{name}"`, `data-testid="input-{field}"`, `data-testid="submit-{action}"`
- **Cards/Posts**: `data-testid="post-card-{id}"`, `data-testid="post-title"`, `data-testid="post-content"`
- **Lists**: `data-testid="list-{name}"`, `data-testid="list-item-{index}"`
- **Theme Switcher**: `data-testid="theme-toggle"`, `data-testid="theme-{light|dark}"`
- **Messages/Alerts**: `data-testid="alert-{type}"`, `data-testid="alert-message"
#### Example:
```html
<button data-testid="btn-create-post" class="btn btn-primary">
Create Post
</button>
<article data-testid="post-card-{{ post.id }}" class="card">
<h2 data-testid="post-title">{{ post.title }}</h2>
<p data-testid="post-content">{{ post.content }}</p>
</article>
```
### CSS Architecture (Gitea-inspired)
- **Theme files**: `static/css/themes/theme-{light|dark}.css` with CSS variables
- **Base styles**: `static/css/base.css` - reset, typography, CSS variables usage
- **Components**: `static/css/components.css` - buttons, cards, forms, inputs
- **Layout**: `static/css/layout.css` - grid, navigation, containers
### Theme Support
- Light and dark themes based on Gitea color scheme
- Theme switching via `data-theme` attribute on `<html>` element
- LocalStorage persistence for user preference
- All colors use CSS custom properties (variables)
### Static Assets
- **All assets are local** - no external CDN dependencies
- Location: `static/` directory at project root
- Served via FastAPI `StaticFiles` middleware
## Authentication & Authorization
### Web UI Authentication
- **Token storage**: HTTP-only secure cookies
- **Login flow**: Redirect to Keycloak login page → Callback → Set cookie → Redirect back
- **Registration**: Only through Keycloak admin interface
- **Profile**: Read-only display of user info
### Authorization Rules
#### Post Visibility
| Role | Published Posts | Own Drafts | Other Drafts |
|------|----------------|------------|--------------|
| GUEST (unauthenticated) | ✅ | ❌ | ❌ |
| USER | ✅ | ✅ | ❌ |
| ADMIN | ✅ | ✅ | ✅ |
#### UI Elements by Role
| Element | GUEST | USER | ADMIN |
|---------|-------|------|-------|
| "New Post" button | ❌ | ✅ | ✅ |
| "Edit" button on own posts | ❌ | ✅ | ✅ |
| "Edit" button on other posts | ❌ | ❌ | ✅ |
| "Delete" button on own posts | ❌ | ✅ | ✅ |
| "Delete" button on other posts | ❌ | ❌ | ✅ |
| Draft badges | ❌ | Own only | All |
| User menu in header | ❌ | ✅ | ✅ |
| Profile page access | ❌ | ✅ | ✅ |
### Auth Routes
- `GET /auth/login` - Redirect to Keycloak
- `GET /auth/callback` - OAuth callback handler
- `GET /auth/logout` - Clear cookie and logout
- `GET /profile` - User profile page (read-only)
### Cookie Settings
```python
response.set_cookie(
key="access_token",
value=token,
httponly=True,
secure=True, # In production
samesite="lax",
max_age=3600, # 1 hour
)
```
### DDD Concepts Used
### Entities
- Have identity (UUID)
- Mutable state
- Business logic methods (publish, update_title, etc.)
- Example: `Post` entity
### Value Objects
- Immutable
- Defined by attributes
- Validated on creation
- Examples: `Title`, `Content`, `Slug`
### Aggregates & Repositories
- `Post` is an aggregate root
- `PostRepository` interface in Domain
- `SQLAlchemyPostRepository` implementation in Infrastructure
### Domain Events
- Placeholder for future implementation
- Can be added via event bus in application layer
## Configuration
- `.env` file loaded by pydantic-settings
- Settings available via `app.infrastructure.config.settings`
## Database
- SQLAlchemy 2.0 with async support
- SQLite by default (aiosqlite)
- Tables auto-created on startup
- Use `init_db()` and `close_db()` in lifespan
## TDD Development Workflow
This project uses **Test-Driven Development (TDD)** with a formal test agreement process.
### Feature Lifecycle
```
User: "начнем новую фичу"
|
v
Discovery Phase (автоматически)
|-- Анализ существующего кода
|-- Определение затронутых слоев DDD
|-- Рекомендации по тесткейсам
|
v
User Agreement (согласование)
|-- Пользователь подтверждает/корректирует тесткейсы
|
v
Test Design
|-- Актуализация FEATURE_*.md
|-- Создание artifact: pyaqa/feature/{feature-name}.md
|-- Назначение TC-UNIT-NNN, TC-API-NNN, TC-WEB-NNN, TC-E2E-NNN
|
v
Write Tests (RED)
|-- Написать тесты, убедиться что они падают
|
v
Implementation (GREEN)
|-- Domain -> Application -> Infrastructure -> Presentation
|-- Минимальная реализация для прохождения тестов
|
v
Refactor
|-- Улучшение кода с сохранением зеленых тестов
|-- Линтеры, type checker
|
v
Verification
|-- ruff check, ruff format, isort, mypy
|-- pytest (coverage ≥70%)
|-- E2E tests
|
v
User Acceptance
|-- Пользователь подтверждает приемку
|
v
Commit (во все затронутые проекты)
|-- blog, pytfm, pyaqa (root)
```
### Branch Naming
- Формат: `feature/{feature-name}`
- База: `dev`
### Test Case IDs
- `TC-UNIT-NNN` — unit тесты (domain, use cases)
- `TC-API-NNN` — API endpoint тесты
- `TC-WEB-NNN` — Web route тесты (HTML responses)
- `TC-E2E-NNN` — End-to-end тесты (Playwright)
### Test Level Selection
Все 4 уровня по умолчанию. Можно сокращать в зависимости от фичи:
- **Domain-only фича**: только TC-UNIT
- **API-only фича**: TC-UNIT + TC-API
- **Web UI фича**: TC-UNIT + TC-WEB + TC-E2E
- **Full-stack фича**: все 4 уровня
### Artifact Location
- **Шаблон**: `pyaqa/feature/TEMPLATE.md`
- **Артефакт фичи**: `pyaqa/feature/{feature-name}.md`
### Commit Rules
При приемке фичи коммитить во ВСЕ затронутые подпроекты:
1. `blog/` — если затронут
2. `pytfm/` — если затронут
3. `pyaqa/` (root) — всегда (обновление ссылок на подпроекты)
## Notes
- Web routes (`app/presentation/web/routes.py`) currently use `MockPost` and `MOCK_POSTS` instead of real use cases — integrate with actual use cases when ready
- `alembic/` directory exists but is non-functional (no `alembic.ini`, no migration scripts)
- `tests/integration/`, `tests/api/`, `tests/e2e/` are documented in architecture but do not exist yet
- `app/domain/roles.py` exists but its symbols are not exported in `app/domain/__init__.py`
- Woodpecker CI uses `.woodpecker/` directory (3 separate YAML files) instead of single `.woodpecker.yml` — valid but non-standard
- CI pipelines have copy-paste boilerplate; `test.yaml` uses `--group tests` while `lint.yaml` and `type.yaml` use `--only-group <X>`