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,261 @@
|
||||
# Contributing Guide
|
||||
|
||||
## Welcome
|
||||
|
||||
Thank you for your interest in contributing to Headquarter! This document provides guidelines and workflows for contributing.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository
|
||||
2. Clone your fork: `git clone https://github.com/your-username/headquarter.git`
|
||||
3. Set up development environment (see [Setup Guide](./setup.md))
|
||||
4. Create a branch: `git checkout -b feature/your-feature`
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Find or Create an Issue
|
||||
|
||||
- Check existing issues for something to work on
|
||||
- Create an issue to discuss new features before implementing
|
||||
- Comment on issues to claim them
|
||||
|
||||
### 2. Create a Branch
|
||||
|
||||
```bash
|
||||
# Feature branch
|
||||
git checkout -b feature/description
|
||||
|
||||
# Bug fix branch
|
||||
git checkout -b fix/description
|
||||
|
||||
# Documentation branch
|
||||
git checkout -b docs/description
|
||||
```
|
||||
|
||||
### 3. Make Changes
|
||||
|
||||
- Write clear, concise code
|
||||
- Follow existing patterns and conventions
|
||||
- Add tests for new functionality
|
||||
- Update documentation as needed
|
||||
|
||||
### 4. Run Quality Gates
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
cd apps/api
|
||||
ruff check src/ tests/
|
||||
mypy src/
|
||||
pytest
|
||||
|
||||
# Frontend
|
||||
cd apps/web
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
npm run test
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 5. Commit Changes
|
||||
|
||||
We use conventional commits:
|
||||
|
||||
```bash
|
||||
# Format: type(scope): description
|
||||
|
||||
# Examples:
|
||||
git commit -m "feat(auth): add OAuth2 login"
|
||||
git commit -m "fix(api): handle missing user gracefully"
|
||||
git commit -m "docs(readme): update installation instructions"
|
||||
git commit -m "test(git): add URL parsing tests"
|
||||
git commit -m "refactor(models): extract base repository"
|
||||
```
|
||||
|
||||
**Types**:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation
|
||||
- `style`: Formatting (no code change)
|
||||
- `refactor`: Code restructuring
|
||||
- `test`: Adding tests
|
||||
- `chore`: Maintenance tasks
|
||||
|
||||
### 6. Push and Create Pull Request
|
||||
|
||||
```bash
|
||||
git push origin feature/description
|
||||
```
|
||||
|
||||
**PR Description should include**:
|
||||
- What changed and why
|
||||
- How to test
|
||||
- Screenshots (for UI changes)
|
||||
- Link to related issue
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Python (Backend)
|
||||
|
||||
**Style**: Follow PEP 8 and project conventions
|
||||
|
||||
```python
|
||||
# Function naming: snake_case
|
||||
def get_user_by_id(user_id: str) -> User | None:
|
||||
pass
|
||||
|
||||
# Class naming: PascalCase
|
||||
class GitRepositoryService:
|
||||
pass
|
||||
|
||||
# Constants: UPPER_SNAKE_CASE
|
||||
MAX_FILE_SIZE = 1024 * 1024 # 1MB
|
||||
|
||||
# Type hints required
|
||||
def process_data(data: dict[str, Any]) -> ProcessedResult:
|
||||
pass
|
||||
```
|
||||
|
||||
**Docstrings**: Google style
|
||||
```python
|
||||
def extract_base_repo_url(url: str) -> str | None:
|
||||
"""Extract base repository URL from a browser URL.
|
||||
|
||||
Args:
|
||||
url: The URL to parse, may be a browser URL or git URL.
|
||||
|
||||
Returns:
|
||||
The base repository URL with .git suffix, or None if parsing fails.
|
||||
|
||||
Examples:
|
||||
>>> extract_base_repo_url("https://github.com/user/repo/tree/main")
|
||||
'https://github.com/user/repo.git'
|
||||
|
||||
>>> extract_base_repo_url("https://github.com/user/repo.git")
|
||||
'https://github.com/user/repo.git'
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
### TypeScript (Frontend)
|
||||
|
||||
**Style**: Follow existing patterns
|
||||
|
||||
```typescript
|
||||
// Interface naming: PascalCase
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// Function naming: camelCase
|
||||
function getUserById(userId: string): Promise<User> {
|
||||
return api.get(`/users/${userId}`);
|
||||
}
|
||||
|
||||
// Component naming: PascalCase
|
||||
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
|
||||
return <div>{user.name}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### New Features
|
||||
|
||||
- Unit tests for business logic
|
||||
- Integration tests for API endpoints
|
||||
- Component tests for UI components
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Regression test that would catch the bug
|
||||
- Verify fix with the test
|
||||
|
||||
### Example
|
||||
|
||||
```python
|
||||
# Backend test
|
||||
def test_extract_github_browser_url():
|
||||
url = "https://github.com/user/repo/tree/main"
|
||||
result = extract_base_repo_url(url)
|
||||
assert result == "https://github.com/user/repo.git"
|
||||
|
||||
# Frontend test
|
||||
test('shows file tree', () => {
|
||||
render(<FileTree files={mockFiles} onFileClick={() => {}} />);
|
||||
expect(screen.getByText('src')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Update documentation when:
|
||||
- Adding new features
|
||||
- Changing API endpoints
|
||||
- Modifying configuration
|
||||
- Adding environment variables
|
||||
|
||||
**Documentation locations**:
|
||||
- `README.md` - Project overview
|
||||
- `docs/features/` - Feature documentation
|
||||
- `docs/api/` - API documentation
|
||||
- `docs/deployment/` - Deployment guides
|
||||
|
||||
## Review Process
|
||||
|
||||
1. **Automated checks** must pass (CI/CD)
|
||||
2. **Code review** by at least one maintainer
|
||||
3. **Approval** required before merge
|
||||
4. **Squash merge** to keep history clean
|
||||
|
||||
### Review Checklist
|
||||
|
||||
**For Authors**:
|
||||
- [ ] Tests pass locally
|
||||
- [ ] Quality gates pass
|
||||
- [ ] Documentation updated
|
||||
- [ ] PR description is clear
|
||||
|
||||
**For Reviewers**:
|
||||
- [ ] Code makes sense
|
||||
- [ ] Tests cover changes
|
||||
- [ ] No security issues
|
||||
- [ ] Follows conventions
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Update version in `pyproject.toml` and `package.json`
|
||||
2. Update `CHANGELOG.md`
|
||||
3. Create git tag: `git tag v1.2.3`
|
||||
4. Push tag: `git push origin v1.2.3`
|
||||
5. Create GitHub release with notes
|
||||
|
||||
## Community
|
||||
|
||||
### Communication Channels
|
||||
|
||||
- GitHub Issues: Bug reports and feature requests
|
||||
- GitHub Discussions: Questions and ideas
|
||||
- Pull Requests: Code contributions
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
- Be respectful and inclusive
|
||||
- Welcome newcomers
|
||||
- Focus on constructive feedback
|
||||
- Respect different viewpoints
|
||||
|
||||
## Questions?
|
||||
|
||||
- Check existing documentation
|
||||
- Search closed issues
|
||||
- Ask in GitHub Discussions
|
||||
- Join community chat (if available)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Development Setup](./setup.md)
|
||||
- [Testing Guide](./testing.md)
|
||||
- [Quality Gates](./quality-gates.md)
|
||||
- [Project README](../../README.md)
|
||||
@@ -0,0 +1,365 @@
|
||||
# Quality Gates
|
||||
|
||||
## Overview
|
||||
|
||||
All code changes must pass quality gates before being merged. These gates ensure code consistency, type safety, and prevent common issues.
|
||||
|
||||
## Backend Quality Gates
|
||||
|
||||
### 1. Code Formatting (ruff)
|
||||
|
||||
**Purpose**: Enforce consistent code style
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
ruff check src/ tests/
|
||||
ruff format src/ tests/
|
||||
```
|
||||
|
||||
**Configuration** (`pyproject.toml`):
|
||||
```toml
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
select = ["E", "F", "I", "W", "UP"]
|
||||
ignore = ["E501"]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
**Pre-commit hook**:
|
||||
```bash
|
||||
# Install pre-commit
|
||||
pip install pre-commit
|
||||
pre-commit install
|
||||
|
||||
# Run manually
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
### 2. Type Checking (mypy)
|
||||
|
||||
**Purpose**: Catch type errors before runtime
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
mypy src/
|
||||
```
|
||||
|
||||
**Configuration** (`pyproject.toml`):
|
||||
```toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_ignores = true
|
||||
disallow_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
check_untyped_defs = true
|
||||
```
|
||||
|
||||
**Common issues**:
|
||||
- Missing type hints on function parameters
|
||||
- Returning wrong type
|
||||
- None checks needed
|
||||
|
||||
### 3. Unit Tests (pytest)
|
||||
|
||||
**Purpose**: Verify functionality works as expected
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
pytest -v
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- All tests must pass
|
||||
- New code should have tests
|
||||
- Coverage should not decrease
|
||||
|
||||
### 4. Security Checks
|
||||
|
||||
**Bandit** (security linter):
|
||||
```bash
|
||||
bandit -r src/
|
||||
```
|
||||
|
||||
**Safety** (dependency vulnerabilities):
|
||||
```bash
|
||||
safety check
|
||||
```
|
||||
|
||||
## Frontend Quality Gates
|
||||
|
||||
### 1. Type Checking (TypeScript)
|
||||
|
||||
**Purpose**: Catch type errors at build time
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
**Configuration** (`tsconfig.json`):
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Linting (ESLint)
|
||||
|
||||
**Purpose**: Enforce code style and catch issues
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run lint
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
**Configuration** (`.eslintrc.cjs`):
|
||||
```javascript
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Build Check
|
||||
|
||||
**Purpose**: Ensure production build succeeds
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run build
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- No TypeScript errors
|
||||
- No build warnings
|
||||
- Bundle size reasonable
|
||||
|
||||
### 4. Unit Tests (Vitest)
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run test
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- All tests pass
|
||||
- No test failures
|
||||
- Coverage maintained
|
||||
|
||||
## Running All Gates
|
||||
|
||||
### Backend
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Run all gates
|
||||
ruff check src/ tests/ && \
|
||||
mypy src/ && \
|
||||
pytest
|
||||
|
||||
# Or use Makefile
|
||||
make lint # ruff + mypy
|
||||
make test # pytest
|
||||
make check # All backend gates
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
|
||||
# Run all gates
|
||||
npm run lint && \
|
||||
npm run typecheck && \
|
||||
npm run test && \
|
||||
npm run build
|
||||
|
||||
# Or use package.json scripts
|
||||
npm run check # All frontend gates
|
||||
```
|
||||
|
||||
### Full Project
|
||||
|
||||
```bash
|
||||
# From root
|
||||
make check-all # Run all backend and frontend gates
|
||||
```
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
All quality gates run automatically on:
|
||||
- Every Pull Request
|
||||
- Every push to main branch
|
||||
|
||||
**CI Pipeline**:
|
||||
```yaml
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
|
||||
backend-lint:
|
||||
stage: lint
|
||||
script:
|
||||
- cd apps/api && ruff check src/ tests/
|
||||
- cd apps/api && mypy src/
|
||||
|
||||
backend-test:
|
||||
stage: test
|
||||
script:
|
||||
- cd apps/api && pytest -v
|
||||
|
||||
frontend-lint:
|
||||
stage: lint
|
||||
script:
|
||||
- cd apps/web && npm run lint
|
||||
- cd apps/web && npm run typecheck
|
||||
|
||||
frontend-test:
|
||||
stage: test
|
||||
script:
|
||||
- cd apps/web && npm run test
|
||||
|
||||
frontend-build:
|
||||
stage: build
|
||||
script:
|
||||
- cd apps/web && npm run build
|
||||
```
|
||||
|
||||
## Fixing Common Issues
|
||||
|
||||
### Backend
|
||||
|
||||
**ruff: Line too long**
|
||||
```python
|
||||
# Bad
|
||||
result = some_very_long_function_name(with_many_parameters, that_make_the_line_too_long)
|
||||
|
||||
# Good
|
||||
result = some_very_long_function_name(
|
||||
with_many_parameters,
|
||||
that_make_the_line_too_long,
|
||||
)
|
||||
```
|
||||
|
||||
**mypy: Missing return type**
|
||||
```python
|
||||
# Bad
|
||||
def get_user(user_id):
|
||||
return User.query.get(user_id)
|
||||
|
||||
# Good
|
||||
def get_user(user_id: str) -> User | None:
|
||||
return User.query.get(user_id)
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
**TypeScript: Implicit any**
|
||||
```typescript
|
||||
// Bad
|
||||
function processData(data) {
|
||||
return data.map(item => item.name);
|
||||
}
|
||||
|
||||
// Good
|
||||
function processData(data: DataItem[]) {
|
||||
return data.map(item => item.name);
|
||||
}
|
||||
```
|
||||
|
||||
**ESLint: Unused variable**
|
||||
```typescript
|
||||
// Bad
|
||||
const [count, setCount] = useState(0);
|
||||
// count is never used
|
||||
|
||||
// Good
|
||||
const [count] = useState(0);
|
||||
// Or remove if not needed
|
||||
```
|
||||
|
||||
## IDE Integration
|
||||
|
||||
### VS Code
|
||||
|
||||
**Settings** (`.vscode/settings.json`):
|
||||
```json
|
||||
{
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.mypyEnabled": true,
|
||||
"python.formatting.provider": "ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PyCharm
|
||||
|
||||
1. **File** → **Settings** → **Tools** → **External Tools**
|
||||
2. Add ruff and mypy as external tools
|
||||
3. Set up pre-commit hooks
|
||||
|
||||
## Pre-commit Hooks
|
||||
|
||||
`.pre-commit-config.yaml`:
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.7.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-all]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: frontend-lint
|
||||
name: Frontend Lint
|
||||
entry: bash -c 'cd apps/web && npm run lint'
|
||||
language: system
|
||||
files: ^apps/web/
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [ruff Documentation](https://docs.astral.sh/ruff/)
|
||||
- [mypy Documentation](https://mypy.readthedocs.io/)
|
||||
- [ESLint Documentation](https://eslint.org/)
|
||||
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
||||
@@ -0,0 +1,299 @@
|
||||
# Development Setup Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.11+
|
||||
- Node.js 18+
|
||||
- PostgreSQL 15+
|
||||
- Redis 7+
|
||||
- Git
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-org/headquarter.git
|
||||
cd headquarter
|
||||
```
|
||||
|
||||
### 2. Backend Setup
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Create virtual environment
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate # Linux/Mac
|
||||
# .venv\Scripts\activate # Windows
|
||||
|
||||
# Install dependencies
|
||||
pip install -e ".[dev]"
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
```
|
||||
|
||||
### 3. Frontend Setup
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
```
|
||||
|
||||
### 4. Database Setup
|
||||
|
||||
```bash
|
||||
# Start PostgreSQL and Redis
|
||||
# (Using Docker or local installation)
|
||||
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=headquarter postgres:15
|
||||
docker run -d -p 6379:6379 redis:7
|
||||
|
||||
# Create database
|
||||
createdb headquarter
|
||||
|
||||
# Run migrations
|
||||
cd apps/api
|
||||
alembic upgrade head
|
||||
|
||||
# (Optional) Seed data
|
||||
python -m src.scripts.seed
|
||||
```
|
||||
|
||||
### 5. Start Development Servers
|
||||
|
||||
Terminal 1 - Backend:
|
||||
```bash
|
||||
cd apps/api
|
||||
source .venv/bin/activate
|
||||
python -m uvicorn src.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
Terminal 2 - Frontend:
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Terminal 3 - (Optional) Authentik:
|
||||
```bash
|
||||
# If using local Authentik
|
||||
docker compose -f docker-compose.authentik.yml up -d
|
||||
```
|
||||
|
||||
## Development Environment Variables
|
||||
|
||||
Create `apps/api/.env`:
|
||||
|
||||
```bash
|
||||
APP_ENV=development
|
||||
DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
SESSION_SECRET=dev-session-secret-change-me
|
||||
API_DOMAIN=localhost
|
||||
WEB_DOMAIN=localhost
|
||||
|
||||
# Authentik (optional for local dev)
|
||||
AUTHENTIK_DOMAIN=authentik.local
|
||||
AUTHENTIK_CLIENT_ID=headquarter-web
|
||||
AUTHENTIK_CLIENT_SECRET=change-me
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter-web
|
||||
```
|
||||
|
||||
Create `apps/web/.env`:
|
||||
|
||||
```bash
|
||||
VITE_API_BASE_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
## IDE Setup
|
||||
|
||||
### VS Code Extensions
|
||||
|
||||
Recommended extensions:
|
||||
- Python (ms-python.python)
|
||||
- Pylance (ms-python.vscode-pylance)
|
||||
- ESLint (dbaeumer.vscode-eslint)
|
||||
- Prettier (esbenp.prettier-vscode)
|
||||
- TypeScript Importer (pmneo.tsimporter)
|
||||
|
||||
### PyCharm/IntelliJ
|
||||
|
||||
1. Open `apps/api` as project
|
||||
2. Set Python interpreter to `.venv`
|
||||
3. Enable Django/Flask support for FastAPI
|
||||
|
||||
## Debugging
|
||||
|
||||
### Backend Debugging
|
||||
|
||||
**VS Code launch.json**:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: FastAPI",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "uvicorn",
|
||||
"args": ["src.main:app", "--reload", "--port", "8000"],
|
||||
"jinja": true,
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**PyCharm**:
|
||||
1. Run → Edit Configurations
|
||||
2. Add Python configuration
|
||||
3. Module name: `uvicorn`
|
||||
4. Parameters: `src.main:app --reload --port 8000`
|
||||
|
||||
### Frontend Debugging
|
||||
|
||||
**VS Code launch.json**:
|
||||
```json
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}/apps/web/src"
|
||||
}
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Create new migration
|
||||
alembic revision --autogenerate -m "description"
|
||||
|
||||
# Run migrations
|
||||
alembic upgrade head
|
||||
|
||||
# Downgrade
|
||||
alembic downgrade -1
|
||||
|
||||
# Current version
|
||||
alembic current
|
||||
|
||||
# History
|
||||
alembic history
|
||||
```
|
||||
|
||||
### Adding Dependencies
|
||||
|
||||
**Backend**:
|
||||
```bash
|
||||
cd apps/api
|
||||
# Add production dependency
|
||||
pip install package-name
|
||||
# Add to pyproject.toml [project.dependencies]
|
||||
|
||||
# Add dev dependency
|
||||
pip install -e ".[dev]"
|
||||
# Add to pyproject.toml [project.optional-dependencies.dev]
|
||||
```
|
||||
|
||||
**Frontend**:
|
||||
```bash
|
||||
cd apps/web
|
||||
npm install package-name
|
||||
npm install -D package-name # dev dependency
|
||||
```
|
||||
|
||||
### Git Workflow
|
||||
|
||||
1. Create feature branch: `git checkout -b feature/name`
|
||||
2. Make changes
|
||||
3. Run quality gates (see below)
|
||||
4. Commit: `git commit -m "feat: description"`
|
||||
5. Push: `git push origin feature/name`
|
||||
6. Create Pull Request
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
headquarter/
|
||||
├── apps/
|
||||
│ ├── api/ # Backend (FastAPI)
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── api/ # API routes
|
||||
│ │ │ ├── auth/ # Authentication
|
||||
│ │ │ ├── models/ # Database models
|
||||
│ │ │ ├── utils/ # Utilities
|
||||
│ │ │ └── main.py # Entry point
|
||||
│ │ ├── tests/ # Test suites
|
||||
│ │ ├── alembic/ # Migrations
|
||||
│ │ └── pyproject.toml # Dependencies
|
||||
│ └── web/ # Frontend (React)
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # API clients
|
||||
│ │ ├── components/ # React components
|
||||
│ │ ├── pages/ # Page components
|
||||
│ │ └── styles/ # CSS
|
||||
│ └── package.json # Dependencies
|
||||
├── docs/ # Documentation
|
||||
├── docker-compose.yml # Dev setup
|
||||
└── Makefile # Common commands
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Connection Errors
|
||||
|
||||
```bash
|
||||
# Check PostgreSQL is running
|
||||
pg_isready -h localhost -p 5432
|
||||
|
||||
# Check credentials
|
||||
psql postgresql://headquarter:headquarter@localhost:5432/headquarter -c "SELECT 1"
|
||||
```
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
```bash
|
||||
# Find process using port 8000
|
||||
lsof -i :8000
|
||||
|
||||
# Kill process
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
### Node Modules Issues
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
### Python Environment Issues
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
rm -rf .venv
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
||||
- [React Documentation](https://react.dev/)
|
||||
- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/)
|
||||
- [Alembic Documentation](https://alembic.sqlalchemy.org/)
|
||||
@@ -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