ananke/internal/state/store_additional_test.go

161 lines
5.3 KiB
Go
Raw Normal View History

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")
}
}