package statequality import ( "fmt" "os" "path/filepath" "strings" "testing" "time" "scm.bstein.dev/bstein/ananke/internal/state" ) // TestEnsureDirAndIntentIOContracts runs one orchestration or CLI step. // Signature: TestEnsureDirAndIntentIOContracts(t *testing.T). // Why: exercises directory and intent IO edges that appear during interrupted drills. func TestEnsureDirAndIntentIOContracts(t *testing.T) { if err := state.EnsureDir(""); err == nil { t.Fatalf("expected empty dir rejection") } root := t.TempDir() intentPath := filepath.Join(root, "intent", "intent.json") in := state.Intent{ State: state.IntentStartupInProgress, Reason: "drill", Source: "test", } if err := state.WriteIntent(intentPath, in); err != nil { t.Fatalf("write intent: %v", err) } got, err := state.ReadIntent(intentPath) if err != nil { t.Fatalf("read intent: %v", err) } if got.State != in.State || got.Reason != in.Reason || got.Source != in.Source { t.Fatalf("unexpected intent readback: %+v", got) } if got.UpdatedAt.IsZero() { t.Fatalf("expected UpdatedAt to be auto-filled") } missingIntent, err := state.ReadIntent(filepath.Join(root, "missing.json")) if err != nil { t.Fatalf("read missing intent: %v", err) } if missingIntent != (state.Intent{}) { t.Fatalf("expected zero intent for missing file, got %+v", missingIntent) } emptyPath := filepath.Join(root, "empty-intent.json") if err := os.WriteFile(emptyPath, nil, 0o644); err != nil { t.Fatalf("write empty intent: %v", err) } emptyIntent, err := state.ReadIntent(emptyPath) if err != nil { t.Fatalf("read empty intent: %v", err) } if emptyIntent != (state.Intent{}) { t.Fatalf("expected zero intent for empty file, got %+v", emptyIntent) } } // TestIntentReadAutoHealAndParseIntentOutput runs one orchestration or CLI step. // Signature: TestIntentReadAutoHealAndParseIntentOutput(t *testing.T). // Why: validates corruption auto-heal behavior and CLI intent output parser edge handling. func TestIntentReadAutoHealAndParseIntentOutput(t *testing.T) { root := t.TempDir() intentPath := filepath.Join(root, "intent.json") if err := os.WriteFile(intentPath, []byte(`{"state":"broken"`), 0o644); err != nil { t.Fatalf("write malformed intent: %v", err) } parsed, err := state.ReadIntent(intentPath) if err != nil { t.Fatalf("read malformed intent should auto-heal: %v", err) } if parsed != (state.Intent{}) { t.Fatalf("expected healed malformed intent to return zero value, got %+v", parsed) } corrupt, err := filepath.Glob(filepath.Join(root, "intent.json.corrupt-*")) if err != nil { t.Fatalf("glob corrupt files: %v", err) } if len(corrupt) == 0 { t.Fatalf("expected a quarantined corrupt intent artifact") } if _, err := state.ParseIntentOutput("status: ok"); err == nil { t.Fatalf("expected parse error when intent token is missing") } if in, err := state.ParseIntentOutput(`intent=none reason="ignored"`); err != nil || in != (state.Intent{}) { t.Fatalf("expected none intent to parse as zero value, got %+v err=%v", in, err) } _, err = state.ParseIntentOutput(`intent=normal updated_at=not-a-timestamp`) if err == nil { t.Fatalf("expected invalid updated_at parse failure") } } // TestLockingAndStoreHistoryContracts runs one orchestration or CLI step. // Signature: TestLockingAndStoreHistoryContracts(t *testing.T). // Why: covers lock lifecycle, stale lock recovery, history trimming, and P95 calculations. func TestLockingAndStoreHistoryContracts(t *testing.T) { root := t.TempDir() lockPath := filepath.Join(root, "ananke.lock") // Active lock should reject acquisition. active := "pid=" + strconvI(os.Getpid()) + " started=" + time.Now().UTC().Format(time.RFC3339) + "\n" if err := os.WriteFile(lockPath, []byte(active), 0o600); err != nil { t.Fatalf("write active lock: %v", err) } if _, err := state.AcquireLock(lockPath); err == nil { t.Fatalf("expected active lock rejection") } // Malformed/stale lock should be reclaimed. if err := os.WriteFile(lockPath, []byte("pid=not-a-number\n"), 0o600); err != nil { t.Fatalf("write malformed lock: %v", err) } unlock, err := state.AcquireLock(lockPath) if err != nil { t.Fatalf("acquire reclaimed lock: %v", err) } unlock() if _, err := os.Stat(lockPath); !os.IsNotExist(err) { t.Fatalf("expected lock file cleanup after unlock, stat err=%v", err) } storePath := filepath.Join(root, "runs.json") st := state.New(storePath) for i := 0; i < 205; i++ { rec := state.RunRecord{ ID: "run-" + strconvI(i), Action: "shutdown", StartedAt: time.Now().UTC().Add(-time.Duration(i+1) * time.Second), EndedAt: time.Now().UTC(), DurationSeconds: i + 1, Success: true, } if err := st.Append(rec); err != nil { t.Fatalf("append record %d: %v", i, err) } } records, err := st.Load() if err != nil { t.Fatalf("load records: %v", err) } if len(records) != 200 { t.Fatalf("expected history trim to 200 records, got %d", len(records)) } if got := st.ShutdownP95(30); got <= 30 { t.Fatalf("expected computed p95 above default, got %d", got) } if got := st.ShutdownP95ByReasonPrefix(77, 3, []string{"missing-prefix"}); got != 77 { t.Fatalf("expected default fallback when no reason matches, got %d", got) } // Corrupt run history should be auto-healed to an empty list. if err := os.WriteFile(storePath, []byte("{bad-json"), 0o640); err != nil { t.Fatalf("write corrupt history: %v", err) } recovered, err := st.Load() if err != nil { t.Fatalf("load corrupt history should auto-heal: %v", err) } if len(recovered) != 0 { t.Fatalf("expected healed corrupt history to return empty list, got %d", len(recovered)) } // Directory path triggers read error; shutdown estimator should fall back to default. dirAsFile := filepath.Join(root, "history-dir") if err := os.MkdirAll(dirAsFile, 0o755); err != nil { t.Fatalf("mkdir history-dir: %v", err) } brokenStore := state.New(dirAsFile) if got := brokenStore.ShutdownP95(123); got != 123 { t.Fatalf("expected default on read error, got %d", got) } } // TestMustWriteIntentValidation runs one orchestration or CLI step. // Signature: TestMustWriteIntentValidation(t *testing.T). // Why: covers strict intent-state validation guarantees for operator CLI writes. func TestMustWriteIntentValidation(t *testing.T) { path := filepath.Join(t.TempDir(), "intent.json") if err := state.MustWriteIntent(path, state.IntentShutdownComplete, "done", "test"); err != nil { t.Fatalf("expected valid intent write, got %v", err) } err := state.MustWriteIntent(path, "unknown-state", "nope", "test") if err == nil || !strings.Contains(err.Error(), "invalid intent state") { t.Fatalf("expected invalid intent state error, got %v", err) } } // strconvI runs one orchestration or CLI step. // Signature: strconvI(v int) string. // Why: avoids importing strconv repeatedly in compact coverage-focused tests. func strconvI(v int) string { return fmt.Sprintf("%d", v) }