@@ -284,6 +284,9 @@ func (c *Client) ReadRecipeContent(ctx context.Context, recipeID string) (Recipe
if err := c.doAuthenticated(ctx, http.MethodGet, path, nil, decodeContent); err != nil {
return RecipeContent{}, err
}
+ if err := validateRecipeContent(content); err != nil {
+ return RecipeContent{}, err
+ }
return content, nil
}
@@ -422,6 +425,17 @@ func validateRecipeCards(recipes []RecipeCard) error {
return nil
}
+func validateRecipeContent(content RecipeContent) error {
+ if strings.TrimSpace(content.Content) == "" {
+ return fmt.Errorf("cooked recipe content response missing content")
+ }
+ if content.Portions < 1 {
+ return fmt.Errorf("cooked recipe content response missing or invalid portions")
+ }
+
+ return nil
+}
+
func jsonNumberToInt(number json.Number) (int, error) {
text := number.String()
if text == "" {
@@ -126,6 +126,39 @@ func TestSavePreparedRecipeACIDAPIClientResponses1RejectsMissingOrBlankRecipeID(
}
}
+// api-client.RESPONSES.1 recipes.READ.7 recipes.READ.8
+func TestReadRecipeContentACIDAPIClientResponses1RejectsMissingContentOrPortions(t *testing.T) {
+ tests := []struct {
+ name string
+ body string
+ wantErr string
+ }{
+ {name: "missing content", body: `{"portions":2}`, wantErr: "missing content"},
+ {name: "blank content", body: `{"content":" ","portions":2}`, wantErr: "missing content"},
+ {name: "missing portions", body: `{"content":"# Pasta"}`, wantErr: "missing or invalid portions"},
+ {name: "zero portions", body: `{"content":"# Pasta","portions":0}`, wantErr: "missing or invalid portions"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var contentCalls int
+ client, closeServer := newTestClient(t, rawRecipeContentTestHandler(t, &contentCalls, tt.body))
+ defer closeServer()
+
+ _, err := client.ReadRecipeContent(context.Background(), "recipe/with space")
+ if err == nil {
+ t.Fatal("ReadRecipeContent() error = nil, want invalid recipe content error")
+ }
+ if !strings.Contains(err.Error(), tt.wantErr) {
+ t.Fatalf("ReadRecipeContent() error = %v, want %q", err, tt.wantErr)
+ }
+ if contentCalls != 1 {
+ t.Fatalf("content calls = %d, want 1", contentCalls)
+ }
+ })
+ }
+}
+
func rawRecipeListTestHandler(t *testing.T, path, body string) http.HandlerFunc {
t.Helper()
@@ -144,6 +177,27 @@ func rawRecipeListTestHandler(t *testing.T, path, body string) http.HandlerFunc
}
}
+func rawRecipeContentTestHandler(t *testing.T, contentCalls *int, body string) http.HandlerFunc {
+ t.Helper()
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.EscapedPath() {
+ case "/api/public/login":
+ writeLoginResponse(t, w)
+ case "/api/recipe/recipe%2Fwith%20space/content":
+ requireMethod(t, r, http.MethodGet, "content")
+ requireSessionCookie(t, r)
+ (*contentCalls)++
+ w.Header().Set("Content-Type", "application/json")
+ if _, err := w.Write([]byte(body)); err != nil {
+ t.Fatalf("write raw recipe content response: %v", err)
+ }
+ default:
+ t.Fatalf("unexpected path %s", r.URL.EscapedPath())
+ }
+ }
+}
+
func rawSavePreparedRecipeTestHandler(t *testing.T, saveCalls *int, body string) http.HandlerFunc {
t.Helper()