Files
MealQuest/internal/dinners/export_test.go
T
dev_agentandClaude Opus 5.5 ec718024a1
CI / ci (pull_request) Failing after 10m12s
CI / ci (push) Successful in 11m35s
Add photo backfill for failed dinners without a photo
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>
2026-10-11 20:53:58 -04:00

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
}