142 lines
5.0 KiB
Go
142 lines
5.0 KiB
Go
package statequality
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"scm.bstein.dev/bstein/ananke/internal/state"
|
|
)
|
|
|
|
// TestStateEnsureDirAndWriteIntentBranchCoverage runs one orchestration or CLI step.
|
|
// Signature: TestStateEnsureDirAndWriteIntentBranchCoverage(t *testing.T).
|
|
// Why: closes remaining success-branch gaps in EnsureDir and WriteIntent timestamp preservation.
|
|
func TestStateEnsureDirAndWriteIntentBranchCoverage(t *testing.T) {
|
|
root := t.TempDir()
|
|
dir := filepath.Join(root, "state")
|
|
if err := state.EnsureDir(dir); err != nil {
|
|
t.Fatalf("EnsureDir success path failed: %v", err)
|
|
}
|
|
if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
|
|
t.Fatalf("expected ensured state dir to exist, stat err=%v", err)
|
|
}
|
|
|
|
intentPath := filepath.Join(dir, "intent.json")
|
|
ts := time.Now().UTC().Add(-time.Minute).Truncate(time.Second)
|
|
if err := state.WriteIntent(intentPath, state.Intent{
|
|
State: state.IntentNormal,
|
|
Reason: "preserve-updated-at",
|
|
Source: "test",
|
|
UpdatedAt: ts,
|
|
}); err != nil {
|
|
t.Fatalf("WriteIntent with explicit UpdatedAt failed: %v", err)
|
|
}
|
|
got, err := state.ReadIntent(intentPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadIntent after explicit UpdatedAt write failed: %v", err)
|
|
}
|
|
if !got.UpdatedAt.Equal(ts) {
|
|
t.Fatalf("expected UpdatedAt to remain unchanged; got=%s want=%s", got.UpdatedAt, ts)
|
|
}
|
|
}
|
|
|
|
// TestStateStoreLockAndLoadBranchCoverage runs one orchestration or CLI step.
|
|
// Signature: TestStateStoreLockAndLoadBranchCoverage(t *testing.T).
|
|
// Why: closes remaining lock/load branch gaps used by daemon and CLI quality gates.
|
|
func TestStateStoreLockAndLoadBranchCoverage(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
lockPath := filepath.Join(root, "ananke.lock")
|
|
unlock, err := state.AcquireLock(lockPath)
|
|
if err != nil {
|
|
t.Fatalf("AcquireLock fresh lock failed: %v", err)
|
|
}
|
|
unlock()
|
|
|
|
storePath := filepath.Join(root, "runs.json")
|
|
if err := os.WriteFile(storePath, []byte{}, 0o640); err != nil {
|
|
t.Fatalf("write empty store file: %v", err)
|
|
}
|
|
recs, err := state.New(storePath).Load()
|
|
if err != nil {
|
|
t.Fatalf("Load empty store file failed: %v", err)
|
|
}
|
|
if len(recs) != 0 {
|
|
t.Fatalf("expected zero records from empty store file, got %d", len(recs))
|
|
}
|
|
}
|
|
|
|
// TestStateIntentParserAdditionalEdges runs one orchestration or CLI step.
|
|
// Signature: TestStateIntentParserAdditionalEdges(t *testing.T).
|
|
// Why: covers parser edge branches where source and updated_at tokens are absent/blank.
|
|
func TestStateIntentParserAdditionalEdges(t *testing.T) {
|
|
afterBlank, err := state.ParseIntentOutput("\nintent=normal source=peer")
|
|
if err != nil {
|
|
t.Fatalf("ParseIntentOutput with leading blank line failed: %v", err)
|
|
}
|
|
if afterBlank.State != state.IntentNormal {
|
|
t.Fatalf("unexpected parsed state after blank line: %+v", afterBlank)
|
|
}
|
|
|
|
in, err := state.ParseIntentOutput("intent=normal source=peer updated_at=")
|
|
if err != nil {
|
|
t.Fatalf("ParseIntentOutput with blank updated_at failed: %v", err)
|
|
}
|
|
if in.State != state.IntentNormal || in.Source != "peer" {
|
|
t.Fatalf("unexpected parsed intent payload: %+v", in)
|
|
}
|
|
|
|
withPrefix, err := state.ParseIntentOutput("status=ok intent=shutdown_complete reason=\"done\" source=daemon")
|
|
if err != nil {
|
|
t.Fatalf("ParseIntentOutput with prefixed tokens failed: %v", err)
|
|
}
|
|
if withPrefix.State != state.IntentShutdownComplete || withPrefix.Reason != "done" || withPrefix.Source != "daemon" {
|
|
t.Fatalf("unexpected prefixed parsed intent payload: %+v", withPrefix)
|
|
}
|
|
}
|
|
|
|
// TestStateIntentAutoHealFailureBranch runs one orchestration or CLI step.
|
|
// Signature: TestStateIntentAutoHealFailureBranch(t *testing.T).
|
|
// Why: covers ReadIntent branch where corrupt file quarantine fails and error details are returned.
|
|
func TestStateIntentAutoHealFailureBranch(t *testing.T) {
|
|
root := t.TempDir()
|
|
dir := filepath.Join(root, "ro")
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
t.Fatalf("mkdir readonly dir: %v", err)
|
|
}
|
|
path := filepath.Join(dir, "intent.json")
|
|
if err := os.WriteFile(path, []byte("{bad-json"), 0o600); err != nil {
|
|
t.Fatalf("write malformed intent file: %v", err)
|
|
}
|
|
if err := os.Chmod(dir, 0o500); err != nil {
|
|
t.Fatalf("chmod readonly dir: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = os.Chmod(dir, 0o700) })
|
|
|
|
_, err := state.ReadIntent(path)
|
|
if err == nil {
|
|
t.Fatalf("expected ReadIntent auto-heal failure for readonly dir")
|
|
}
|
|
if !strings.Contains(err.Error(), "auto-heal failed") {
|
|
t.Fatalf("expected auto-heal failure context, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestStateAcquireLockReclaimsNoPidLock runs one orchestration or CLI step.
|
|
// Signature: TestStateAcquireLockReclaimsNoPidLock(t *testing.T).
|
|
// Why: covers stale-lock parsing branch where lock file contains no pid line.
|
|
func TestStateAcquireLockReclaimsNoPidLock(t *testing.T) {
|
|
root := t.TempDir()
|
|
lockPath := filepath.Join(root, "ananke.lock")
|
|
if err := os.WriteFile(lockPath, []byte("started=2020-01-01T00:00:00Z\n"), 0o600); err != nil {
|
|
t.Fatalf("write no-pid lock: %v", err)
|
|
}
|
|
unlock, err := state.AcquireLock(lockPath)
|
|
if err != nil {
|
|
t.Fatalf("expected lock reclaim for no-pid lock, got %v", err)
|
|
}
|
|
unlock()
|
|
}
|