Skip to content

Testing

This guide covers the complete testing strategy for the infra repository, including infrastructure validation (nix-unit, validation checks, VM tests) and documentation testing (Vitest, Playwright).

Testing effort scales with change risk. Not all changes require the same validation depth.

The testing strategy focuses on three principles:

  1. Critical path first: Validate core functionality that, if broken, would block all development.
  2. Depth matches risk: Configuration-only changes need less validation than module logic changes.
  3. Trust boundaries: Local validation for high-risk changes; trust CI for well-tested patterns.

Match your validation depth to what you changed:

Change typeRecommended checkRationale
Config values onlyjust check-fastLow risk, fast feedback (~1-2 min)
Module logicjust checkMedium risk, need full validation (~5-7 min)
New host/userjust check + manual deployHigh risk, full validation + real deployment
CI workflow changesPush to trigger CICI is the test
Documentation onlyjust docs-buildStarlight validation, no nix checks needed
Test infrastructurejust checkTest the tests

Run comprehensive validation (just check or nix flake check) before:

  • Creating or updating a pull request
  • After rebasing on main
  • When touching test infrastructure (modules/checks/)
  • When adding new machines or users
  • After significant flake.lock updates

For routine development iteration, just check-fast provides faster feedback by skipping VM integration tests.

Infrastructure tests validate the nix flake structure, machine configurations, and deployment safety.

CategoryFileCountPurpose
nix-unitmodules/checks/nix-unit.nix16Unit tests for flake structure and invariants
validationmodules/checks/validation.nix10Configuration validation and naming conventions
integrationmodules/checks/integration.nix2VM boot tests for NixOS machines
performancemodules/checks/performance.nix0 (planned: 4)Performance benchmarks and optimization (planned)
treefmt(flake-parts)1Code formatting validation
pre-commit(flake-parts)1Pre-commit hook validation
Terminal window
# Run all checks (includes VM tests, ~5-7 minutes)
just check
# Run fast checks only (excludes VM tests, ~1-2 minutes)
just check-fast
# Run specific check
nix build .#checks.aarch64-darwin.nix-unit
# Run with verbose output for debugging
nix flake check --show-trace

nix-unit tests validate flake structure and configuration invariants without building derivations.

TC-IDTest NameTypeDescription
TC-001testMetadataFlakeOutputsExistsmokeFlake structure smoke test
TC-002testRegressionTerraformModulesExistregressionTerraform module exports exist
TC-003testRegressionNixosConfigExistsregressionNixOS config structure valid
TC-004testInvariantClanInventoryMachinesinvariantExpected machines in clan inventory
TC-005testInvariantNixosConfigurationsExistinvariantExpected NixOS configs present
TC-006testInvariantDarwinConfigurationsExistinvariantExpected Darwin configs present
TC-007testInvariantHomeConfigurationsExistinvariantExpected home configs present
TC-008testFeatureModuleDiscoveryfeatureimport-tree discovers nixos modules
TC-009testFeatureDarwinModuleDiscoveryfeatureimport-tree discovers darwin modules
TC-010testFeatureNamespaceExportsfeatureModules export to correct namespaces
TC-011testTypeSafetySpecialargsPropagationtype-safetyinputs available via specialArgs
TC-012testTypeSafetyNixosConfigStructuretype-safetyAll configs have config attribute
TC-013testInvariantNamespaceMerginginvariantModule namespace auto-merging works
TC-014testInvariantClanModuleIntegrationinvariantClan machines have module exports
TC-015testFeatureImportTreeCompletenessfeatureimport-tree discovers all namespace modules
TC-016testInvariantCrossplatformHomeModulesinvariantHome modules available for darwin and linux

Validation checks run shell commands to verify configuration correctness.

TC-IDCheck NameDescription
TC-020home-module-exportsHome modules exported to flake namespace
TC-021home-configurations-exposedNested homeConfigurations exposed for nh CLI
TC-022naming-conventionsMachine names follow kebab-case
TC-023terraform-validateTerraform configuration syntactically valid
TC-024terraform-config-structureTerraform config has expected resources
TC-025vars-user-password-validationClan vars system for user passwords
TC-026secrets-tier-separationSecrets organized in correct tiers (vars vs secrets)
TC-027clan-inventory-consistencyInventory references only valid machines
TC-028secrets-encryption-integrityAll secret files are SOPS-encrypted
TC-029machine-registry-completenessAll machine modules registered in clan

VM integration tests require QEMU/KVM and only run on Linux systems.

TC-IDCheck NameDescription
TC-040vm-test-frameworkVM test framework smoke test
TC-041vm-boot-all-machinesVM boot validation for NixOS machines

These tests are automatically skipped on Darwin. Use just check-fast to skip them locally on Linux when iterating quickly.

Performance tests validate build efficiency and closure sizes.

TC-IDCheck NameDescription
TC-050closure-size-validationClosure size validation (planned)
TC-051ci-build-matrixCI build matrix optimization (planned)
TC-052build-performance-benchmarksBuild performance benchmarks (planned)
TC-053binary-cache-efficiencyBinary cache efficiency (planned)

These tests are currently in planning phase and not yet implemented.

Documentation uses Vitest for unit testing and Playwright for E2E testing.

packages/docs/
├── src/
│ ├── components/
│ │ └── Card.test.ts # Component tests (co-located)
│ └── utils/
│ └── formatters.test.ts # Unit tests (co-located)
├── e2e/
│ └── homepage.spec.ts # E2E tests
├── tests/
│ ├── fixtures/ # Shared test data
│ └── types/ # Test type definitions
├── vitest.config.ts # Vitest configuration
└── playwright.config.ts # Playwright configuration
Terminal window
# Run all documentation tests
just docs-test
# Run unit tests only
just docs-test-unit
# Run E2E tests
just docs-test-e2e
# Generate coverage report
just docs-test-coverage
# Watch mode for development
cd packages/docs && bun run test:watch

Unit tests use Vitest and are co-located with source files:

src/utils/formatters.test.ts
import { describe, expect, it } from "vitest";
import { capitalizeFirst } from "./formatters";
describe("capitalizeFirst", () => {
it("capitalizes the first letter", () => {
expect(capitalizeFirst("hello")).toBe("Hello");
});
});

Use the Astro Container API to test Astro components:

src/components/Card.test.ts
import { experimental_AstroContainer as AstroContainer } from "astro/container";
import { describe, expect, it } from "vitest";
import Card from "./Card.astro";
describe("Card component", () => {
it("renders with props", async () => {
const container = await AstroContainer.create();
const result = await container.renderToString(Card, {
props: { title: "Test Card" },
});
expect(result).toContain("Test Card");
});
});

E2E tests use Playwright and live in the e2e/ directory:

e2e/homepage.spec.ts
import { expect, test } from "@playwright/test";
test.describe("Homepage", () => {
test("has correct title", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/infra/);
});
});

Use this decision tree to select the appropriate validation level:

Changed documentation only?
└─ Yes → just docs-build && just docs-linkcheck
└─ No → Continue...
Changed config values only (no module logic)?
└─ Yes → just check-fast
└─ No → Continue...
Changed module logic, added host/user, or touched tests?
└─ Yes → just check
└─ No → Continue...
Changed CI workflow?
└─ Yes → Push to trigger CI (CI is the test)
CommandRuntimeVM testsUse when
just check~5-7 minIncludedBefore PR, after rebase, full validation
just check-fast~1-2 minExcludedDevelopment iteration, config-only changes

The difference is VM integration tests, which:

  • Require QEMU/KVM (Linux only)
  • Boot NixOS VMs to validate machine configurations
  • Are automatically skipped on Darwin

Symptom: nix-unit tests fail with attribute errors

error: attribute 'cinnabar' missing

Solution: Check that expected machine names match your configuration. Update test expectations in modules/checks/nix-unit.nix if you’ve renamed machines.

Symptom: Warnings about unknown settings

warning: unknown setting 'allowed-users'

Context: These warnings are expected and harmless. nix-unit runs in pure evaluation mode where daemon settings don’t apply.

Symptom: VM tests fail on Darwin

Context: VM integration tests require QEMU/KVM and only work on Linux. They’re automatically skipped on Darwin via lib.optionalAttrs isLinux.

Solution: Use just check-fast on Darwin. CI runs VM tests on Linux runners.

Symptom: Different derivation hashes between systems

Context: Some packages have platform-specific closures. This is expected for darwin-specific and linux-specific configurations.

Symptom: CI job runs when it should skip (or vice versa)

Context: CI uses content-addressed caching based on file hashes.

Solutions:

  • Force rerun: gh workflow run ci.yaml --ref $(git branch --show-current) -f force_run=true
  • Or add force-ci label to PR
  • Check hash-sources patterns in CI job definitions

Symptom: Cache hit but build still fails

Context: Cache key may match but cached result may be stale.

Solution: Force rerun with force_run=true to bypass cache.

Terminal window
# Run single check by name
nix build .#checks.aarch64-darwin.nix-unit
# List all available checks
nix flake show --json | jq '.checks'
# Skip VM tests (fast mode)
just check-fast x86_64-linux

Symptom: Playwright browsers not found

Terminal window
# Install Playwright browsers
just playwright-install
# or
cd packages/docs && bunx playwright install --with-deps

Symptom: Port 4321 already in use

Solution: Stop other development servers on port 4321.

Symptom: Nix environment issues with Playwright

Terminal window
# Rebuild nix shell
nix develop --rebuild
# Verify environment variables
echo $PLAYWRIGHT_BROWSERS_PATH

Test behavior can be configured via module options in modules/checks/:

Tests are enabled by default. To disable specific test categories during development:

# In a flake module (not recommended for production)
perSystem = { ... }: {
checks = lib.optionalAttrs false { /* disabled checks */ };
};
VariablePurpose
PLAYWRIGHT_BROWSERS_PATHPath to Playwright browser binaries
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTSSkip host validation for Nix
SOPS_AGE_KEY_FILEPath to age key for secrets tests

Run specific test categories:

Terminal window
# nix-unit tests only
nix build .#checks.aarch64-darwin.nix-unit
# Validation checks only
nix build .#checks.aarch64-darwin.naming-conventions
nix build .#checks.aarch64-darwin.terraform-validate
# Integration tests (Linux only)
nix build .#checks.x86_64-linux.vm-boot-all-machines