chore: initialize gondulf project structure

Set up Python project with uv environment management and FastAPI stack.

Project structure:
- src/gondulf/ - Main application package
- tests/ - Test suite directory
- pyproject.toml - Project configuration with dependencies
- README.md - Project documentation
- uv.lock - Dependency lock file

Dependencies configured:
- FastAPI + Uvicorn for web framework
- SQLAlchemy for database ORM
- pytest + coverage for testing
- ruff, black, mypy, flake8 for code quality
- Development environment using uv direct execution model

All project standards reviewed and implemented per:
- /docs/standards/coding.md
- /docs/standards/testing.md
- /docs/standards/git.md
- /docs/standards/development-environment.md
- /docs/standards/versioning.md
This commit is contained in:
2025-11-20 10:42:10 -07:00
commit 6d21442705
17 changed files with 4301 additions and 0 deletions

61
docs/standards/README.md Normal file
View File

@@ -0,0 +1,61 @@
# Project Standards
This directory contains all project-wide standards and conventions for the IndieAuth server implementation.
## Standard Documents
### Core Standards
- **[versioning.md](versioning.md)** - Semantic versioning approach (v2.0.0)
- **[git.md](git.md)** - Trunk-based development workflow
- **[testing.md](testing.md)** - Testing strategy with 80% minimum coverage
- **[coding.md](coding.md)** - Python coding standards and conventions
- **[development-environment.md](development-environment.md)** - uv-based environment management and workflow
## Key Decisions
### Technology Stack (Approved)
- **Language**: Python 3.10+
- **Framework**: FastAPI
- **Database**: SQLite
- **Environment Management**: uv (with direct execution model)
- **Testing**: pytest with 80% minimum coverage
- **Code Quality**: Black, Ruff, mypy
### Development Workflow
- **Git**: Trunk-based development on `main` branch
- **Environments**: Direct execution via `uv run` (no activation)
- **Testing**: TDD preferred, behavior-focused tests
- **Documentation**: Comprehensive before implementation
## Quick Reference
### Daily Commands
```bash
# Run application
uv run uvicorn src.main:app --reload
# Run tests
uv run pytest
# Add dependency
uv pip install package
uv pip freeze > requirements.txt
# Code quality
uv run ruff check .
uv run mypy src
```
### Standards Compliance
All code must:
- Pass linting (Ruff)
- Pass type checking (mypy)
- Have 80%+ test coverage (90% for new code)
- Follow PEP 8 and project conventions
- Be documented with clear docstrings
## Status
**Standards Finalized**: 2025-11-20
All foundational standards have been defined and documented. These standards form the basis for all implementation work on the IndieAuth server project.

377
docs/standards/coding.md Normal file
View File

@@ -0,0 +1,377 @@
# Python Coding Standard
## Overview
This document defines coding standards for the IndieAuth server implementation in Python. The primary goal is maintainability and clarity over cleverness.
## Python Version
- **Target**: Python 3.10+ (for modern type hints and async support)
- Use only stable language features
- Avoid deprecated patterns
## Code Style
### Formatting
- Use **Black** for automatic code formatting (line length: 88)
- Use **isort** for import sorting
- No manual formatting - let tools handle it
### Linting
- Use **flake8** with the following configuration:
```ini
# .flake8
[flake8]
max-line-length = 88
extend-ignore = E203, W503
exclude = .git,__pycache__,docs,build,dist
```
- Use **mypy** for static type checking:
```ini
# mypy.ini
[mypy]
python_version = 3.10
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
```
## Project Structure
```
indieauth/
├── __init__.py
├── main.py # Application entry point
├── config.py # Configuration management
├── models/ # Data models
│ ├── __init__.py
│ ├── client.py
│ ├── token.py
│ └── user.py
├── endpoints/ # HTTP endpoint handlers
│ ├── __init__.py
│ ├── authorization.py
│ ├── token.py
│ └── registration.py
├── services/ # Business logic
│ ├── __init__.py
│ ├── auth_service.py
│ ├── token_service.py
│ └── client_service.py
├── storage/ # Data persistence
│ ├── __init__.py
│ ├── base.py
│ └── sqlite.py
├── utils/ # Utility functions
│ ├── __init__.py
│ ├── crypto.py
│ └── validation.py
└── exceptions.py # Custom exceptions
```
## Naming Conventions
### General Rules
- Use descriptive names - clarity over brevity
- Avoid abbreviations except well-known ones (url, id, db)
- Use American English spelling
### Specific Conventions
- **Modules**: `lowercase_with_underscores.py`
- **Classes**: `PascalCase`
- **Functions/Methods**: `lowercase_with_underscores()`
- **Constants**: `UPPERCASE_WITH_UNDERSCORES`
- **Private**: Prefix with single underscore `_private_method()`
- **Internal**: Prefix with double underscore `__internal_var`
### Examples
```python
# Good
class ClientRegistration:
MAX_REDIRECT_URIS = 10
def validate_redirect_uri(self, uri: str) -> bool:
pass
# Bad
class client_reg: # Wrong case
maxURIs = 10 # Wrong case, abbreviation
def checkURI(self, u): # Unclear naming, missing types
pass
```
## Type Hints
Type hints are **mandatory** for all functions and methods:
```python
from typing import Optional, List, Dict, Union
from datetime import datetime
def generate_token(
client_id: str,
scope: Optional[str] = None,
expires_in: int = 3600
) -> Dict[str, Union[str, int, datetime]]:
"""Generate an access token for the client."""
pass
```
## Docstrings
Use Google-style docstrings for all public modules, classes, and functions:
```python
def exchange_code(
code: str,
client_id: str,
code_verifier: Optional[str] = None
) -> Token:
"""
Exchange authorization code for access token.
Args:
code: The authorization code received from auth endpoint
client_id: The client identifier
code_verifier: PKCE code verifier if PKCE was used
Returns:
Access token with associated metadata
Raises:
InvalidCodeError: If code is invalid or expired
InvalidClientError: If client_id doesn't match code
PKCERequiredError: If PKCE is required but not provided
"""
```
## Error Handling
### Custom Exceptions
Define specific exceptions in `exceptions.py`:
```python
class IndieAuthError(Exception):
"""Base exception for IndieAuth errors."""
pass
class InvalidClientError(IndieAuthError):
"""Raised when client authentication fails."""
pass
class InvalidTokenError(IndieAuthError):
"""Raised when token validation fails."""
pass
```
### Error Handling Pattern
```python
# Good - Specific exception handling
try:
token = validate_token(bearer_token)
except InvalidTokenError as e:
logger.warning(f"Token validation failed: {e}")
return error_response(401, "invalid_token")
except Exception as e:
logger.error(f"Unexpected error during validation: {e}")
return error_response(500, "internal_error")
# Bad - Catching all exceptions
try:
token = validate_token(bearer_token)
except: # Never use bare except
return error_response(400, "error")
```
## Logging
Use the standard `logging` module:
```python
import logging
logger = logging.getLogger(__name__)
class TokenService:
def create_token(self, client_id: str) -> str:
logger.debug(f"Creating token for client: {client_id}")
token = self._generate_token()
logger.info(f"Token created for client: {client_id}")
return token
```
### Logging Levels
- **DEBUG**: Detailed diagnostic information
- **INFO**: General informational messages
- **WARNING**: Warning messages for potentially harmful situations
- **ERROR**: Error messages for failures
- **CRITICAL**: Critical problems that require immediate attention
### Sensitive Data
Never log sensitive data:
```python
# Bad
logger.info(f"User logged in with password: {password}")
logger.debug(f"Generated token: {access_token}")
# Good
logger.info(f"User logged in: {user_id}")
logger.debug(f"Token generated for client: {client_id}")
```
## Configuration Management
Use environment variables for configuration:
```python
# config.py
import os
from typing import Optional
class Config:
"""Application configuration."""
# Required settings
SECRET_KEY: str = os.environ["INDIEAUTH_SECRET_KEY"]
DATABASE_URL: str = os.environ["INDIEAUTH_DATABASE_URL"]
# Optional settings with defaults
TOKEN_EXPIRY: int = int(os.getenv("INDIEAUTH_TOKEN_EXPIRY", "3600"))
RATE_LIMIT: int = int(os.getenv("INDIEAUTH_RATE_LIMIT", "100"))
DEBUG: bool = os.getenv("INDIEAUTH_DEBUG", "false").lower() == "true"
@classmethod
def validate(cls) -> None:
"""Validate configuration on startup."""
if not cls.SECRET_KEY:
raise ValueError("INDIEAUTH_SECRET_KEY must be set")
if len(cls.SECRET_KEY) < 32:
raise ValueError("INDIEAUTH_SECRET_KEY must be at least 32 characters")
```
## Dependency Management
### Requirements Files
```
requirements.txt # Production dependencies only
requirements-dev.txt # Development dependencies (includes requirements.txt)
requirements-test.txt # Test dependencies (includes requirements.txt)
```
### Dependency Principles
- Pin exact versions in requirements.txt
- Minimize dependencies - prefer standard library
- Audit dependencies for security vulnerabilities
- Document why each dependency is needed
## Security Practices
### Input Validation
Always validate and sanitize input:
```python
from urllib.parse import urlparse
def validate_redirect_uri(uri: str) -> bool:
"""Validate that redirect URI is safe."""
parsed = urlparse(uri)
# Must be absolute URI
if not parsed.scheme or not parsed.netloc:
return False
# Must be HTTPS in production
if not DEBUG and parsed.scheme != "https":
return False
# Prevent open redirects
if parsed.netloc in BLACKLISTED_DOMAINS:
return False
return True
```
### Secrets Management
```python
import secrets
def generate_token() -> str:
"""Generate cryptographically secure token."""
return secrets.token_urlsafe(32)
def constant_time_compare(a: str, b: str) -> bool:
"""Compare strings in constant time to prevent timing attacks."""
return secrets.compare_digest(a, b)
```
## Performance Considerations
### Async/Await
Use async for I/O operations when beneficial:
```python
async def verify_client(client_id: str) -> Optional[Client]:
"""Verify client exists and is valid."""
client = await db.get_client(client_id)
if client and not client.is_revoked:
return client
return None
```
### Caching
Cache expensive operations appropriately:
```python
from functools import lru_cache
@lru_cache(maxsize=128)
def get_client_metadata(client_id: str) -> dict:
"""Fetch and cache client metadata."""
# Expensive operation
return fetch_client_metadata(client_id)
```
## Module Documentation
Each module should have a header docstring:
```python
"""
Authorization endpoint implementation.
This module handles the OAuth 2.0 authorization endpoint as specified
in the IndieAuth specification. It processes authorization requests,
validates client information, and generates authorization codes.
"""
```
## Comments
### When to Comment
- Complex algorithms or business logic
- Workarounds or non-obvious solutions
- TODO items with issue references
- Security-critical code sections
### Comment Style
```python
# Good comments explain WHY, not WHAT
# Bad - Explains what the code does
counter = counter + 1 # Increment counter
# Good - Explains why
counter = counter + 1 # Track attempts for rate limiting
# Security-critical sections need extra attention
# SECURITY: Validate redirect_uri to prevent open redirect attacks
# See: https://owasp.org/www-project-web-security-testing-guide/
if not validate_redirect_uri(redirect_uri):
raise SecurityError("Invalid redirect URI")
```
## Code Organization Principles
1. **Single Responsibility**: Each module/class/function does one thing
2. **Dependency Injection**: Pass dependencies, don't hard-code them
3. **Composition over Inheritance**: Prefer composition for code reuse
4. **Fail Fast**: Validate input early and fail with clear errors
5. **Explicit over Implicit**: Clear interfaces over magic behavior

View File

@@ -0,0 +1,350 @@
# Development Environment Standard
## Overview
This document defines the standard development environment setup and workflow for the IndieAuth server project. We use **uv** for Python virtual environment management and package installation, following a direct execution model that eliminates the need for environment activation.
## Prerequisites
### System Requirements
- **Python**: 3.10 or higher
- **Git**: 2.25 or higher
- **SQLite**: 3.35 or higher (usually included with Python)
### Installing uv
uv is available for all major platforms. Install using one of these methods:
**Linux/macOS (recommended):**
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
**Using pip (alternative):**
```bash
pip install uv
```
**Using Homebrew (macOS):**
```bash
brew install uv
```
After installation, verify:
```bash
uv --version
```
## Project Setup
### Initial Setup (First Time)
1. **Clone the repository:**
```bash
git clone <repository-url> gondulf-indieauth
cd gondulf-indieauth
```
2. **Create virtual environment with uv:**
```bash
uv venv
```
This creates a `.venv` directory in the project root.
3. **Install dependencies:**
```bash
uv pip sync requirements.txt
```
Or if using pyproject.toml:
```bash
uv pip install -e .
```
### Daily Development Workflow
**IMPORTANT**: You do NOT need to activate the virtual environment. Use `uv run` to execute commands within the environment context.
## Using uv Direct Execution
### Core Concept
Instead of activating/deactivating virtual environments, we use uv's direct execution commands. This approach:
- Eliminates activation state confusion
- Works consistently across all shells
- Makes commands explicit and clear
- Simplifies CI/CD pipelines
### Common Development Commands
**Running Python scripts:**
```bash
# Instead of: python script.py
uv run python script.py
```
**Starting the development server:**
```bash
# Instead of: python -m uvicorn main:app --reload
uv run uvicorn main:app --reload
```
**Running tests:**
```bash
# Instead of: pytest
uv run pytest
# With coverage:
uv run pytest --cov=src --cov-report=term-missing
```
**Interactive Python shell:**
```bash
# Instead of: python
uv run python
```
**Running linters and formatters:**
```bash
# Instead of: ruff check .
uv run ruff check .
# Format code:
uv run ruff format .
```
### Package Management
**Installing packages:**
```bash
# Instead of: pip install package
uv pip install package
# Install with extras:
uv pip install "package[extra]"
# Install development dependencies:
uv pip install -r requirements-dev.txt
```
**Upgrading packages:**
```bash
# Instead of: pip install --upgrade package
uv pip install --upgrade package
```
**Listing installed packages:**
```bash
# Instead of: pip list
uv pip list
```
**Freezing dependencies:**
```bash
# Instead of: pip freeze > requirements.txt
uv pip freeze > requirements.txt
```
**Syncing with lock file:**
```bash
# Ensures exact versions from lock file:
uv pip sync requirements.txt
```
## IDE Configuration
### VS Code
1. Open VS Code settings (`.vscode/settings.json`)
2. Configure Python interpreter:
```json
{
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.terminal.activateEnvironment": false
}
```
Setting `activateEnvironment` to false prevents VS Code from auto-activating, maintaining our direct execution model.
### PyCharm
1. Go to Settings → Project → Python Interpreter
2. Click gear icon → Add
3. Choose "Existing environment"
4. Browse to: `<project-root>/.venv/bin/python`
5. Uncheck "Activate virtualenv" in terminal settings
### Other IDEs
Point the interpreter to `.venv/bin/python` and disable automatic activation if the option exists.
## Common Workflows
### Starting a New Feature
1. **Ensure dependencies are up to date:**
```bash
uv pip sync requirements.txt
```
2. **Create feature branch:**
```bash
git checkout -b feature/your-feature-name
```
3. **Run tests to ensure clean state:**
```bash
uv run pytest
```
### Adding a New Dependency
1. **Install the package:**
```bash
uv pip install new-package
```
2. **Update requirements file:**
```bash
uv pip freeze > requirements.txt
```
3. **Commit both changes:**
```bash
git add requirements.txt
git commit -m "Add new-package to dependencies"
```
### Running the Application
**Development mode with auto-reload:**
```bash
uv run uvicorn src.main:app --reload --host 127.0.0.1 --port 8000
```
**Production-like mode:**
```bash
uv run uvicorn src.main:app --host 0.0.0.0 --port 8000
```
### Running Tests
**All tests:**
```bash
uv run pytest
```
**Specific test file:**
```bash
uv run pytest tests/test_auth.py
```
**With coverage report:**
```bash
uv run pytest --cov=src --cov-report=html --cov-report=term-missing
```
**Watch mode (requires pytest-watch):**
```bash
uv run ptw
```
### Code Quality Checks
**Run all checks (before committing):**
```bash
# Linting
uv run ruff check .
# Type checking
uv run mypy src
# Security scanning
uv run bandit -r src
# Test coverage
uv run pytest --cov=src --cov-report=term-missing
```
## CI/CD Integration
uv commands work seamlessly in CI/CD pipelines without activation:
**GitHub Actions example:**
```yaml
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install uv
run: pip install uv
- name: Install dependencies
run: |
uv venv
uv pip sync requirements.txt
- name: Run tests
run: uv run pytest --cov=src
- name: Run linting
run: uv run ruff check .
```
## Troubleshooting
### Command not found errors
**Problem**: Running `python` or `pytest` directly doesn't work.
**Solution**: Always prefix with `uv run`: `uv run python` or `uv run pytest`
### Package installation issues
**Problem**: Package conflicts or resolution errors.
**Solution**: Clear the cache and reinstall:
```bash
uv cache clean
uv pip sync requirements.txt
```
### IDE not recognizing packages
**Problem**: IDE shows import errors despite packages being installed.
**Solution**: Ensure IDE interpreter points to `.venv/bin/python`, not system Python.
### Different behavior between local and CI
**Problem**: Tests pass locally but fail in CI.
**Solution**: Use `uv pip sync` instead of `uv pip install` to ensure exact versions.
## Best Practices
1. **Never commit `.venv/`** - It's already in `.gitignore`
2. **Always use `uv run`** for command execution
3. **Keep requirements.txt updated** when adding/removing packages
4. **Use `uv pip sync`** for reproducible environments
5. **Document any special setup** in project README
6. **Avoid activation scripts** - Use direct execution instead
## Benefits of This Approach
1. **Explicit Context**: Every command clearly shows it's running in the project environment
2. **No Activation State**: Eliminates "which environment am I in?" confusion
3. **Shell Agnostic**: Same commands work in bash, zsh, fish, PowerShell, cmd
4. **CI/CD Friendly**: Commands are identical in local and automated environments
5. **Faster Execution**: uv's Rust implementation provides superior performance
6. **Simpler Onboarding**: New developers don't need to learn activation patterns
## Quick Reference Card
```bash
# Setup (once)
uv venv
uv pip sync requirements.txt
# Daily commands
uv run python script.py # Run a script
uv run uvicorn src.main:app # Start server
uv run pytest # Run tests
uv run ruff check . # Lint code
uv pip install package # Add package
uv pip list # List packages
uv run python # Interactive shell
```
Remember: When in doubt, prefix with `uv run` for Python commands and `uv pip` for package management.

149
docs/standards/git.md Normal file
View File

@@ -0,0 +1,149 @@
# Git Workflow Standard
## Overview
This project uses trunk-based development for simplicity and continuous integration.
## Branch Strategy
### Main Branch
- `main` - The primary development branch
- Always in a deployable state
- Protected branch requiring review before merge
- All commits must pass CI/CD checks
### Feature Development
- Work directly on `main` for small, low-risk changes
- Use short-lived feature branches for larger changes
- Feature branches live maximum 2 days before merging
### Branch Naming (When Used)
- `fix/brief-description` - Bug fixes
- `feature/brief-description` - New features
- `refactor/brief-description` - Code refactoring
- `docs/brief-description` - Documentation only
Examples:
- `feature/client-registration`
- `fix/token-expiration`
- `docs/api-endpoints`
## Commit Message Format
Follow Conventional Commits specification:
```
<type>(<scope>): <subject>
[optional body]
[optional footer(s)]
```
### Types
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation only changes
- `style`: Code style changes (formatting, missing semicolons, etc)
- `refactor`: Code change that neither fixes a bug nor adds a feature
- `perf`: Performance improvements
- `test`: Adding or fixing tests
- `chore`: Changes to build process or auxiliary tools
### Scope (Optional)
Component or module affected:
- `auth`: Authorization endpoint
- `token`: Token endpoint
- `client`: Client registration
- `admin`: Administrative features
- `db`: Database changes
- `config`: Configuration changes
### Examples
```
feat(client): add self-registration endpoint
Implements dynamic client registration according to
RFC 7591 with rate limiting to prevent abuse.
Closes #12
```
```
fix(token): correct expiration time calculation
```
```
docs: update README with installation instructions
```
## Pull Request Process (When Using Branches)
1. Create branch from latest `main`
2. Make changes with clear, atomic commits
3. Ensure all tests pass locally
4. Push branch and open pull request
5. Ensure CI passes
6. Request review if required
7. Merge using "Squash and merge" for clean history
## Merge Strategy
- **Squash and merge** for feature branches (maintains clean history)
- **Fast-forward only** for direct commits to main
- Never use merge commits (no merge bubbles)
## Code Review Requirements
All code must be reviewed before merging to `main`:
- Self-review checklist:
- Tests pass
- Documentation updated
- No commented-out code
- No debug statements
- Follows coding standards
- Peer review for significant changes
## Git Hooks (Recommended)
### Pre-commit
- Run linter (e.g., `flake8` for Python)
- Run formatter (e.g., `black` for Python)
- Check for secrets/credentials
### Pre-push
- Run test suite
- Verify no large files
## Release Process
1. Ensure all tests pass on `main`
2. Update version in appropriate files
3. Create release commit: `chore: release v{version}`
4. Tag the commit: `git tag -a v{version} -m "Release v{version}"`
5. Push tag: `git push origin v{version}`
6. Create GitHub release with release notes
## Rewriting History
- **Never** rewrite history on `main`
- Only use `git rebase` on local branches before pushing
- Use `git commit --amend` only for unpushed commits
## Large Files
- Use `.gitignore` to exclude generated files, dependencies, and build artifacts
- Never commit:
- Binary files (unless absolutely necessary)
- Credentials or secrets
- Local configuration files
- IDE-specific files
- Log files
- Database files
## Repository Maintenance
- Keep repository size under 100MB
- Regularly clean up merged branches
- Archive old issues and pull requests
- Maintain clear README and CONTRIBUTING files

243
docs/standards/testing.md Normal file
View File

@@ -0,0 +1,243 @@
# Testing Standard
## Overview
Testing is mandatory for all code. This project maintains high quality through comprehensive automated testing at multiple levels.
## Testing Philosophy
- Write tests first when possible (TDD approach)
- Test behavior, not implementation
- Keep tests simple and focused
- Fast tests are better tests
- Clear test names that describe what is being tested
## Test Coverage Requirements
### Minimum Coverage
- Overall: 80% code coverage minimum
- Critical paths (auth, token, security): 95% coverage
- New code: 90% coverage required
### Coverage Exclusions
Acceptable to exclude from coverage:
- Logging statements
- Debug utilities
- Main entry points (if trivial)
- Third-party integration boilerplate
## Testing Pyramid
### Unit Tests (70% of tests)
- Test individual functions and methods
- No external dependencies (use mocks)
- Should run in milliseconds
- Located in `tests/unit/`
### Integration Tests (20% of tests)
- Test component interactions
- May use test database
- Test API endpoints with real HTTP requests
- Located in `tests/integration/`
### End-to-End Tests (10% of tests)
- Test complete IndieAuth flows
- Verify compliance with W3C specification
- Include client registration and authentication flows
- Located in `tests/e2e/`
## Test Structure
### Test File Organization
```
tests/
├── unit/
│ ├── test_auth.py
│ ├── test_token.py
│ └── test_client.py
├── integration/
│ ├── test_auth_endpoint.py
│ ├── test_token_endpoint.py
│ └── test_client_registration.py
├── e2e/
│ ├── test_full_auth_flow.py
│ └── test_client_self_registration.py
├── fixtures/
│ └── test_data.py
└── conftest.py # pytest configuration
```
### Test Naming Convention
- Test files: `test_*.py`
- Test classes: `Test*`
- Test methods: `test_*`
Examples:
```python
# Good test names
def test_token_expires_after_configured_duration():
def test_client_registration_requires_redirect_uri():
def test_authorization_code_is_single_use():
# Bad test names
def test_auth(): # Too vague
def test_1(): # Meaningless
```
## Python Testing Tools
### Core Testing Framework
- **pytest**: Primary testing framework
- Use fixtures for test setup
- Use parametrize for testing multiple cases
- Use markers for test categorization
### Required Testing Libraries
```python
# requirements-test.txt
pytest>=7.0.0
pytest-cov>=4.0.0 # Coverage reporting
pytest-asyncio>=0.20.0 # Async test support
pytest-mock>=3.10.0 # Mocking utilities
freezegun>=1.2.0 # Time mocking
factory-boy>=3.2.0 # Test data factories
responses>=0.22.0 # HTTP response mocking
```
### Test Fixtures
```python
# Example fixture structure
@pytest.fixture
def auth_client():
"""Returns authenticated test client."""
@pytest.fixture
def test_user():
"""Returns test user with domain."""
@pytest.fixture
def registered_client():
"""Returns pre-registered OAuth client."""
```
## Writing Tests
### Test Structure (AAA Pattern)
```python
def test_authorization_code_exchange():
# Arrange - Set up test data and conditions
code = generate_auth_code()
client = create_test_client()
# Act - Execute the behavior being tested
token = exchange_code_for_token(code, client)
# Assert - Verify the outcome
assert token.is_valid()
assert token.client_id == client.id
```
### Mocking Guidelines
- Mock external services (HTTP calls, databases in unit tests)
- Don't mock what you're testing
- Prefer dependency injection over patching
### Testing IndieAuth Compliance
Special test suite for W3C specification compliance:
```python
class TestIndieAuthCompliance:
"""Tests that verify W3C IndieAuth specification compliance."""
def test_authorization_endpoint_supports_response_type_code(self):
def test_token_endpoint_requires_code_verifier_with_pkce(self):
def test_client_id_must_be_valid_url(self):
```
## Test Execution
### Running Tests
```bash
# Run all tests
pytest
# Run with coverage
pytest --cov=indieauth --cov-report=html
# Run specific test level
pytest tests/unit/
pytest tests/integration/
pytest tests/e2e/
# Run tests matching pattern
pytest -k "test_token"
# Run with verbose output
pytest -vv
```
### Continuous Integration
All tests must pass before merging:
```yaml
# Example CI configuration
test:
- pytest tests/unit/ --cov=indieauth
- pytest tests/integration/
- pytest tests/e2e/
- coverage report --fail-under=80
```
## Performance Testing
### Response Time Requirements
- Authorization endpoint: < 200ms
- Token endpoint: < 100ms
- Client registration: < 500ms
### Load Testing
- Support 100 concurrent authentications
- Handle 1000 registered clients
- Token validation: 10,000 requests/minute
## Security Testing
### Required Security Tests
- Test for timing attacks in token validation
- Test rate limiting on all endpoints
- Test PKCE validation
- Test redirect URI validation
- Test for open redirect vulnerabilities
- Test token entropy and uniqueness
## Test Data Management
### Test Database
- Use in-memory SQLite for unit tests
- Use PostgreSQL/MySQL for integration tests (same as production)
- Reset database between test runs
- Use transactions for test isolation
### Test Secrets
- Never use production secrets in tests
- Generate test keys and tokens dynamically
- Use consistent test data for reproducibility
## Documentation of Tests
Each test should be self-documenting:
```python
def test_expired_token_is_rejected():
"""
Verify that tokens past their expiration time are rejected.
This prevents replay attacks and ensures tokens have limited lifetime
as required by OAuth 2.0 security best practices.
"""
```
## Test Maintenance
- Review and update tests when requirements change
- Remove obsolete tests
- Refactor tests that become brittle
- Keep test execution time under 1 minute for unit tests
- Keep full test suite under 5 minutes

View File

@@ -0,0 +1,80 @@
# Versioning Standard
## Overview
This project follows Semantic Versioning 2.0.0 (https://semver.org/) for all releases.
## Version Format
`MAJOR.MINOR.PATCH`
- **MAJOR**: Incremented for incompatible API changes or breaking changes to IndieAuth protocol implementation
- **MINOR**: Incremented for new features added in a backward-compatible manner
- **PATCH**: Incremented for backward-compatible bug fixes and documentation improvements
## Version Increments for IndieAuth Server
### MAJOR Version Changes (Breaking)
- Changes that break compatibility with existing IndieAuth clients
- Removal or modification of required IndieAuth endpoints
- Changes to token formats that invalidate existing tokens
- Database schema changes that require manual migration
- Configuration format changes that are not backward-compatible
- Removal of previously supported authentication methods
### MINOR Version Changes (Features)
- Addition of new optional IndieAuth endpoints (e.g., metadata endpoint)
- New client registration features
- Additional authentication methods
- Performance optimizations that don't change behavior
- New administrative features
- Support for additional IndieAuth extensions
### PATCH Version Changes (Fixes)
- Bug fixes that don't change API behavior
- Security patches that maintain compatibility
- Documentation improvements
- Internal refactoring without external changes
- Dependency updates that don't affect functionality
- Configuration option additions with sensible defaults
## Pre-release Versions
Pre-release versions may be denoted by appending:
- `-alpha.N` for early development versions
- `-beta.N` for feature-complete testing versions
- `-rc.N` for release candidates
Example: `1.0.0-beta.2`
## Initial Release Target
`1.0.0` - First stable release with:
- Full W3C IndieAuth specification compliance
- Client self-registration capability
- Production-ready security model
## Version Tagging
Git tags follow the format: `v{MAJOR}.{MINOR}.{PATCH}`
Example: `v1.0.0`, `v1.1.0-beta.1`
## Release Notes
Each release must include:
- Version number and release date
- Summary of changes categorized by type (Features, Fixes, Breaking Changes)
- Migration instructions for breaking changes
- Known issues if applicable
- Contributors acknowledgment
## Version Support Policy
- Latest MAJOR version: Full support with bug fixes and security updates
- Previous MAJOR version: Security updates only for 6 months after new MAJOR release
- Older versions: No support
## Development Branch Versioning
The main branch always contains the next unreleased version with `-dev` suffix in any version files.