package state import ( "encoding/json" "os" "path/filepath" "strconv" "testing" "time" ) // TestEnsureDirRejectsEmpty runs one orchestration or CLI step. // Signature: TestEnsureDirRejectsEmpty(t *testing.T). // Why: covers explicit guard branch for empty state directory inputs. func TestEnsureDirRejectsEmpty(t *testing.T) { if err := EnsureDir(""); err == nil { t.Fatalf("expected empty directory error") } } // TestStoreAppendTrimToMaxRecords runs one orchestration or CLI step. // Signature: TestStoreAppendTrimToMaxRecords(t *testing.T). // Why: covers retention branch that trims run history to the 200-record cap. func TestStoreAppendTrimToMaxRecords(t *testing.T) { path := filepath.Join(t.TempDir(), "runs.json") s := New(path) now := time.Now().UTC() for i := 0; i < 205; i++ { if err := s.Append(RunRecord{ ID: "r-" + strconv.Itoa(i), Action: "shutdown", StartedAt: now, EndedAt: now, DurationSeconds: i + 1, Success: true, }); err != nil { t.Fatalf("append %d failed: %v", i, err) } } recs, err := s.Load() if err != nil { t.Fatalf("load failed: %v", err) } if len(recs) != 200 { t.Fatalf("expected trim to 200 records, got %d", len(recs)) } } // TestStoreLoadHandlesEmptyFile runs one orchestration or CLI step. // Signature: TestStoreLoadHandlesEmptyFile(t *testing.T). // Why: covers load branch for empty existing run-history file. func TestStoreLoadHandlesEmptyFile(t *testing.T) { path := filepath.Join(t.TempDir(), "runs.json") if err := os.WriteFile(path, nil, 0o640); err != nil { t.Fatalf("write empty file: %v", err) } recs, err := New(path).Load() if err != nil { t.Fatalf("load empty file: %v", err) } if len(recs) != 0 { t.Fatalf("expected no records, got %d", len(recs)) } } // TestStoreLoadReturnsErrorOnUnhealableDecode runs one orchestration or CLI step. // Signature: TestStoreLoadReturnsErrorOnUnhealableDecode(t *testing.T). // Why: covers decode failure path where replacement write itself can fail. func TestStoreLoadReturnsErrorOnUnhealableDecode(t *testing.T) { if os.Geteuid() == 0 { t.Skip("permission semantics differ under root") } dir := t.TempDir() path := filepath.Join(dir, "runs.json") if err := os.WriteFile(path, []byte("{bad-json"), 0o640); err != nil { t.Fatalf("write invalid file: %v", err) } // Make directory readonly so quarantine replacement cannot be written. if err := os.Chmod(dir, 0o500); err != nil { t.Fatalf("chmod dir readonly: %v", err) } defer os.Chmod(dir, 0o700) if _, err := New(path).Load(); err == nil { t.Fatalf("expected load failure when auto-heal cannot write replacement") } } // TestShutdownP95FallsBackOnLoadError runs one orchestration or CLI step. // Signature: TestShutdownP95FallsBackOnLoadError(t *testing.T). // Why: covers load-error fallback branch in percentile helper. func TestShutdownP95FallsBackOnLoadError(t *testing.T) { path := filepath.Join(t.TempDir(), "runs.json") if err := os.WriteFile(path, []byte("{bad"), 0o640); err != nil { t.Fatalf("write invalid file: %v", err) } // Use impossible perms to force read failure. if err := os.Chmod(path, 0o000); err != nil { t.Fatalf("chmod file: %v", err) } defer os.Chmod(path, 0o640) if got := New(path).ShutdownP95(321); got != 321 { t.Fatalf("expected fallback default 321, got %d", got) } } // TestShutdownP95ReturnsDefaultOnNonPositiveQuantile runs one orchestration or CLI step. // Signature: TestShutdownP95ReturnsDefaultOnNonPositiveQuantile(t *testing.T). // Why: covers branch where computed percentile record is non-positive. func TestShutdownP95ReturnsDefaultOnNonPositiveQuantile(t *testing.T) { path := filepath.Join(t.TempDir(), "runs.json") now := time.Now().UTC() records := []RunRecord{ {Action: "shutdown", StartedAt: now, EndedAt: now, DurationSeconds: 0, Success: true}, {Action: "shutdown", StartedAt: now, EndedAt: now, DurationSeconds: -1, Success: true}, } b, err := json.Marshal(records) if err != nil { t.Fatalf("marshal records: %v", err) } if err := os.WriteFile(path, b, 0o640); err != nil { t.Fatalf("write records: %v", err) } if got := New(path).ShutdownP95WithMinSamples(777, 1); got != 777 { t.Fatalf("expected default 777, got %d", got) } } // TestStaleLockHelpers runs one orchestration or CLI step. // Signature: TestStaleLockHelpers(t *testing.T). // Why: covers stale-lock parser branches directly for reliability. func TestStaleLockHelpers(t *testing.T) { tmp := t.TempDir() missing := filepath.Join(tmp, "missing.lock") stale, err := staleLock(missing) if err != nil || !stale { t.Fatalf("expected missing lock to be stale=true err=nil, got stale=%v err=%v", stale, err) } invalidPID := filepath.Join(tmp, "invalid.lock") if err := os.WriteFile(invalidPID, []byte("pid=notanumber\n"), 0o600); err != nil { t.Fatalf("write invalid pid lock: %v", err) } stale, err = staleLock(invalidPID) if err != nil || !stale { t.Fatalf("expected invalid pid lock to be stale=true err=nil, got stale=%v err=%v", stale, err) } active := filepath.Join(tmp, "active.lock") if err := os.WriteFile(active, []byte("pid="+strconv.Itoa(os.Getpid())+"\n"), 0o600); err != nil { t.Fatalf("write active lock: %v", err) } stale, err = staleLock(active) if err != nil { t.Fatalf("active staleLock error: %v", err) } if stale { t.Fatalf("expected active lock to report stale=false") } }