package store import ( "sync" "testing" "time" "git.unkin.net/unkin/repospawner/internal/repospec" ) func fixedClock(base time.Time) (*Store, func(time.Duration)) { s := New() now := base s.SetClock(func() time.Time { return now }) return s, func(d time.Duration) { now = now.Add(d) } } func TestListNewestFirst(t *testing.T) { s, advance := fixedClock(time.Date(2026, 8, 30, 0, 0, 0, 0, time.UTC)) for _, name := range []string{"first", "second", "third"} { s.Put(NewRequest(name, repospec.Request{Name: name}, s.Now())) advance(time.Minute) } got := s.List() if len(got) != 3 { t.Fatalf("List returned %d requests", len(got)) } for i, want := range []string{"third", "second", "first"} { if got[i].Name != want { t.Errorf("List()[%d] = %q, want %q", i, got[i].Name, want) } } } func TestPutOnlyStampsUpdatedOnChange(t *testing.T) { s, advance := fixedClock(time.Date(2026, 8, 30, 0, 0, 0, 0, time.UTC)) r := NewRequest("id", repospec.Request{Name: "widget", StatusChecks: []string{"a"}}, s.Now()) s.Put(r) stored, _ := s.Get("id") advance(time.Hour) s.Put(r) again, _ := s.Get("id") if !again.Updated.Equal(stored.Updated) { t.Errorf("Updated moved on an unchanged Put: %v -> %v", stored.Updated, again.Updated) } r.State = StatePROpen s.Put(r) changed, _ := s.Get("id") if !changed.Updated.After(stored.Updated) { t.Errorf("Updated did not move on a real change: %v", changed.Updated) } if !changed.Created.Equal(stored.Created) { t.Errorf("Created must not move: %v -> %v", stored.Created, changed.Created) } } func TestHasActiveName(t *testing.T) { s := New() r := NewRequest("id", repospec.Request{Name: "widget"}, time.Now()) s.Put(r) if !s.HasActiveName("widget") { t.Error("an in-flight request must claim its name") } if s.HasActiveName("other") { t.Error("an unrelated name must be free") } for _, state := range []State{StateReady, StateClosed, StateFailed} { r.State = state s.Put(r) if s.HasActiveName("widget") { t.Errorf("state %q is terminal and must release the name", state) } } } func TestNewIDIsUnique(t *testing.T) { seen := map[string]bool{} for i := 0; i < 1000; i++ { id := NewID() if id == "" { t.Fatal("NewID returned an empty id") } if seen[id] { t.Fatalf("NewID repeated %q", id) } seen[id] = true } } func TestConcurrentAccess(t *testing.T) { s := New() var wg sync.WaitGroup for i := 0; i < 32; i++ { wg.Add(1) go func(i int) { defer wg.Done() id := NewID() s.Put(NewRequest(id, repospec.Request{Name: "widget"}, time.Now())) s.Get(id) s.List() s.HasActiveName("widget") }(i) } wg.Wait() if len(s.List()) != 32 { t.Errorf("List returned %d requests, want 32", len(s.List())) } }