mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 00:14:00 +00:00
fix: rebuild candidate cache from queue authority
This commit is contained in:
@@ -20,6 +20,17 @@ func NewCandidateCache() *CandidateCache {
|
||||
return &CandidateCache{candidates: make(map[string]domain.Candidate)}
|
||||
}
|
||||
|
||||
// RebuildFromQueue is the safe restart/failover path for the cache. Queue
|
||||
// expiry and state filtering happen at the authoritative source before the
|
||||
// cache is atomically replaced; callers never have to reconstruct those
|
||||
// rules from a stale Redis index.
|
||||
func RebuildFromQueue(cache *CandidateCache, queue *domain.Queue, now time.Time) error {
|
||||
if cache == nil || queue == nil || now.IsZero() {
|
||||
return fmt.Errorf("invalid candidate rebuild arguments")
|
||||
}
|
||||
return cache.Rebuild(queue.Candidates(now))
|
||||
}
|
||||
|
||||
func (c *CandidateCache) Upsert(candidate domain.Candidate) error {
|
||||
if candidate.TicketID == "" || candidate.PlayerID == "" || candidate.EnqueuedAt.IsZero() {
|
||||
return fmt.Errorf("invalid candidate")
|
||||
|
||||
@@ -37,3 +37,30 @@ func TestCandidateCacheRejectsInvalidOrDuplicateDurableProjection(t *testing.T)
|
||||
t.Fatal("duplicate candidate accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebuildFromQueueUsesAuthoritativeExpiryAndState(t *testing.T) {
|
||||
now := time.Unix(1000, 0)
|
||||
queue := domain.NewQueue()
|
||||
active := domain.Candidate{TicketID: "ticket-active", PlayerID: "player-active", EnqueuedAt: now}
|
||||
stale := domain.Candidate{TicketID: "ticket-stale", PlayerID: "player-stale", EnqueuedAt: now}
|
||||
if _, err := queue.Create(active.PlayerID, active.TicketID, "create-active-123456", active, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := queue.Create(stale.PlayerID, stale.TicketID, "create-stale-123456", stale, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := queue.Cancel(stale.PlayerID, stale.TicketID, "cancel-stale-123456", 0, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cache := NewCandidateCache()
|
||||
if err := cache.Upsert(domain.Candidate{TicketID: "obsolete", PlayerID: "obsolete", EnqueuedAt: now}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RebuildFromQueue(cache, queue, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := cache.Snapshot(now)
|
||||
if len(got) != 1 || got[0].TicketID != active.TicketID {
|
||||
t.Fatalf("authoritative rebuild = %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user