Wire plan_ready, list_ready, new_suggestions and Send to Google Chat (Feature 19) (#167)
CI / ci (push) Successful in 7m2s
CI / ci (push) Successful in 7m2s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #167.
This commit is contained in:
@@ -17,11 +17,14 @@ import (
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/ai/harness"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/ai/skills"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/chat"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/chat/chattest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/db"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/gcal"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/gcal/gcaltest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/jobs"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/notify/notifytest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/plan"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/shopping"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
|
||||
@@ -44,6 +47,10 @@ type plansEnv struct {
|
||||
// unconnected.
|
||||
google *gcaltest.Google
|
||||
gcal *gcal.Service
|
||||
// chat is the fake Google Chat endpoint chatSvc posts to; no webhook
|
||||
// is stored until a test stores one.
|
||||
chat *chattest.Chat
|
||||
chatSvc *chat.Service
|
||||
// logs holds every log line the router writes.
|
||||
logs *bytes.Buffer
|
||||
}
|
||||
@@ -85,15 +92,21 @@ func newPlansRouterTitled(t *testing.T, title shopping.Titler) plansEnv {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc, err := plan.NewService(st, fail, runner, googleSvc, pub, logger)
|
||||
notifier := notifytest.New(t, st)
|
||||
svc, err := plan.NewService(st, fail, runner, googleSvc, notifier, pub, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
shop, err := shopping.NewService(st, title, runner, logger)
|
||||
fakeChat := chattest.New(t)
|
||||
chatSvc, err := chat.NewService(st, newTestBox(t), pub, fakeChat.Transport())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := NewRouter(logger, "dev", nil, stubAuth{}, stubSessions{}, newTestSettings(t), newTestSearch(t), newTestAI(t), newTestChat(t),
|
||||
shop, err := shopping.NewService(st, title, runner, notifier, chatSvc, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := NewRouter(logger, "dev", nil, stubAuth{}, stubSessions{}, newTestSettings(t), newTestSearch(t), newTestAI(t), chatSvc,
|
||||
newTestDinners(t), newTestSuggest(t), svc, shop, googleSvc, newTestPush(t), runner, newTestPhotos(t), fstest.MapFS{"index.html": {Data: []byte(fixtureIndex)}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -129,7 +142,7 @@ func newPlansRouterTitled(t *testing.T, title shopping.Titler) plansEnv {
|
||||
}
|
||||
}
|
||||
return plansEnv{h: h, st: st, svc: svc, shop: shop, runner: runner, questLog: dinner("Pad Thai", "quest_log", "stovetop", 35),
|
||||
google: google, gcal: googleSvc, logs: logs}
|
||||
google: google, gcal: googleSvc, chat: fakeChat, chatSvc: chatSvc, logs: logs}
|
||||
}
|
||||
|
||||
func (e plansEnv) setStatus(t *testing.T, status string) {
|
||||
|
||||
@@ -163,6 +163,7 @@ func NewRouter(logger *slog.Logger, version string, trusted forwarded.Trusted, a
|
||||
apiV1.Get("/shopping/archive", getSupplyArchive(logger, shopSvc))
|
||||
apiV1.Get("/shopping/{weekStart}", getSupplyRun(logger, shopSvc))
|
||||
apiV1.Post("/shopping/{weekStart}/generate", postSupplyRunGenerate(logger, shopSvc))
|
||||
apiV1.Post("/shopping/{weekStart}/send-chat", postSupplyRunSendChat(logger, shopSvc))
|
||||
apiV1.Patch("/shopping/items/{id}", patchShoppingItem(logger, shopSvc))
|
||||
apiV1.Get("/rotation-reviews", getRotationReviews(logger, planSvc))
|
||||
apiV1.Post("/dinners/{id}/rotation-review", postRotationReview(logger, planSvc))
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/gcal"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/jobs"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/notify/notifytest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/plan"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/recipes"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/recipes/recipestest"
|
||||
@@ -186,7 +187,7 @@ func newTestSuggest(t *testing.T) *suggest.Service {
|
||||
fail := func(context.Context, string, skills.SideQuestsRequest) (skills.SideQuests, error) {
|
||||
return skills.SideQuests{}, errors.New("no model in this test")
|
||||
}
|
||||
svc, err := suggest.NewService(st, g, fail, runner, logger)
|
||||
svc, err := suggest.NewService(st, g, fail, runner, notifytest.New(t, st), logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -206,7 +207,7 @@ func newTestPlan(t *testing.T) *plan.Service {
|
||||
fail := func(context.Context, string, skills.WeekRequest) (skills.Week, error) {
|
||||
return skills.Week{}, errors.New("no model in this test")
|
||||
}
|
||||
svc, err := plan.NewService(st, fail, runner, newTestGoogle(t), &url.URL{Scheme: "https", Host: "mealquest.example"}, logger)
|
||||
svc, err := plan.NewService(st, fail, runner, newTestGoogle(t), notifytest.New(t, st), &url.URL{Scheme: "https", Host: "mealquest.example"}, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -214,7 +215,8 @@ func newTestPlan(t *testing.T) *plan.Service {
|
||||
}
|
||||
|
||||
// newTestShopping returns a shopping service over a migrated temp
|
||||
// database, whose Supply runs stay untitled.
|
||||
// database, whose Supply runs stay untitled and whose posts to Google Chat
|
||||
// all fail.
|
||||
func newTestShopping(t *testing.T) *shopping.Service {
|
||||
t.Helper()
|
||||
st := newTestStore(t)
|
||||
@@ -223,7 +225,7 @@ func newTestShopping(t *testing.T) *shopping.Service {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc, err := shopping.NewService(st, untitled, runner, logger)
|
||||
svc, err := shopping.NewService(st, untitled, runner, notifytest.New(t, st), newTestChat(t), logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/chat"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/shopping"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
|
||||
@@ -17,6 +18,9 @@ import (
|
||||
// maxShoppingItemBody bounds a PATCH /shopping/items/{id} body.
|
||||
const maxShoppingItemBody = 1 << 10
|
||||
|
||||
// maxSendChatBody bounds a POST /shopping/{weekStart}/send-chat body.
|
||||
const maxSendChatBody = 1 << 10
|
||||
|
||||
// supplyRun is a Supply run as the API shows it. label is the week label,
|
||||
// which stands in for title until the AI writes one.
|
||||
type supplyRun struct {
|
||||
@@ -25,6 +29,9 @@ type supplyRun struct {
|
||||
Title *string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
SentToChatAt *string `json:"sent_to_chat_at"`
|
||||
// ChatSendUnconfirmedAt is when a send to Google Chat whose outcome is
|
||||
// not known began.
|
||||
ChatSendUnconfirmedAt *string `json:"chat_send_unconfirmed_at"`
|
||||
shopping.Counts
|
||||
Groups []supplyRunGroup `json:"groups"`
|
||||
}
|
||||
@@ -59,6 +66,12 @@ type generatedSupplyRun struct {
|
||||
Skipped []shopping.SkippedDay `json:"skipped"`
|
||||
}
|
||||
|
||||
// sendChatRequest is the POST /shopping/{weekStart}/send-chat body:
|
||||
// resend posts a week already sent again.
|
||||
type sendChatRequest struct {
|
||||
Resend bool `json:"resend"`
|
||||
}
|
||||
|
||||
// shoppingItemPatch is the PATCH /shopping/items/{id} body.
|
||||
type shoppingItemPatch struct {
|
||||
Needed *bool `json:"needed"`
|
||||
@@ -120,6 +133,53 @@ func postSupplyRunGenerate(logger *slog.Logger, svc *shopping.Service) http.Hand
|
||||
}
|
||||
}
|
||||
|
||||
// postSupplyRunSendChat posts the week's Supply run to Google Chat and
|
||||
// answers with it, sent_to_chat_at recorded. Its body is a JSON object,
|
||||
// {} or {"resend": true}: a week already sent, or with a send
|
||||
// unconfirmed, is answered as it is, without a second post, unless resend
|
||||
// is true. No webhook stored is 409 not_configured; a send whose outcome is
|
||||
// not known (Google Chat did not answer, or its acceptance could not be
|
||||
// recorded) 502 chat_unconfirmed, not retryable, since retrying could post
|
||||
// twice; and Google Chat refusing the message 502 chat_refused, retryable
|
||||
// only for 429 or 5xx.
|
||||
func postSupplyRunSendChat(logger *slog.Logger, svc *shopping.Service) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
week, ok := weekStartOrFail(w, r, logger)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
body, err := decodePatch[sendChatRequest](http.MaxBytesReader(w, r.Body, maxSendChatBody))
|
||||
if err != nil {
|
||||
writeError(w, r, logger, http.StatusBadRequest, "invalid_request", err.Error())
|
||||
return
|
||||
}
|
||||
l, err := svc.SendToChat(r.Context(), week, body.Resend)
|
||||
var refused *chat.RefusedError
|
||||
switch {
|
||||
case errors.Is(err, chat.ErrNotConfigured):
|
||||
writeError(w, r, logger, http.StatusConflict, "not_configured", "no Google Chat webhook is configured; add one in Settings")
|
||||
return
|
||||
case errors.Is(err, shopping.ErrSendUnconfirmed):
|
||||
logger.WarnContext(r.Context(), "send Supply run to Google Chat", "error", err)
|
||||
writeAPIError(w, r, logger, http.StatusBadGateway, apiError{Code: "chat_unconfirmed", Message: shopping.ErrSendUnconfirmed.Error()})
|
||||
return
|
||||
case errors.As(err, &refused):
|
||||
logger.WarnContext(r.Context(), "send Supply run to Google Chat", "error", err)
|
||||
writeAPIError(w, r, logger, http.StatusBadGateway, apiError{Code: "chat_refused", Message: err.Error(), Retryable: refused.Retryable()})
|
||||
return
|
||||
case err != nil:
|
||||
writeShoppingError(w, r, logger, "send Supply run to Google Chat", err)
|
||||
return
|
||||
}
|
||||
out, err := supplyRunOf(week, l)
|
||||
if err != nil {
|
||||
writeShoppingError(w, r, logger, "send Supply run to Google Chat", err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, r, logger, http.StatusOK, out)
|
||||
}
|
||||
}
|
||||
|
||||
// patchShoppingItem marks a Supply run item needed or "have it" and
|
||||
// answers with its Supply run's new counts.
|
||||
func patchShoppingItem(logger *slog.Logger, svc *shopping.Service) http.HandlerFunc {
|
||||
@@ -232,13 +292,14 @@ func supplyRunOf(week store.LocalDate, l shopping.List) (supplyRun, error) {
|
||||
}
|
||||
}
|
||||
return supplyRun{
|
||||
WeekStart: l.WeekStart,
|
||||
Label: label,
|
||||
Title: l.Title,
|
||||
Status: l.Status,
|
||||
SentToChatAt: l.SentToChatAt,
|
||||
Counts: l.Counts(),
|
||||
Groups: groups,
|
||||
WeekStart: l.WeekStart,
|
||||
Label: label,
|
||||
Title: l.Title,
|
||||
Status: l.Status,
|
||||
SentToChatAt: l.SentToChatAt,
|
||||
ChatSendUnconfirmedAt: l.ChatSendUnconfirmedAt,
|
||||
Counts: l.Counts(),
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/ai/harness"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/ai/skills"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/chat/chattest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/db"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/plan"
|
||||
@@ -82,14 +83,15 @@ func (e plansEnv) archive(t *testing.T) {
|
||||
// supplyRunJSON is a Supply run answer decoded with every field the API
|
||||
// promises.
|
||||
type supplyRunJSON struct {
|
||||
WeekStart string `json:"weekStart"`
|
||||
Label string `json:"label"`
|
||||
Title *string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
SentToChatAt *string `json:"sent_to_chat_at"`
|
||||
NeededCount int `json:"needed_count"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Groups []struct {
|
||||
WeekStart string `json:"weekStart"`
|
||||
Label string `json:"label"`
|
||||
Title *string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
SentToChatAt *string `json:"sent_to_chat_at"`
|
||||
ChatSendUnconfirmedAt *string `json:"chat_send_unconfirmed_at"`
|
||||
NeededCount int `json:"needed_count"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Groups []struct {
|
||||
Dinner struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -184,7 +186,7 @@ func TestSupplyRunShape(t *testing.T) {
|
||||
if status != http.StatusOK {
|
||||
t.Fatalf("generate = %d %v", status, raw)
|
||||
}
|
||||
wantTop := []string{"groups", "label", "needed_count", "sent_to_chat_at", "skipped", "status", "title", "total_count", "weekStart"}
|
||||
wantTop := []string{"chat_send_unconfirmed_at", "groups", "label", "needed_count", "sent_to_chat_at", "skipped", "status", "title", "total_count", "weekStart"}
|
||||
if got := keys(raw); !reflect.DeepEqual(got, wantTop) {
|
||||
t.Errorf("generate fields = %v, want %v", got, wantTop)
|
||||
}
|
||||
@@ -787,3 +789,124 @@ func TestSupplyArchive(t *testing.T) {
|
||||
t.Errorf("frozen list = %s %d/%d %v, want archived 2/3 %v", frozen.Status, frozen.NeededCount, frozen.TotalCount, frozen.render(), wantLines)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSupplyRunSendChat posts the week's Supply run to Google Chat once,
|
||||
// records sent_to_chat_at, answers a second send with the status as it
|
||||
// is, and posts again only with resend.
|
||||
func TestSupplyRunSendChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := newPlansRouter(t)
|
||||
e.groundWeek(t)
|
||||
var l supplyRunJSON
|
||||
callShopping(t, e, http.MethodPost, shoppingPath+"2026-10-12/generate", "", &l)
|
||||
callShopping(t, e, http.MethodPatch, shoppingPath+"items/"+l.itemID(t, "onion"), `{"needed":false}`, nil)
|
||||
send := shoppingPath + "2026-10-12/send-chat"
|
||||
|
||||
if status, body := callShopping(t, e, http.MethodPost, send, `{}`, nil); status != http.StatusConflict || errorCode(body) != "not_configured" {
|
||||
t.Fatalf("send with no webhook = %d %v, want 409 not_configured", status, body)
|
||||
}
|
||||
if _, err := e.chatSvc.Put(t.Context(), chattest.Webhook, "Family"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var sent supplyRunJSON
|
||||
if status, body := callShopping(t, e, http.MethodPost, send, `{}`, &sent); status != http.StatusOK {
|
||||
t.Fatalf("send = %d %v", status, body)
|
||||
}
|
||||
if sent.SentToChatAt == nil || sent.WeekStart != "2026-10-12" || !reflect.DeepEqual(sent.render(), []string{
|
||||
"Monday 2026-10-12 Beef Chili: ground beef [1 lb] needed",
|
||||
"Monday 2026-10-12 Beef Chili: kidney beans [2 cans] needed",
|
||||
"Monday 2026-10-12 Beef Chili: onion [1] have it",
|
||||
"Tuesday 2026-10-13 Regular 2026-10-13: spaghetti [500 g] needed",
|
||||
"Tuesday 2026-10-13 Regular 2026-10-13: basil [null] needed",
|
||||
}) {
|
||||
t.Errorf("send answer = %+v, want the Supply run with sent_to_chat_at", sent)
|
||||
}
|
||||
posts := e.chat.Posts()
|
||||
if len(posts) != 1 {
|
||||
t.Fatalf("Google Chat got %d posts, want 1", len(posts))
|
||||
}
|
||||
want := "Supply run · Week 42 · Oct 12 – 18\n\n*Beef Chili*\n• ground beef — 1 lb\n• kidney beans — 2 cans\n\n" +
|
||||
"*Regular 2026-10-13*\n• spaghetti — 500 g\n• basil\n\nOpen the Supply run →"
|
||||
if posts[0].Text() != want {
|
||||
t.Errorf("posted text = %q, want %q", posts[0].Text(), want)
|
||||
}
|
||||
|
||||
var again supplyRunJSON
|
||||
if status, body := callShopping(t, e, http.MethodPost, send, `{}`, &again); status != http.StatusOK || again.SentToChatAt == nil || *again.SentToChatAt != *sent.SentToChatAt {
|
||||
t.Errorf("send again = %d %v, sent_to_chat_at %v, want 200 and %s", status, body, again.SentToChatAt, *sent.SentToChatAt)
|
||||
}
|
||||
if n := len(e.chat.Posts()); n != 1 {
|
||||
t.Errorf("send again without resend posted: %d posts, want 1", n)
|
||||
}
|
||||
var got supplyRunJSON
|
||||
callShopping(t, e, http.MethodGet, shoppingPath+"2026-10-12", "", &got)
|
||||
if got.SentToChatAt == nil || *got.SentToChatAt != *sent.SentToChatAt {
|
||||
t.Errorf("GET sent_to_chat_at = %v, want %s", got.SentToChatAt, *sent.SentToChatAt)
|
||||
}
|
||||
|
||||
var resent supplyRunJSON
|
||||
if status, body := callShopping(t, e, http.MethodPost, send, `{"resend":true}`, &resent); status != http.StatusOK || resent.SentToChatAt == nil {
|
||||
t.Fatalf("resend = %d %v", status, body)
|
||||
}
|
||||
if n := len(e.chat.Posts()); n != 2 {
|
||||
t.Errorf("resend: %d posts, want 2", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupplyRunSendChatErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := newPlansRouter(t)
|
||||
e.groundWeek(t)
|
||||
callShopping(t, e, http.MethodPost, shoppingPath+"2026-10-12/generate", "", nil)
|
||||
if _, err := e.chatSvc.Put(t.Context(), chattest.Webhook, "Family"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name, path, body string
|
||||
status int
|
||||
code string
|
||||
}{
|
||||
{"no body", "2026-10-12/send-chat", "", http.StatusBadRequest, "invalid_request"},
|
||||
{"resend not a boolean", "2026-10-12/send-chat", `{"resend":"yes"}`, http.StatusBadRequest, "invalid_request"},
|
||||
{"unknown field", "2026-10-12/send-chat", `{"force":true}`, http.StatusBadRequest, "invalid_request"},
|
||||
{"not a Monday", "2026-10-13/send-chat", `{}`, http.StatusBadRequest, "invalid_week"},
|
||||
{"no list", "2026-10-19/send-chat", `{}`, http.StatusNotFound, "not_found"},
|
||||
} {
|
||||
status, body := callShopping(t, e, http.MethodPost, shoppingPath+tc.path, tc.body, nil)
|
||||
if status != tc.status || errorCode(body) != tc.code {
|
||||
t.Errorf("%s = %d %v, want %d %s", tc.name, status, body, tc.status, tc.code)
|
||||
}
|
||||
}
|
||||
|
||||
e.chat.SetStatus(http.StatusInternalServerError)
|
||||
status, body := callShopping(t, e, http.MethodPost, shoppingPath+"2026-10-12/send-chat", `{}`, nil)
|
||||
if status != http.StatusBadGateway || errorCode(body) != "chat_refused" || body["error"].(map[string]any)["retryable"] != true {
|
||||
t.Errorf("send Google Chat refuses = %d %v, want 502 chat_refused, retryable", status, body)
|
||||
}
|
||||
e.chat.SetStatus(http.StatusNotFound)
|
||||
status, body = callShopping(t, e, http.MethodPost, shoppingPath+"2026-10-12/send-chat", `{}`, nil)
|
||||
if status != http.StatusBadGateway || errorCode(body) != "chat_refused" || body["error"].(map[string]any)["retryable"] != false {
|
||||
t.Errorf("send Google Chat answers 404 = %d %v, want 502 chat_refused, not retryable", status, body)
|
||||
}
|
||||
var l supplyRunJSON
|
||||
callShopping(t, e, http.MethodGet, shoppingPath+"2026-10-12", "", &l)
|
||||
if l.SentToChatAt != nil || l.ChatSendUnconfirmedAt != nil {
|
||||
t.Errorf("after refused sends sent_to_chat_at = %v, chat_send_unconfirmed_at = %v, want both null", l.SentToChatAt, l.ChatSendUnconfirmedAt)
|
||||
}
|
||||
e.chat.Close()
|
||||
status, body = callShopping(t, e, http.MethodPost, shoppingPath+"2026-10-12/send-chat", `{}`, nil)
|
||||
if status != http.StatusBadGateway || errorCode(body) != "chat_unconfirmed" || body["error"].(map[string]any)["retryable"] != false {
|
||||
t.Errorf("send Google Chat does not answer = %d %v, want 502 chat_unconfirmed, not retryable", status, body)
|
||||
}
|
||||
callShopping(t, e, http.MethodGet, shoppingPath+"2026-10-12", "", &l)
|
||||
if l.SentToChatAt != nil || l.ChatSendUnconfirmedAt == nil {
|
||||
t.Errorf("after an unanswered send sent_to_chat_at = %v, chat_send_unconfirmed_at = %v, want null and set", l.SentToChatAt, l.ChatSendUnconfirmedAt)
|
||||
}
|
||||
|
||||
e.archive(t)
|
||||
status, body = callShopping(t, e, http.MethodPost, shoppingPath+"2026-10-12/send-chat", `{"resend":true}`, nil)
|
||||
if status != http.StatusConflict || errorCode(body) != "archived" {
|
||||
t.Errorf("send an archived list = %d %v, want 409 archived", status, body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/ai/skills"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/db"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/jobs"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/notify/notifytest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/recipes/recipestest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/suggest"
|
||||
@@ -40,7 +41,7 @@ func newSuggestionsRouter(t *testing.T, names ...string) (http.Handler, *store.S
|
||||
fail := func(context.Context, string, skills.SideQuestsRequest) (skills.SideQuests, error) {
|
||||
return skills.SideQuests{}, errors.New("no model in this test")
|
||||
}
|
||||
svc, err := suggest.NewService(st, g, fail, runner, logger)
|
||||
svc, err := suggest.NewService(st, g, fail, runner, notifytest.New(t, st), logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user