docs: comprehensive documentation overhaul
Add complete documentation structure: - Frontend architecture documentation - Database schema documentation - Deployment guides (Docker, Traefik, Authentik, Environment) - Development guides (Setup, Testing, Contributing, Quality Gates) - Deployment architecture documentation - Updated docs README with complete navigation All new features and APIs are now documented. Quality gates: docs only, no code changes
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
# Testing Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter has comprehensive testing for both backend and frontend to ensure reliability and catch regressions.
|
||||
|
||||
## Backend Testing
|
||||
|
||||
### Test Framework
|
||||
|
||||
- **pytest**: Test runner
|
||||
- **pytest-asyncio**: Async test support
|
||||
- **httpx**: HTTP client for API tests
|
||||
- **factory-boy**: Test data generation (recommended)
|
||||
|
||||
### Test Structure
|
||||
|
||||
```
|
||||
apps/api/tests/
|
||||
├── conftest.py # Shared fixtures
|
||||
├── test_auth_api.py # Auth endpoint tests
|
||||
├── test_auth_services.py # Auth service tests
|
||||
├── test_models.py # Database model tests
|
||||
├── test_projects_api.py # Project endpoint tests
|
||||
├── test_git_repositories.py # Repository tests
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=src --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/test_auth_api.py
|
||||
|
||||
# Run specific test
|
||||
pytest tests/test_auth_api.py::test_login_redirect
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
|
||||
# Run async tests
|
||||
pytest --asyncio-mode=auto
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
**Unit Test Example**:
|
||||
```python
|
||||
import pytest
|
||||
from src.utils.git_url_parser import extract_base_repo_url
|
||||
|
||||
def test_extract_github_url():
|
||||
url = "https://github.com/user/repo/tree/main"
|
||||
result = extract_base_repo_url(url)
|
||||
assert result == "https://github.com/user/repo.git"
|
||||
|
||||
def test_valid_git_url_unchanged():
|
||||
url = "https://github.com/user/repo.git"
|
||||
result = extract_base_repo_url(url)
|
||||
assert result == url
|
||||
```
|
||||
|
||||
**Async Test Example**:
|
||||
```python
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_projects(client: AsyncClient):
|
||||
response = await client.get("/projects")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
```
|
||||
|
||||
**API Integration Test**:
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_project(client: AsyncClient, auth_headers):
|
||||
response = await client.post(
|
||||
"/projects",
|
||||
json={"name": "Test Project"},
|
||||
headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["name"] == "Test Project"
|
||||
assert "id" in data
|
||||
```
|
||||
|
||||
### Test Fixtures
|
||||
|
||||
**conftest.py** provides:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
@pytest.fixture
|
||||
async def client():
|
||||
from src.main import app
|
||||
async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
async def auth_headers(client):
|
||||
# Login and return auth headers
|
||||
response = await client.post("/auth/login")
|
||||
# ... setup session
|
||||
return {"Cookie": "session=..."}
|
||||
```
|
||||
|
||||
### Test Database
|
||||
|
||||
Tests use a separate database:
|
||||
|
||||
```bash
|
||||
# Test database URL (from .env)
|
||||
TEST_DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter_test
|
||||
|
||||
# Run tests with test DB
|
||||
TEST_DATABASE_URL=... pytest
|
||||
```
|
||||
|
||||
## Frontend Testing
|
||||
|
||||
### Test Framework
|
||||
|
||||
- **Vitest**: Test runner
|
||||
- **React Testing Library**: Component testing
|
||||
- **jsdom**: DOM environment
|
||||
|
||||
### Test Structure
|
||||
|
||||
```
|
||||
apps/web/src/
|
||||
├── components/
|
||||
│ └── protected-route.test.tsx
|
||||
├── pages/
|
||||
│ ├── dashboard.test.tsx
|
||||
│ └── projects.test.tsx
|
||||
└── test/
|
||||
└── setup.ts # Test setup
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
|
||||
# Run all tests
|
||||
npm run test
|
||||
|
||||
# Run in watch mode
|
||||
npm run test -- --watch
|
||||
|
||||
# Run with coverage
|
||||
npm run test -- --coverage
|
||||
|
||||
# Run specific file
|
||||
npm run test -- protected-route
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
**Component Test Example**:
|
||||
```typescript
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { FileTree } from '../components/file-tree';
|
||||
|
||||
describe('FileTree', () => {
|
||||
const mockFiles = [
|
||||
{ name: 'src', type: 'directory', path: 'src' },
|
||||
{ name: 'main.py', type: 'file', path: 'src/main.py' },
|
||||
];
|
||||
|
||||
it('renders file list', () => {
|
||||
render(<FileTree files={mockFiles} onFileClick={() => {}} />);
|
||||
|
||||
expect(screen.getByText('src')).toBeInTheDocument();
|
||||
expect(screen.getByText('main.py')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onFileClick when file clicked', () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<FileTree files={mockFiles} onFileClick={handleClick} />);
|
||||
|
||||
fireEvent.click(screen.getByText('main.py'));
|
||||
expect(handleClick).toHaveBeenCalledWith(mockFiles[1]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Async Test Example**:
|
||||
```typescript
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { ProjectsPage } from '../pages/projects';
|
||||
|
||||
describe('ProjectsPage', () => {
|
||||
it('loads and displays projects', async () => {
|
||||
render(<ProjectsPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('My Projects')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## E2E Testing (Future)
|
||||
|
||||
### Playwright Setup
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm install -D @playwright/test
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
**Example E2E Test**:
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('user can login', async ({ page }) => {
|
||||
await page.goto('http://localhost:5173');
|
||||
await page.click('text=Login');
|
||||
|
||||
// Authentik login
|
||||
await page.fill('[name="username"]', 'test@example.com');
|
||||
await page.fill('[name="password"]', 'password');
|
||||
await page.click('text=Sign In');
|
||||
|
||||
// Should redirect back to app
|
||||
await expect(page).toHaveURL('http://localhost:5173/dashboard');
|
||||
});
|
||||
```
|
||||
|
||||
## Test Data
|
||||
|
||||
### Factories (Recommended)
|
||||
|
||||
Use factory-boy for test data:
|
||||
|
||||
```python
|
||||
# tests/factories.py
|
||||
import factory
|
||||
from src.models.user import User
|
||||
|
||||
class UserFactory(factory.Factory):
|
||||
class Meta:
|
||||
model = User
|
||||
|
||||
email = factory.Faker('email')
|
||||
name = factory.Faker('name')
|
||||
authentik_id = factory.Faker('uuid4')
|
||||
```
|
||||
|
||||
### Fixtures
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
async def test_user(db_session):
|
||||
user = UserFactory()
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
return user
|
||||
```
|
||||
|
||||
## Coverage Goals
|
||||
|
||||
| Component | Target Coverage |
|
||||
|-----------|----------------|
|
||||
| Backend API | 80%+ |
|
||||
| Backend Services | 90%+ |
|
||||
| Backend Models | 90%+ |
|
||||
| Frontend Components | 70%+ |
|
||||
| Frontend Pages | 60%+ |
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
### GitHub Actions (Example)
|
||||
|
||||
```yaml
|
||||
name: Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_PASSWORD: headquarter
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: pip install -e ".[dev]"
|
||||
- run: pytest --cov=src --cov-report=xml
|
||||
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
- run: cd apps/web && npm ci
|
||||
- run: cd apps/web && npm run test
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Backend
|
||||
1. **Test isolation**: Each test should be independent
|
||||
2. **Use fixtures**: Don't repeat setup code
|
||||
3. **Mock external services**: Authentik, git operations
|
||||
4. **Test edge cases**: Empty lists, invalid inputs, errors
|
||||
5. **Async properly**: Use `pytest.mark.asyncio` and async fixtures
|
||||
|
||||
### Frontend
|
||||
1. **Test behavior, not implementation**: Check what user sees
|
||||
2. **Use data-testid**: For stable selectors
|
||||
3. **Mock API calls**: Don't hit real backend
|
||||
4. **Test accessibility**: Use `screen.getByRole`
|
||||
5. **Snapshot sparingly**: Only for complex UIs
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
### Backend
|
||||
```bash
|
||||
# Run with debugger
|
||||
pytest --pdb
|
||||
|
||||
# Run specific test with verbose
|
||||
pytest -v -s test_file.py::test_name
|
||||
|
||||
# Show print statements
|
||||
pytest -s
|
||||
```
|
||||
|
||||
### Frontend
|
||||
```bash
|
||||
# Debug mode
|
||||
npm run test -- --reporter=verbose
|
||||
|
||||
# Show browser (for E2E)
|
||||
npx playwright test --headed
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [pytest Documentation](https://docs.pytest.org/)
|
||||
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
|
||||
- [Vitest Documentation](https://vitest.dev/)
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
Reference in New Issue
Block a user