package opstate import ( "sync/atomic" "testing" "time" ) type fakeOp struct { id string done atomic.Bool updatedAt time.Time canceled atomic.Bool } func (f *fakeOp) ID() string { return f.id } func (f *fakeOp) IsDone() bool { return f.done.Load() } func (f *fakeOp) UpdatedAt() time.Time { return f.updatedAt } func (f *fakeOp) Cancel() { f.canceled.Store(true) } func TestRegistryInsertAndGet(t *testing.T) { var r Registry[*fakeOp] op := &fakeOp{id: "op-1", updatedAt: time.Now()} r.Insert(op) got, ok := r.Get("op-1") if !ok { t.Fatal("Get after Insert missed") } if got.ID() != "op-1" { t.Fatalf("Get().ID = %q", got.ID()) } _, ok = r.Get("missing") if ok { t.Fatal("Get on missing key should miss") } } func TestRegistryPruneDropsCompletedOldOps(t *testing.T) { var r Registry[*fakeOp] now := time.Now() recent := &fakeOp{id: "recent", updatedAt: now} recent.done.Store(true) stale := &fakeOp{id: "stale", updatedAt: now.Add(-time.Hour)} stale.done.Store(true) pending := &fakeOp{id: "pending", updatedAt: now.Add(-time.Hour)} // NOT done → stays even though old. r.Insert(recent) r.Insert(stale) r.Insert(pending) cutoff := now.Add(-time.Minute) r.Prune(cutoff) if _, ok := r.Get("stale"); ok { t.Error("stale op should have been pruned") } if _, ok := r.Get("recent"); !ok { t.Error("recent op should survive (newer than cutoff)") } if _, ok := r.Get("pending"); !ok { t.Error("pending op should survive (not done)") } } func TestRegistryListReturnsSnapshot(t *testing.T) { var r Registry[*fakeOp] now := time.Now() a := &fakeOp{id: "a", updatedAt: now} b := &fakeOp{id: "b", updatedAt: now} c := &fakeOp{id: "c", updatedAt: now} c.done.Store(true) r.Insert(a) r.Insert(b) r.Insert(c) got := r.List() if len(got) != 3 { t.Fatalf("List() returned %d entries, want 3", len(got)) } ids := map[string]bool{} for _, op := range got { ids[op.ID()] = true } for _, want := range []string{"a", "b", "c"} { if !ids[want] { t.Errorf("List() missing %q; got %v", want, ids) } } // Mutating the returned slice must not poison the registry. got[0] = &fakeOp{id: "tampered"} if _, ok := r.Get("tampered"); ok { t.Error("List() returned the registry's internal map, not a copy") } } func TestRegistryListEmpty(t *testing.T) { var r Registry[*fakeOp] if got := r.List(); len(got) != 0 { t.Fatalf("List() on empty registry returned %d entries, want 0", len(got)) } } func TestRegistryPruneNoOpOnEmpty(t *testing.T) { var r Registry[*fakeOp] // Just shouldn't panic. r.Prune(time.Now()) }