MQ-103-01: offers were bound only to the normalized name and cleared
only when it changed, so "Chicken curry" -> "Quick Chicken curry" kept
the old candidates live, and a search finishing after renames A -> B -> A
committed (before either cleared offers) stored candidates that matched
A again.
Every actual name edit now raises grounding_revision and clears the
dinner's offers. Each offer set records the revision its search read,
and a candidate grounds the dinner only while the dinner is still at
that revision; GroundDinner already requires the observed revision, so
a rename between the check and the write is a conflict. A pending
dinner renamed keeps getting a job at its new revision; recipe_url is
still cleared only when the normalized name changes.
Tests: a same-normalized-name rename refuses the old candidate and
attaches no recipe; an in-flight search finishing between the A -> B -> A
commits and their offer clearing stores candidates that are refused.
Both fail against 3c0a7fd.
Maestro task: 4fd737b2-fdcb-48ad-858a-ecdd95595e0c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package dinners
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// SetGroundTimeout sets the deadline of grounding by hand until t ends. A
|
|
// test calling it must not run in parallel.
|
|
func SetGroundTimeout(t *testing.T, d time.Duration) {
|
|
old := groundTimeout
|
|
groundTimeout = d
|
|
t.Cleanup(func() { groundTimeout = old })
|
|
}
|
|
|
|
// SetOfferLifetime sets how long candidates ground a dinner until t ends.
|
|
// A test calling it must not run in parallel.
|
|
func SetOfferLifetime(t *testing.T, d time.Duration) {
|
|
old := offerLifetime
|
|
offerLifetime = d
|
|
t.Cleanup(func() { offerLifetime = old })
|
|
}
|
|
|
|
// SetUpdateCommitted runs f in Update once a rename is committed, before
|
|
// the dinner's offers are cleared, until t ends. A test calling it must
|
|
// not run in parallel.
|
|
func SetUpdateCommitted(t *testing.T, f func()) {
|
|
old := updateCommitted
|
|
updateCommitted = f
|
|
t.Cleanup(func() { updateCommitted = old })
|
|
}
|
|
|
|
// AdvanceClock moves the service's clock d ahead of real time until t
|
|
// ends, without firing any timer. A test calling it must not run in
|
|
// parallel.
|
|
func AdvanceClock(t *testing.T, d time.Duration) {
|
|
old := now
|
|
now = func() time.Time { return old().Add(d) }
|
|
t.Cleanup(func() { now = old })
|
|
}
|
|
|
|
// HasOffers reports whether the service holds candidates offered for
|
|
// dinner id.
|
|
func (s *Service) HasOffers(id string) bool {
|
|
s.offersMu.Lock()
|
|
defer s.offersMu.Unlock()
|
|
_, ok := s.offers[id]
|
|
return ok
|
|
}
|
|
|
|
// OfferCount is the number of dinners whose candidates the service holds.
|
|
func (s *Service) OfferCount() int {
|
|
s.offersMu.Lock()
|
|
defer s.offersMu.Unlock()
|
|
return len(s.offers)
|
|
}
|
|
|
|
// RunGroundJob runs the KindGroundDinner handler for p in the caller's
|
|
// goroutine, as the job runner would, and returns the grounding status it
|
|
// reports.
|
|
func (s *Service) RunGroundJob(ctx context.Context, p GroundJob) (string, error) {
|
|
r, err := s.runGroundJob(ctx, p, nil)
|
|
return r.GroundingStatus, err
|
|
}
|