Fix offline onboarding lockout and stale candidates after rename
MQ-98-04: the household settings read that OnboardingGate makes before every page now runs with networkMode 'always', like /me. After the browser emits an offline event it fails into the saved pages instead of pausing on the loading screen. A test emits a real offline event before /me fails. MQ-98-05: renaming a dinner now clears its candidate offers and makes a running candidate search stale, and an offer is bound to the normalized name it was searched for, so a candidate offered before a rename cannot ground the renamed dinner. Regression tests cover a pending dinner renamed after candidates were offered, and a rename during a running search. Maestro task: 4fd737b2-fdcb-48ad-858a-ecdd95595e0c Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -804,8 +804,8 @@ func wantStaleCandidate(t *testing.T, e *env, id, cid string) {
|
||||
}
|
||||
|
||||
// TestGroundingOrArchivingClearsCandidates: once a dinner is grounded by
|
||||
// hand — by candidate, link or manual recipe — or archived, the service
|
||||
// holds none of its candidates' URLs and none of them grounds it.
|
||||
// hand — by candidate, link or manual recipe — renamed or archived, the
|
||||
// service holds none of its candidates' URLs and none of them grounds it.
|
||||
func TestGroundingOrArchivingClearsCandidates(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
@@ -834,6 +834,11 @@ func TestGroundingOrArchivingClearsCandidates(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}, false},
|
||||
{"rename", func(t *testing.T, e *env, id, _ string) {
|
||||
if _, err := e.svc.Update(t.Context(), id, dinners.Patch{Name: ptr("Beef stew")}, member); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}, false},
|
||||
{"archive", func(t *testing.T, e *env, id, _ string) {
|
||||
if _, err := e.svc.Archive(t.Context(), id, member); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -860,9 +865,56 @@ func TestGroundingOrArchivingClearsCandidates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenamePendingRefusesOldCandidates: candidates offered for a pending
|
||||
// dinner's old name no longer ground it once it is renamed — Ground refuses
|
||||
// them and leaves the old recipe off it — while candidates for the new name
|
||||
// do.
|
||||
func TestRenamePendingRefusesOldCandidates(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Unstarted, so the dinner stays pending with its jobs queued.
|
||||
e := newUnstartedEnv(t)
|
||||
e.search.Results["Beef stew"] = []recipes.Candidate{{URL: stew}}
|
||||
c, err := e.svc.CreateRegular(t.Context(), dinners.NewRegular{Name: ptr("Chicken curry")}, member)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old := candidateID(t, e, c.ID, "Chicken Curry")
|
||||
|
||||
d, err := e.svc.Update(t.Context(), c.ID, dinners.Patch{Name: ptr("Beef stew")}, member)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.GroundingStatus != dinners.GroundingPending {
|
||||
t.Fatalf("renamed = %+v, want still pending", d)
|
||||
}
|
||||
if e.svc.HasOffers(c.ID) {
|
||||
t.Errorf("service still holds the old name's candidates of dinner %s", c.ID)
|
||||
}
|
||||
wantStaleCandidate(t, e, c.ID, old)
|
||||
if row := e.dinner(t, c.ID); row.RecipeID.Valid || row.GroundingStatus != dinners.GroundingPending {
|
||||
t.Fatalf("dinner = %+v, want pending with no recipe", row)
|
||||
}
|
||||
|
||||
e.model.QueueGenerate(recipestest.NormalizedReply("oven", 150))
|
||||
g, err := e.svc.Ground(t.Context(), c.ID, dinners.Fix{CandidateID: ptr(candidateID(t, e, c.ID, "Beef Stew"))}, member)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g.GroundingStatus != dinners.GroundingGrounded || g.RecipeID == nil {
|
||||
t.Fatalf("dinner = %+v, want grounded for the new name", g)
|
||||
}
|
||||
rec, err := db.New(e.st.Reader).GetRecipe(t.Context(), *g.RecipeID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rec.SourceUrl != stew {
|
||||
t.Errorf("recipe source = %s, want %s", rec.SourceUrl, stew)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCandidatesRunningWhenCleared: a candidate search still running when
|
||||
// its dinner is grounded by hand or archived stores none of the candidates
|
||||
// it returns, so none of them grounds the dinner.
|
||||
// its dinner is grounded by hand, renamed or archived stores none of the
|
||||
// candidates it returns, so none of them grounds the dinner.
|
||||
func TestCandidatesRunningWhenCleared(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
@@ -877,6 +929,11 @@ func TestCandidatesRunningWhenCleared(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}, false},
|
||||
{"rename", func(t *testing.T, e *env, id string) {
|
||||
if _, err := e.svc.Update(t.Context(), id, dinners.Patch{Name: ptr("Beef stew")}, member); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}, false},
|
||||
{"archive", func(t *testing.T, e *env, id string) {
|
||||
if _, err := e.svc.Archive(t.Context(), id, member); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
Reference in New Issue
Block a user