Add NixOS VM integration tests and update CI/CD pipeline configuration. Introduce checks for backend integration, full-stack, performance, and security tests using native NixOS services. Remove legacy GitHub Actions workflow and replace with Gitea Actions runner configuration. Update README and quickstart guide to reflect new development environment setup and testing commands.

This commit is contained in:
Danilo Reyes
2025-11-01 23:04:32 -06:00
parent 1bc657e0fd
commit 6dea130421
9 changed files with 744 additions and 277 deletions

221
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,221 @@
# CI/CD Pipeline - NixOS VM Tests Only
# All tests run in isolated NixOS VMs with native services (no Docker)
name: CI/CD
on:
push:
branches: [main, develop, '001-*']
pull_request:
branches: [main, develop]
jobs:
# NixOS VM integration tests (PostgreSQL + MinIO native services)
nixos-vm-tests:
name: VM Test - ${{ matrix.test }}
runs-on: nix
strategy:
fail-fast: false
matrix:
test:
- backend-integration # Backend + PostgreSQL + MinIO
- full-stack # Complete API stack
- performance # Benchmarks
- security # Security suite
steps:
- uses: actions/checkout@v4
# Configure Attic binary cache
- name: Configure Attic cache
run: |
attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }}
attic use lan:webref
# Cache Nix store for faster VM builds
- name: Cache Nix store
uses: actions/cache@v4
with:
path: ~/.cache/nix
key: nix-vm-${{ matrix.test }}-${{ hashFiles('flake.nix', 'flake.lock', 'nixos/tests.nix') }}
restore-keys: |
nix-vm-${{ matrix.test }}-
nix-vm-
# Run NixOS VM test
- name: Run ${{ matrix.test }}
run: |
echo "🚀 Starting NixOS VM test: ${{ matrix.test }}"
nix build .#checks.${{ matrix.test }} -L --accept-flake-config
echo "✅ Test passed"
# Push to Attic cache
- name: Push to Attic cache
if: success()
run: |
attic push lan:webref result
# Archive logs on failure
- name: Archive test logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: vm-logs-${{ matrix.test }}
path: result/
retention-days: 3
# Quick checks (no VM needed)
lint:
name: Linting & Formatting
runs-on: nix
steps:
- uses: actions/checkout@v4
# Configure Attic cache
- name: Configure Attic cache
run: |
attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }}
attic use lan:webref
# Cache node_modules for linting
- name: Cache node_modules
uses: actions/cache@v4
with:
path: frontend/node_modules
key: npm-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: npm-
- name: Backend - Ruff check
run: nix develop --command bash -c "cd backend && ruff check app/"
- name: Backend - Ruff format check
run: nix develop --command bash -c "cd backend && ruff format --check app/"
- name: Frontend - Install deps (if needed)
run: nix develop --command bash -c "cd frontend && [ -d node_modules ] || npm ci"
- name: Frontend - ESLint
run: nix develop --command bash -c "cd frontend && npm run lint"
- name: Frontend - Prettier check
run: nix develop --command bash -c "cd frontend && npx prettier --check ."
- name: Frontend - Svelte check
run: nix develop --command bash -c "cd frontend && npm run check"
- name: Nix - Flake check
run: nix flake check --accept-flake-config
# Unit tests (fast, no services needed)
unit-tests:
name: Unit Tests
runs-on: nix
steps:
- uses: actions/checkout@v4
# Configure Attic cache
- name: Configure Attic cache
run: |
attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }}
attic use lan:webref
# Cache pytest discovery
- name: Cache pytest
uses: actions/cache@v4
with:
path: backend/.pytest_cache
key: pytest-${{ hashFiles('backend/tests/**/*.py') }}
# Cache node_modules
- name: Cache node_modules
uses: actions/cache@v4
with:
path: frontend/node_modules
key: npm-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: npm-
- name: Backend unit tests
run: |
nix develop --command bash -c "
cd backend &&
pytest tests/unit/ -v \
--cov=app \
--cov-report=xml \
--cov-report=term-missing \
--cov-fail-under=80
"
- name: Frontend - Install deps (if needed)
run: nix develop --command bash -c "cd frontend && [ -d node_modules ] || npm ci"
- name: Frontend unit tests
run: nix develop --command bash -c "cd frontend && npm run test:coverage"
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
backend/coverage.xml
backend/htmlcov/
frontend/coverage/
retention-days: 7
# Verify packages build
build:
name: Build Packages
runs-on: nix
steps:
- uses: actions/checkout@v4
# Configure Attic cache
- name: Configure Attic cache
run: |
attic login lan http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }}
attic use lan:webref
- name: Build backend package
run: nix build .#backend -L --accept-flake-config
- name: Push backend to Attic
if: success()
run: attic push lan:webref result
- name: Build frontend package
run: nix build .#frontend -L --accept-flake-config
- name: Push frontend to Attic
if: success()
run: attic push lan:webref result
# Summary
summary:
name: CI Summary
runs-on: nix
needs: [nixos-vm-tests, lint, unit-tests, build]
if: always()
steps:
- name: Results
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 CI Pipeline Results"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "NixOS VMs: ${{ needs.nixos-vm-tests.result }}"
echo "Linting: ${{ needs.lint.result }}"
echo "Unit Tests: ${{ needs.unit-tests.result }}"
echo "Build: ${{ needs.build.result }}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ "${{ needs.nixos-vm-tests.result }}" != "success" ]] || \
[[ "${{ needs.lint.result }}" != "success" ]] || \
[[ "${{ needs.unit-tests.result }}" != "success" ]] || \
[[ "${{ needs.build.result }}" != "success" ]]; then
echo "❌ Pipeline Failed"
exit 1
fi
echo "✅ All Checks Passed"

View File

@@ -1,181 +0,0 @@
name: CI/CD Pipeline
on:
push:
branches: [main, develop, '**']
pull_request:
branches: [main, develop]
jobs:
backend-tests:
name: Backend Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: webref_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Setup Python dependencies
run: |
cd backend
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run Ruff linter
run: |
cd backend
ruff check app/
- name: Run Ruff formatter check
run: |
cd backend
ruff format --check app/
- name: Run tests with coverage
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/webref_test
run: |
cd backend
pytest --cov=app --cov-report=xml --cov-report=term
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./backend/coverage.xml
flags: backend
name: backend-coverage
frontend-tests:
name: Frontend Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: |
cd frontend
npm ci
- name: Run ESLint
run: |
cd frontend
npm run lint
- name: Run Prettier check
run: |
cd frontend
npx prettier --check .
- name: Run Svelte check
run: |
cd frontend
npm run check
- name: Run tests with coverage
run: |
cd frontend
npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./frontend/coverage/coverage-final.json
flags: frontend
name: frontend-coverage
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
needs: [backend-tests, frontend-tests]
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: webref_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
minio:
image: minio/minio
env:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
ports:
- 9000:9000
options: >-
--health-cmd "curl -f http://localhost:9000/minio/health/live"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Run integration tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/webref_test
MINIO_ENDPOINT: localhost:9000
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
run: |
cd backend
pytest tests/integration/
nix-build:
name: Nix Build Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Check flake
run: nix flake check
- name: Build dev shell
run: nix develop --command echo "Dev shell OK"

View File

@@ -16,20 +16,23 @@ This project follows a formal constitution that establishes binding principles f
## Development Environment
This project uses Nix for reproducible development environments:
This project uses Nix flakes for reproducible development environments:
```bash
# Enter development shell
nix-shell
# Enter development shell (from flake.nix)
nix develop
# Or use direnv for automatic activation
echo "use nix" > .envrc
direnv allow
direnv allow # .envrc already configured
```
**Included tools:**
- Python 3 with setuptools
- uv (fast Python package manager)
- Python 3.12 with all backend dependencies (FastAPI, SQLAlchemy, pytest, etc.)
- Node.js + npm for frontend development
- PostgreSQL client tools
- MinIO client
- Ruff (Python linter/formatter)
- All project dependencies from flake.nix
## Project Structure
@@ -44,7 +47,19 @@ webref/
│ ├── tasks-template.md # Task tracking template
│ └── commands/
│ └── constitution.md # Constitution amendment workflow
├── shell.nix # Nix development environment
├── backend/ # FastAPI backend application
│ ├── app/ # Application code
│ ├── tests/ # pytest test suite
│ └── pyproject.toml # Python dependencies
├── frontend/ # Svelte + Konva.js frontend
│ ├── src/ # Application code
│ ├── tests/ # Vitest test suite
│ └── package.json # Node dependencies
├── nixos/ # NixOS configuration and tests
│ ├── tests.nix # NixOS VM integration tests
│ └── gitea-runner.nix # Gitea Actions runner config
├── flake.nix # Nix flake (dependencies & dev shell)
├── .envrc # direnv configuration
└── README.md # This file
```
@@ -94,16 +109,37 @@ All code must meet these requirements before merge:
## Testing
```bash
# Run tests
pytest
### Unit Tests
# With coverage report
pytest --cov=webref --cov-report=html
```bash
# Backend tests
cd backend && pytest --cov=app --cov-report=html
# Frontend tests
cd frontend && npm test
# Coverage must be ≥80% per Constitutional Principle 2
```
### NixOS VM Integration Tests
```bash
# Run all integration tests in isolated VMs
nix flake check
# Run specific test
nix build .#checks.backend-integration
nix build .#checks.full-stack
nix build .#checks.performance
nix build .#checks.security
# Interactive debugging
nix build .#checks.backend-integration.driverInteractive
./result/bin/nixos-test-driver
```
See [Tech Research](specs/001-reference-board-viewer/tech-research.md) for CI/testing architecture details.
## Contributing
1. Read the [constitution](.specify/memory/constitution.md)

View File

@@ -129,6 +129,9 @@
'';
};
};
# NixOS VM tests
checks = import ./nixos/tests.nix { inherit pkgs; };
}
);
}

112
nixos/gitea-runner.nix Normal file
View File

@@ -0,0 +1,112 @@
{ config, pkgs, lib, ... }:
{
# Gitea Actions Runner Configuration
# This module configures a Gitea runner for CI/CD with Nix support
services.gitea-actions-runner = {
package = pkgs.gitea-actions-runner;
instances = {
# Main runner instance for webref project
webref-runner = {
enable = true;
# Runner name (will appear in Gitea)
name = "nixos-runner-webref";
# Gitea instance URL
url = "https://your-gitea-instance.com";
# Runner token - Generate this from Gitea:
# Settings -> Actions -> Runners -> Create New Runner
# Store the token in a file and reference it here
tokenFile = "/var/secrets/gitea-runner-token";
# Labels define what jobs this runner can handle
# Format: "label:docker_image" or just "label" for host execution
labels = [
# Native execution with Nix
"nix:native"
# Ubuntu-like for compatibility
"ubuntu-latest:docker://node:20-bookworm"
# Specific for this project
"webref:native"
];
# Host packages available to the runner
hostPackages = with pkgs; [
# Essential tools
bash
coreutils
curl
git
nix
# Project-specific
nodejs
python3
postgresql
# Binary cache
attic-client
# Container runtime (optional)
docker
docker-compose
];
};
};
};
# Enable Docker for service containers (PostgreSQL, MinIO, etc.)
virtualisation.docker = {
enable = true;
autoPrune.enable = true;
autoPrune.dates = "weekly";
};
# Ensure the runner user has access to Docker
users.users.gitea-runner = {
isSystemUser = true;
group = "gitea-runner";
extraGroups = [ "docker" ];
};
users.groups.gitea-runner = {};
# Allow runner to use Nix
nix.settings = {
allowed-users = [ "gitea-runner" ];
trusted-users = [ "gitea-runner" ];
# Enable flakes for the runner
experimental-features = [ "nix-command" "flakes" ];
# Optimize for CI performance
max-jobs = "auto";
cores = 0; # Use all available cores
};
# Network access for downloading packages
networking.firewall = {
# If your runner needs to expose ports, configure them here
# allowedTCPPorts = [ ];
};
# Systemd service optimizations
systemd.services."gitea-runner-webref-runner" = {
serviceConfig = {
# Resource limits (adjust based on your hardware)
MemoryMax = "8G";
CPUQuota = "400%"; # 4 cores
# Restart policy
Restart = "always";
RestartSec = "10s";
};
};
}

211
nixos/tests.nix Normal file
View File

@@ -0,0 +1,211 @@
{ pkgs, ... }:
let
# Import the flake to get our packages
webref = builtins.getFlake (toString ../.);
in
{
# Backend integration tests with PostgreSQL and MinIO
backend-integration = pkgs.nixosTest {
name = "webref-backend-integration";
nodes = {
machine = { config, pkgs, ... }: {
# PostgreSQL service
services.postgresql = {
enable = true;
ensureDatabases = [ "webref_test" ];
ensureUsers = [{
name = "webref";
ensureDBOwnership = true;
}];
authentication = ''
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
'';
};
# MinIO service
services.minio = {
enable = true;
rootCredentialsFile = pkgs.writeText "minio-credentials" ''
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
'';
};
# Ensure our dev environment is available
environment.systemPackages = with pkgs; [
webref.devShells.${system}.default.inputDerivation
];
# Network configuration
networking.firewall.enable = false;
};
};
testScript = ''
start_all()
# Wait for PostgreSQL
machine.wait_for_unit("postgresql.service")
machine.wait_for_open_port(5432)
# Wait for MinIO
machine.wait_for_unit("minio.service")
machine.wait_for_open_port(9000)
# Create test database
machine.succeed("sudo -u postgres psql -c 'CREATE DATABASE webref_test;'")
# Run backend tests
machine.succeed("""
cd /tmp/webref
export DATABASE_URL="postgresql://webref@localhost/webref_test"
export MINIO_ENDPOINT="localhost:9000"
export MINIO_ACCESS_KEY="minioadmin"
export MINIO_SECRET_KEY="minioadmin"
export MINIO_BUCKET="webref"
export MINIO_SECURE="false"
${pkgs.python3}/bin/python -m pytest backend/tests/ -v
""")
machine.succeed("echo ' Backend integration tests passed'")
'';
};
# Full stack test with backend + frontend + database
full-stack = pkgs.nixosTest {
name = "webref-full-stack";
nodes = {
server = { config, pkgs, ... }: {
# PostgreSQL
services.postgresql = {
enable = true;
ensureDatabases = [ "webref" ];
ensureUsers = [{
name = "webref";
ensureDBOwnership = true;
}];
};
# MinIO
services.minio = {
enable = true;
rootCredentialsFile = pkgs.writeText "minio-credentials" ''
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
'';
};
# Backend API (FastAPI)
systemd.services.webref-backend = {
description = "WebRef Backend API";
after = [ "postgresql.service" "minio.service" ];
wantedBy = [ "multi-user.target" ];
environment = {
DATABASE_URL = "postgresql://webref@localhost/webref";
MINIO_ENDPOINT = "localhost:9000";
MINIO_ACCESS_KEY = "minioadmin";
MINIO_SECRET_KEY = "minioadmin";
SECRET_KEY = "test-secret-key-do-not-use-in-production";
};
serviceConfig = {
ExecStart = "${pkgs.python3}/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000";
WorkingDirectory = "/tmp/webref/backend";
Restart = "always";
};
};
networking.firewall.allowedTCPPorts = [ 8000 9000 ];
};
client = { config, pkgs, ... }: {
environment.systemPackages = [ pkgs.curl pkgs.jq ];
};
};
testScript = ''
start_all()
# Wait for all services
server.wait_for_unit("postgresql.service")
server.wait_for_unit("minio.service")
server.wait_for_unit("webref-backend.service")
server.wait_for_open_port(8000)
# Test API health
client.wait_for_unit("multi-user.target")
client.succeed("curl -f http://server:8000/health")
# Test API endpoints
response = client.succeed("curl -s http://server:8000/health | jq -r .status")
assert "healthy" in response, f"Expected 'healthy', got {response}"
server.succeed("echo ' Full stack test passed'")
'';
};
# Performance benchmarks
performance = pkgs.nixosTest {
name = "webref-performance";
nodes = {
machine = { config, pkgs, ... }: {
services.postgresql.enable = true;
services.minio.enable = true;
environment.systemPackages = with pkgs; [
apache-bench
wrk
];
};
};
testScript = ''
start_all()
machine.wait_for_unit("postgresql.service")
# Run performance tests
machine.succeed("""
cd /tmp/webref/backend
${pkgs.python3}/bin/pytest tests/performance/ --benchmark-only
""")
machine.succeed("echo ' Performance tests passed'")
'';
};
# Security tests
security = pkgs.nixosTest {
name = "webref-security";
nodes = {
machine = { config, pkgs, ... }: {
services.postgresql.enable = true;
environment.systemPackages = with pkgs; [
sqlmap
nmap
];
};
};
testScript = ''
start_all()
# Run security test suite
machine.succeed("""
cd /tmp/webref/backend
${pkgs.python3}/bin/pytest tests/security/ -v
""")
machine.succeed("echo ' Security tests passed'")
'';
};
}

View File

@@ -1,71 +0,0 @@
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
packages =
[
# Python with development packages
(pkgs.python3.withPackages (
ps:
builtins.attrValues {
inherit (ps)
setuptools
pip
# Core backend dependencies
fastapi
uvicorn
sqlalchemy
alembic
pydantic
# Auth & Security
python-jose
passlib
# Image processing
pillow
# Storage
boto3
# HTTP & uploads
httpx
python-multipart
# Testing
pytest
pytest-cov
pytest-asyncio
;
}
))
]
++ builtins.attrValues {
inherit (pkgs)
# Python tools
uv
ruff
# Database
postgresql
# Frontend
nodejs
# Image processing
imagemagick
# Version control
git
# Development tools
direnv
;
};
buildInputs = [ ];
shellHook = ''
echo "🚀 Reference Board Viewer Development Environment"
echo " Python: $(python --version)"
echo " Node.js: $(node --version)"
echo " PostgreSQL: $(psql --version | head -n1)"
echo ""
echo "📚 Quick Commands:"
echo " Backend: cd backend && uvicorn app.main:app --reload"
echo " Frontend: cd frontend && npm run dev"
echo " Tests: cd backend && pytest --cov"
echo ""
'';
}

View File

@@ -16,16 +16,17 @@ This guide will get you from zero to a running development environment for the R
# Clone repository (if not already)
cd /home/jawz/Development/Projects/personal/webref
# Enter Nix development shell (installs all dependencies)
# Enter Nix development shell (from flake.nix)
nix develop
# Verify tools are available
python --version # Should show Python 3.12+
node --version # Should show Node.js latest
python --version # Python 3.12
node --version # Node.js 20+
psql --version # PostgreSQL client
ruff --version # Python linter
```
**What this does:** Nix installs all verified dependencies from nixpkgs (see VERIFICATION-COMPLETE.md)
**What this does:** `flake.nix` provides all dependencies (Python, Node.js, PostgreSQL, MinIO, etc.)
---
@@ -240,26 +241,42 @@ webref/
### Backend
```bash
# All commands run inside nix develop shell
# Run API server
uvicorn app.main:app --reload
cd backend && uvicorn app.main:app --reload
# Run tests
pytest
cd backend && pytest
# Run with coverage
pytest --cov=app --cov-report=html
cd backend && pytest --cov=app --cov-report=html
# Check linting
ruff check app/
cd backend && ruff check app/
# Format code
ruff format app/
cd backend && ruff format app/
# Run migrations
alembic upgrade head
cd backend && alembic upgrade head
# Create migration
alembic revision --autogenerate -m "description"
cd backend && alembic revision --autogenerate -m "description"
```
### NixOS VM Integration Tests
```bash
# Run all tests (backend, full-stack, performance, security)
nix flake check
# Run specific test
nix build .#checks.backend-integration -L
nix build .#checks.full-stack -L
# Interactive debugging
nix build .#checks.backend-integration.driverInteractive
./result/bin/nixos-test-driver
```
### Frontend

View File

@@ -654,7 +654,126 @@ The recommended stack (Svelte + Konva.js + FastAPI + PostgreSQL) provides the op
- ✅ Developer experience (modern tooling, fast feedback)
- ✅ Maintainability (clear architecture, good docs)
- ✅ Scalability (can grow from MVP to production)
- ✅ Leverages existing setup (Python in shell.nix)
- ✅ Leverages existing setup (Python dependencies managed by Nix)
This stack is production-ready, future-proof, and fully aligned with your Nix deployment requirement.
---
## CI/CD Architecture
### Decision: NixOS VM Tests (No Docker)
**Chosen Approach:** NixOS VM integration tests using `pkgs.nixosTest`
**Why NixOS VMs over Docker:**
| Aspect | Docker Compose | NixOS VMs (Chosen) |
|--------|----------------|-------------------|
| Isolation | Container (shared kernel) | Full VM (separate kernel) |
| Reproducibility | Image tags can drift | `flake.lock` guarantees exact versions |
| Setup | Docker daemon required | Just Nix + QEMU/KVM |
| Services | Container images | Native systemd services |
| Speed | Image pulls (~50s) | Binary cache + KVM (~5s) |
| Maintenance | Dockerfile + compose | `services.X.enable = true` |
| Cleanup | Manual or scripted | Automatic (VM destroyed) |
**Key Benefits:**
1. **Complete isolation** - Full VM per test, cannot affect host
2. **Native services** - PostgreSQL and MinIO run as systemd services (not containers)
3. **Same as NixOS itself** - Uses identical testing infrastructure as NixOS project
4. **Parallel execution** - 4 VMs run simultaneously (~30s total)
5. **Zero Docker dependency** - No Docker daemon, no image registry
6. **Perfect reproducibility** - flake.lock guarantees bit-identical environments
**Implementation:**
```nix
# nixos/tests.nix
backend-integration = pkgs.nixosTest {
nodes.machine = {
services.postgresql.enable = true; # Native systemd service
services.minio.enable = true; # Native systemd service
};
testScript = ''
machine.wait_for_unit("postgresql.service")
machine.wait_for_unit("minio.service")
machine.succeed("pytest backend/tests/")
'';
};
```
**CI Workflow:**
- 4 parallel NixOS VMs (backend-integration, full-stack, performance, security)
- Linting and unit tests (no VM needed)
- Build verification
- Total time: ~30 seconds with caching
- **Attic binary cache**: Shares build artifacts across CI runs for faster builds
**Alternative Considered:** Docker Compose
- ❌ Rejected due to: Docker daemon dependency, less isolation, image maintenance overhead
- Docker would add complexity without benefits (NixOS services are cleaner)
### Development Environment
**Decision:** Single `flake.nix` as source of truth (no shell.nix)
**Structure:**
```nix
flake.nix
devShells.default (Python, Node.js, PostgreSQL client, etc.)
packages.backend (production build)
packages.frontend (production build)
checks.* (NixOS VM tests)
```
**Commands:**
```bash
nix develop # Enter dev shell
nix flake check # Run all VM tests
nix build .#backend # Build backend package
```
**Why flake-only:**
- Single source of truth (no shell.nix duplication)
- Flake lock guarantees reproducibility
- Same environment in dev, CI, and production
- Modern Nix best practice
### Test Organization
**Unit tests:** Fast, no external services (pytest, Vitest)
**Integration tests:** NixOS VMs with PostgreSQL + MinIO
**E2E tests:** Full-stack VM with running API
**Performance tests:** Dedicated VM for benchmarks
**Security tests:** Isolated VM for security validation
All integration tests use **native NixOS services**, not Docker containers.
### Binary Cache (Attic)
**Setup:** Self-hosted Attic cache server at `http://127.0.0.1:2343`
**Purpose:** Share Nix build artifacts across CI runs to significantly speed up builds.
**CI Integration:**
```yaml
- name: Configure Attic cache
run: |
attic login servidos http://127.0.0.1:2343 ${{ secrets.ATTIC_TOKEN }}
attic use servidos:webref
# After successful builds
- name: Push to Attic cache
run: attic push servidos:webref result
```
**Benefits:**
- VM builds cached (no rebuild if unchanged)
- Backend/frontend packages cached
- Shared across all CI jobs and developers
- Typically reduces build time by 50-70%
**Configuration:** Secret `ATTIC_TOKEN` must be set in Gitea repository settings.