package checkpoint import ( "context" "testing" "time" "gitea.stevedudenhoeffer.com/steve/executus/run" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" ) func TestHandleSaveCompleteDelete(t *testing.T) { ctx := context.Background() mem := NewMemory() meta := RunCheckpointMeta{RunID: "r1", AgentID: "a1", CallerID: "c1"} cp := New(mem, meta, 0, nil) // throttle 0 = save every call if err := cp.Save(ctx, run.RunCheckpointState{Messages: []llm.Message{{Role: "user"}}, Iteration: 2}); err != nil { t.Fatal(err) } got, _ := mem.Load(ctx, "r1") if got == nil || got.Iteration != 2 || got.Meta.AgentID != "a1" { t.Fatalf("checkpoint not persisted: %+v", got) } if il, _ := mem.ListInterrupted(ctx); len(il) != 1 { t.Errorf("ListInterrupted = %d, want 1 (in-flight)", len(il)) } // Complete clears it (no longer a recovery candidate). if err := cp.Complete(ctx); err != nil { t.Fatal(err) } if il, _ := mem.ListInterrupted(ctx); len(il) != 0 { t.Errorf("after Complete, ListInterrupted = %d, want 0", len(il)) } } func TestHandleThrottle(t *testing.T) { ctx := context.Background() mem := NewMemory() now := time.Now() cp := New(mem, RunCheckpointMeta{RunID: "r"}, time.Minute, func() time.Time { return now }) cp.Save(ctx, run.RunCheckpointState{Iteration: 1}) now = now.Add(10 * time.Second) // within throttle window cp.Save(ctx, run.RunCheckpointState{Iteration: 2}) if got, _ := mem.Load(ctx, "r"); got.Iteration != 1 { t.Errorf("throttled save should keep iteration 1, got %d", got.Iteration) } now = now.Add(time.Minute) // past throttle cp.Save(ctx, run.RunCheckpointState{Iteration: 3}) if got, _ := mem.Load(ctx, "r"); got.Iteration != 3 { t.Errorf("post-throttle save should land iteration 3, got %d", got.Iteration) } } func TestNilStoreNoop(t *testing.T) { cp := New(nil, RunCheckpointMeta{RunID: "r"}, 0, nil) if err := cp.Save(context.Background(), run.RunCheckpointState{}); err != nil { t.Errorf("nil-store Save should be a no-op, got %v", err) } if err := cp.Complete(context.Background()); err != nil { t.Error(err) } }