Fix stale Regular grounding and enforce three Regulars for onboarding
A background grounding result now applies only while the dinner is still pending under the normalized name the job searched: GroundPendingDinner and FailPendingDinnerGrounding match normalized_name, so a rename makes the old job's success or failure a no-op. Renaming a dinner still pending enqueues a fresh grounding job for the new name; if that cannot be enqueued, the dinner's grounding is failed so it is not left pending with no job. Completing onboarding (PATCH /api/v1/settings/household with onboarding_completed true) is refused with 400 invalid_settings until the household has at least three Regulars (status regular; archived and Quest log dinners do not count), and onboarding_completed_at is set only then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,13 @@ package household
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/db"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/migrations"
|
||||
)
|
||||
@@ -34,12 +37,34 @@ func migratedStore(t *testing.T, dir string) *store.Store {
|
||||
// seededService returns a settings service over a migrated temp database
|
||||
// seeded with zone America/New_York.
|
||||
func seededService(t *testing.T) *SettingsService {
|
||||
t.Helper()
|
||||
svc, _ := seededServiceStore(t)
|
||||
return svc
|
||||
}
|
||||
|
||||
// seededServiceStore is seededService with its store.
|
||||
func seededServiceStore(t *testing.T) (*SettingsService, *store.Store) {
|
||||
t.Helper()
|
||||
st := migratedStore(t, t.TempDir())
|
||||
if err := SeedSettings(context.Background(), st, "America/New_York"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return NewSettingsService(st)
|
||||
return NewSettingsService(st), st
|
||||
}
|
||||
|
||||
// addDinners stores n dinners with status.
|
||||
func addDinners(t *testing.T, st *store.Store, status string, n int) {
|
||||
t.Helper()
|
||||
for range n {
|
||||
id := store.NewID()
|
||||
now := store.Now().String()
|
||||
if _, err := db.New(st.Writer).CreateDinner(context.Background(), db.CreateDinnerParams{
|
||||
ID: id, Name: "Dinner " + id, NormalizedName: "dinner " + strings.ToLower(id),
|
||||
Status: status, Origin: "regular", GroundingStatus: "pending", CreatedAt: now, UpdatedAt: now,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
@@ -220,7 +245,8 @@ func TestUpdateAppliesPatchAndRecordsUpdatedBy(t *testing.T) {
|
||||
func TestUpdateCompletesOnboardingOnce(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
svc := seededService(t)
|
||||
svc, st := seededServiceStore(t)
|
||||
addDinners(t, st, "regular", MinRegulars)
|
||||
|
||||
got, err := svc.Update(ctx, SettingsPatch{OnboardingCompleted: ptr(true)}, "sam")
|
||||
if err != nil {
|
||||
@@ -250,6 +276,36 @@ func TestUpdateCompletesOnboardingOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateOnboardingNeedsMinRegulars: onboarding cannot be completed
|
||||
// with fewer than MinRegulars Regulars; archived and Quest log dinners do
|
||||
// not count.
|
||||
func TestUpdateOnboardingNeedsMinRegulars(t *testing.T) {
|
||||
t.Parallel()
|
||||
for regulars := range MinRegulars {
|
||||
t.Run(fmt.Sprintf("%d Regulars", regulars), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
svc, st := seededServiceStore(t)
|
||||
addDinners(t, st, "regular", regulars)
|
||||
addDinners(t, st, "archived", MinRegulars)
|
||||
addDinners(t, st, "quest_log", MinRegulars)
|
||||
|
||||
_, err := svc.Update(ctx, SettingsPatch{OnboardingCompleted: ptr(true)}, "sam")
|
||||
var verr *ValidationError
|
||||
if !errors.As(err, &verr) || len(verr.Problems) != 1 || verr.Problems[0].Field != "onboarding_completed" {
|
||||
t.Fatalf("Update = %v, want a ValidationError on onboarding_completed", err)
|
||||
}
|
||||
got, err := svc.Get(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.OnboardingCompletedAt != nil {
|
||||
t.Errorf("onboarding_completed_at = %s after a refused completion, want null", *got.OnboardingCompletedAt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRequiresMember(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user