Files
Fusion 83f94b1f09 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
2026-05-19 14:18:20 +02:00

7.8 KiB

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

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:

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:

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:

@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:

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:

# 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

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:

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:

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

cd apps/web
npm install -D @playwright/test
npx playwright install

Example E2E Test:

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

Use factory-boy for test data:

# 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

@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)

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

# Run with debugger
pytest --pdb

# Run specific test with verbose
pytest -v -s test_file.py::test_name

# Show print statements
pytest -s

Frontend

# Debug mode
npm run test -- --reporter=verbose

# Show browser (for E2E)
npx playwright test --headed

Resources