Skip to content

Comprehensive test suite #16

Description

@castrojo

Description

Implement a comprehensive test suite covering all internal packages. The strategy prioritizes determinism (no real hardware, no real network), speed (table-driven unit tests), and confidence (golden files catch config regressions).

Interfaces Required (define before writing tests)

// internal/runner/runner.go
type Runner interface {
    Run(ctx context.Context, name string, args ...string) (Output, error)
}
type Output struct { Stdout, Stderr string; ExitCode int }

// internal/bakery/client.go
type HTTPClient interface {
    Do(req *http.Request) (*http.Response, error)
}

// internal/probe/prober.go
type DiskProber interface { ListDisks(ctx context.Context) ([]Disk, error) }
type NetworkProber interface { ListInterfaces(ctx context.Context) ([]Interface, error) }

// internal/install/installer.go
type Installer interface { Install(ctx context.Context, config InstallConfig) error }

Per-Package Test Plan

internal/runner

  • TestRunner_Execute_ReturnsStdout — captures stdout from real command
  • TestRunner_Execute_ReturnsStderr — captures stderr separately
  • TestRunner_Execute_NonZeroExit — returns structured error with exit code
  • TestRunner_DryRun_LogsCommand — dry-run records command without executing
  • TestRunner_DryRun_DoesNotMutateSystem — no side effects
  • TestRunner_Execute_Timeout — context cancellation kills child process
  • TestRunner_Execute_BinaryNotFound — wraps exec.ErrNotFound
  • TestRunner_CommandLog_OrderPreserved — dry-run log order

internal/probe

  • TestProbe_Disks_ParsesLsblkJSON — parses fixture (NVMe, SATA, USB)
  • TestProbe_Disks_FiltersByType — excludes loop, rom, lvm
  • TestProbe_Disks_EmptyOutput — no disks → empty slice, no error
  • TestProbe_Disks_MalformedJSON — returns parse error
  • TestProbe_Disks_ByIDPath — maps /dev/sda → /dev/disk/by-id/...
  • TestProbe_Network_ParsesIPLink — parses fixture (eth0, wlan0, lo)
  • TestProbe_Network_FiltersLoopback — excludes lo
  • TestProbe_Network_NoInterfaces — returns empty, no error

internal/bakery

  • TestBakery_FetchCatalog_Success — parses valid catalog JSON (httptest)
  • TestBakery_FetchCatalog_HTTPError — 404/500 returns typed error
  • TestBakery_FetchCatalog_Timeout — context deadline exceeded
  • TestBakery_FetchCatalog_MalformedJSON — decode error with body prefix
  • TestBakery_FetchCatalog_EmptyCatalog — zero sysexts is valid
  • TestBakery_FilterByArch_AMD64 — filters entries by architecture
  • TestBakery_SysextURL_Constructs — builds download URL from entry

internal/ignition (golden file tests)

  • TestIgnition_Assemble_MinimalConfig — hostname + disk → valid Butane
  • TestIgnition_Assemble_WithSSHKeys — SSH keys in passwd section
  • TestIgnition_Assemble_WithSysexts — sysext units and downloads
  • TestIgnition_Assemble_WithStaticIP — network unit rendered
  • TestIgnition_Assemble_FlatcarVariant — output contains variant: flatcar
  • TestIgnition_Compile_GoldenMinimal — golden file comparison
  • TestIgnition_Compile_GoldenFull — golden file comparison (all options)
  • TestIgnition_ExternalURL_Passthrough — URL mode skips local generation
  • TestIgnition_ExternalURL_MutualExclusion — URL + local → validation error

internal/validate (table-driven)

  • TestValidate_Hostname_Valid / _Empty / _TooLong / _InvalidChars
  • TestValidate_IPv4_ValidCIDR / _InvalidCIDR / _OutOfRange
  • TestValidate_Gateway_ValidIPv4 / _WithCIDR / _OutsideSubnet
  • TestValidate_SSHKey_ValidEd25519 / _ValidRSA / _Empty / _MalformedPrefix
  • TestValidate_DiskPath_ValidByID / _RawDevice / _NonExistent
  • TestValidate_ConfigConsistency_StaticIPRequiresGateway
  • TestValidate_ConfigConsistency_DHCPIgnoresStaticFields

internal/wizard (state machine)

  • TestWizard_InitialState — starts at step 0
  • TestWizard_Next_AdvancesStep / _BlockedByValidation
  • TestWizard_Back_DecrementsStep / _AtFirstStep
  • TestWizard_Summary_AllFields — shows all collected data
  • TestWizard_Confirm_TriggersInstall — invokes install
  • TestWizard_ExternalURL_SkipsConfigSteps
  • TestWizard_StateTransitions_Full — complete happy path

internal/install

  • TestInstall_Execute_HappyPath — correct flatcar-install args
  • TestInstall_Execute_DryRun — logs without execution
  • TestInstall_Execute_DiskByID — uses /dev/disk/by-id/ path
  • TestInstall_Execute_CommandFailure — wraps runner error
  • TestInstall_Execute_MissingBinary — clear error message

cmd/knuckle

  • TestCLI_DryRunFlag_SetsRunner
  • TestCLI_VersionFlag_Prints
  • TestCLI_HelpFlag_ShowsUsage
  • TestCLI_InvalidFlag_Exits1

Test Fixtures (testdata/)

testdata/
├── probe/
│   ├── lsblk_nvme_sata_usb.json       # Mixed disk types
│   ├── lsblk_single_nvme.json         # Single NVMe
│   ├── lsblk_empty.json               # No blockdevices
│   ├── lsblk_with_loops.json          # Loop devices to filter
│   ├── lsblk_malformed.json           # Truncated JSON
│   ├── ip_link_eth_wlan.json          # Multiple interfaces
│   ├── ip_link_single_eth.json        # Single wired NIC
│   └── ip_link_empty.json             # No interfaces
├── bakery/
│   ├── catalog_full.json              # Complete sysext catalog
│   ├── catalog_empty.json             # Empty array
│   └── catalog_malformed.json         # Invalid JSON
├── ignition/golden/
│   ├── minimal.ign                    # Hostname + disk + SSH key
│   ├── full.ign                       # All options
│   ├── static_ip.ign                  # Static network
│   ├── sysexts.ign                    # With extensions
│   └── dhcp.ign                       # DHCP networking
└── install/
    └── flatcar_install_help.txt       # For arg verification

Critical Edge Cases

  • Disk with no serial → by-id path construction fails gracefully
  • SSH key with YAML special chars (#, :) → no YAML injection in Butane
  • IPv6 when only IPv4 expected → wrong address family rejected
  • CIDR /0 or /32 → technically valid but flagged as suspicious
  • Gateway outside subnet → config is syntactically valid but won't route
  • Catalog response >10MB → enforce read limit (DoS protection)
  • Rapid back/next in wizard → no state corruption
  • No disks found → error screen, no crash
  • Sysext name with URL-unsafe chars → URL encoding in download path

Coverage Targets

Package Target
internal/validate ≥80%
internal/ignition ≥80%
internal/probe ≥80%
internal/runner ≥80%
internal/bakery ≥70%
internal/install ≥70%
internal/wizard ≥60%

Implementation Priority

  1. internal/validate — pure functions, highest immediate value
  2. internal/runner — foundation; mock must work for all other tests
  3. internal/probe — fixture-driven, catches parsing regressions
  4. internal/ignition — golden files catch config regressions (machine-bricking risk)
  5. internal/bakery — httptest pattern
  6. internal/install — depends on runner mock
  7. internal/wizard — integration-heavy, depends on all mocks
  8. cmd/knuckle — thin layer, lowest risk

Build Tags & Running

go test ./...                           # Unit tests only (fast)
go test -tags=integration ./...         # Include integration tests
go test ./internal/ignition -update     # Regenerate golden files
go test -race ./...                     # Race detector for wizard state

Acceptance Criteria

  • Every package has _test.go with listed test functions
  • go test ./... passes with zero failures
  • go test -race ./... passes (no data races)
  • Golden files exist and -update flag works
  • Test fixtures committed (not generated at test time)
  • Interfaces defined and used in production code
  • Mock implementations for all interfaces
  • Coverage meets targets above
  • Integration tests gated behind //go:build integration
  • No test depends on network, real disks, or root privileges
  • CI runs go test -race ./... on every push

Metadata

Metadata

Assignees

No one assigned

    Labels

    1-triageNew work awaiting human triage.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions