Nightshift: test-gap analysis
Analysis of test coverage gaps in tailstick, a USB-delivered Tailscale enrollment tool with session/timed leases and autonomous cleanup.
Package coverage summary
| Package |
Source files |
Test files |
Functions |
Tested |
Gap % |
internal/model |
1 (133 lines) |
0 |
0 (types only) |
N/A |
⚪ Type-only |
internal/state |
1 (82 lines) |
0 |
4 |
0 |
🔴 100% |
internal/logging |
1 (63 lines) |
0 |
4 |
0 |
🔴 100% |
internal/platform |
2 (216 lines) |
0 |
9 |
0 |
🔴 100% |
internal/app |
3 (999 lines) |
1 (533 lines) |
~25 |
~15 |
🟡 ~40% |
internal/config |
1 (116 lines) |
1 (38 lines) |
3 |
1 |
🟡 ~33% |
internal/crypto |
1 (128 lines) |
1 (33 lines) |
~5 |
~2 |
🟡 ~60% |
internal/gui |
1 (224 lines) |
1 (132 lines) |
~8 |
2 |
🟡 ~25% |
internal/tailscale |
1 (270 lines) |
2 (145 lines) |
~10 |
~5 |
🟡 ~50% |
Critical gaps (🔴 0% coverage)
internal/state — No tests at all
Severity: HIGH — This is the persistent state layer, the source of truth for lease lifecycle.
Load() — Reads/parses JSON state; returns empty state for missing files. Untested: corrupt JSON handling, schema version defaulting.
Save() — Atomic write with tmp file. Untested: directory creation, permission bits (0600), overwrite behavior.
UpsertRecord() — Core upsert logic. Untested: insert new record, update existing record by LeaseID.
AppendAudit() — Append-only audit log. Untested: directory creation, JSONL format, concurrent writes.
internal/logging — No tests at all
Severity: MEDIUM — Logging is infrastructure, not business logic.
New() — Untested: directory creation, file open behavior.
Close() — Untested: double-close safety, nil file handling.
write() — Untested: output format (RFC3339 + level + message), mutex behavior.
Info() / Error() — Untested: level formatting.
internal/platform — No tests at all
Severity: HIGH — Platform detection affects every operation.
Detect() — Untested on Linux (boot_id read, hostname sanitization).
sanitizeHost() — Untested: special chars, empty input, Unicode, leading/trailing hyphens.
IsElevated() — Untested: Linux euid check.
StatePath() / LogPath() / LocalSecretPath() / AgentBinaryPath() — Untested: Windows vs Linux path selection.
RequireSupportedLinux() — Untested: Ubuntu/Debian detection, unsupported distro rejection.
Runner.Run() — Untested: dry-run mode, empty command, context cancellation.
Runner.RunWithTimeout() — Untested: timeout propagation.
Moderate gaps (🟡 partial coverage)
internal/config — 1 test, 2 functions untested
Severity: MEDIUM
- Only
Load() with env-var resolution is tested.
- Missing:
Load() with missing file, corrupt JSON, authKeyEnv/ephemeralAuthKeyEnv resolution, stableVersionOverride defaults.
- Missing:
resolveEnvVars() edge cases (empty env, nested env references).
internal/crypto — 2 tests, partial coverage
Severity: MEDIUM
Encrypt() / Decrypt() round-trip is likely tested.
- Missing: key derivation edge cases, empty plaintext, large payloads, wrong-key decryption failure.
internal/gui — 2 endpoint tests, ~6 endpoints untested
Severity: MEDIUM
TestPresetsRedactsSecretsAndOnlyAllowsGet — presets endpoint.
TestEnrollRejectsInvalidModeAndNegativeDurations — enroll validation.
- Missing:
DELETE /api/leases/{id}, GET /api/status, GET /api/leases, WebSocket/SSE endpoints (if any), CORS behavior.
internal/tailscale — ~50% coverage
Severity: LOW-MEDIUM
client_test.go tests some API interactions.
context_test.go is minimal (11 lines).
- Missing: error path coverage (network timeouts, malformed JSON from
tailscale status), auth key validation.
internal/app — ~40% coverage (workflow_test.go is the largest test file)
Severity: LOW-MEDIUM
workflow_test.go (533 lines) covers the core workflow engine.
- Missing:
cli.go command parsing tests (196 lines untested), gui.go integration tests.
Recommended test priorities
- P0 —
internal/state: Test UpsertRecord(), Load() (missing file + corrupt JSON), Save() (atomic write). Pure functions, easy to test with t.TempDir().
- P0 —
internal/platform: Test sanitizeHost(), Runner.Run() (dry-run mode), StatePath()/LogPath() paths. Most are pure functions.
- P1 —
internal/config: Test env resolution for all credential fields, missing file behavior.
- P1 —
internal/gui: Test remaining API endpoints, especially DELETE /api/leases/{id}.
- P2 —
internal/logging: Test output format and Close() safety.
- P2 —
internal/crypto: Edge case coverage.
- P3 —
internal/tailscale: Error path coverage.
- P3 —
internal/app/cli.go: Command flag parsing.
Testing patterns observed
- Uses stdlib
testing package (no testify or ginkgo).
- Uses
t.TempDir() for filesystem tests.
- Uses
httptest.NewRequest / httptest.NewRecorder for HTTP tests.
- Good pattern: table-driven tests with named sub-tests (see
gui/server_test.go).
- Good pattern: function injection for testability (see
Server.EnrollFn).
Estimated effort
| Package |
Tests to add |
Estimated LOC |
Difficulty |
state |
6-8 |
120-160 |
Easy |
platform |
8-10 |
150-200 |
Easy |
config |
3-4 |
60-80 |
Easy |
logging |
3-4 |
60-80 |
Easy |
gui |
4-6 |
100-150 |
Medium |
| Total |
24-32 |
490-670 |
— |
Generated by nightshift — test-gap task.
Nightshift: test-gap analysis
Analysis of test coverage gaps in tailstick, a USB-delivered Tailscale enrollment tool with session/timed leases and autonomous cleanup.
Package coverage summary
internal/modelinternal/stateinternal/logginginternal/platforminternal/appinternal/configinternal/cryptointernal/guiinternal/tailscaleCritical gaps (🔴 0% coverage)
internal/state— No tests at allSeverity: HIGH — This is the persistent state layer, the source of truth for lease lifecycle.
Load()— Reads/parses JSON state; returns empty state for missing files. Untested: corrupt JSON handling, schema version defaulting.Save()— Atomic write with tmp file. Untested: directory creation, permission bits (0600), overwrite behavior.UpsertRecord()— Core upsert logic. Untested: insert new record, update existing record by LeaseID.AppendAudit()— Append-only audit log. Untested: directory creation, JSONL format, concurrent writes.internal/logging— No tests at allSeverity: MEDIUM — Logging is infrastructure, not business logic.
New()— Untested: directory creation, file open behavior.Close()— Untested: double-close safety, nil file handling.write()— Untested: output format (RFC3339 + level + message), mutex behavior.Info()/Error()— Untested: level formatting.internal/platform— No tests at allSeverity: HIGH — Platform detection affects every operation.
Detect()— Untested on Linux (boot_id read, hostname sanitization).sanitizeHost()— Untested: special chars, empty input, Unicode, leading/trailing hyphens.IsElevated()— Untested: Linux euid check.StatePath()/LogPath()/LocalSecretPath()/AgentBinaryPath()— Untested: Windows vs Linux path selection.RequireSupportedLinux()— Untested: Ubuntu/Debian detection, unsupported distro rejection.Runner.Run()— Untested: dry-run mode, empty command, context cancellation.Runner.RunWithTimeout()— Untested: timeout propagation.Moderate gaps (🟡 partial coverage)
internal/config— 1 test, 2 functions untestedSeverity: MEDIUM
Load()with env-var resolution is tested.Load()with missing file, corrupt JSON,authKeyEnv/ephemeralAuthKeyEnvresolution,stableVersionOverridedefaults.resolveEnvVars()edge cases (empty env, nested env references).internal/crypto— 2 tests, partial coverageSeverity: MEDIUM
Encrypt()/Decrypt()round-trip is likely tested.internal/gui— 2 endpoint tests, ~6 endpoints untestedSeverity: MEDIUM
TestPresetsRedactsSecretsAndOnlyAllowsGet— presets endpoint.TestEnrollRejectsInvalidModeAndNegativeDurations— enroll validation.DELETE /api/leases/{id},GET /api/status,GET /api/leases, WebSocket/SSE endpoints (if any), CORS behavior.internal/tailscale— ~50% coverageSeverity: LOW-MEDIUM
client_test.gotests some API interactions.context_test.gois minimal (11 lines).tailscale status), auth key validation.internal/app— ~40% coverage (workflow_test.go is the largest test file)Severity: LOW-MEDIUM
workflow_test.go(533 lines) covers the core workflow engine.cli.gocommand parsing tests (196 lines untested),gui.gointegration tests.Recommended test priorities
internal/state: TestUpsertRecord(),Load()(missing file + corrupt JSON),Save()(atomic write). Pure functions, easy to test witht.TempDir().internal/platform: TestsanitizeHost(),Runner.Run()(dry-run mode),StatePath()/LogPath()paths. Most are pure functions.internal/config: Test env resolution for all credential fields, missing file behavior.internal/gui: Test remaining API endpoints, especiallyDELETE /api/leases/{id}.internal/logging: Test output format and Close() safety.internal/crypto: Edge case coverage.internal/tailscale: Error path coverage.internal/app/cli.go: Command flag parsing.Testing patterns observed
testingpackage (no testify or ginkgo).t.TempDir()for filesystem tests.httptest.NewRequest/httptest.NewRecorderfor HTTP tests.gui/server_test.go).Server.EnrollFn).Estimated effort
stateplatformconfigloggingguiGenerated by nightshift — test-gap task.