Task 36c40130-a69a-46ed-a047-2cdc5ee2d2e0 (Feature 23, task 1 of 2). A backfill_dinner_photos job grounds, by name with the existing recipes.Grounder.Ground, every unarchived dinner whose grounding failed and that has no photo and no pasted recipe_url. Ground already tries the ranked candidates best first and keeps the first whose photo caches and whose ingredients normalize, reusing stored recipes and photos. Success records the recipe and photo as grounded; a failure leaves the dinner failed with the new grounding_error for the fix-recipe flow, without stopping its siblings. Writes are guarded on status, photo and grounding_revision, so grounded, manual, pending, renamed and archived dinners are never touched. POST /api/v1/settings/photo-backfill starts a full pass and answers 202 with the job id; the kind is service-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
2.5 KiB
Go
84 lines
2.5 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
|
|
}
|
|
|
|
// StoreOffers stores urls as dinner id's offers, as a Candidates search
|
|
// that read the dinner at grounding_revision revision would on finishing.
|
|
func (s *Service) StoreOffers(id string, urls map[string]string, revision int64) {
|
|
keep, done := s.trackOffers(id)
|
|
defer done()
|
|
keep(urls, revision)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// SafeSourceURL is safeSourceURL.
|
|
var SafeSourceURL = safeSourceURL
|
|
|
|
// RunBackfillJob runs the KindBackfillPhotos handler for p in the caller's
|
|
// goroutine, as the job runner would, and returns its outcomes.
|
|
func (s *Service) RunBackfillJob(ctx context.Context, p BackfillJob) ([]BackfillOutcome, error) {
|
|
r, err := s.runBackfillJob(ctx, p, nil)
|
|
return r.Dinners, err
|
|
}
|