client_recipe_validation_test.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: LicenseRef-MutuaL-1.2
  4
  5package cooked
  6
  7import (
  8	"context"
  9	"net/http"
 10	"strings"
 11	"testing"
 12)
 13
 14func TestListRecipesACIDAPIClientResponses1AcceptsEmptyRecipesList(t *testing.T) {
 15	client, closeServer := newTestClient(t, rawRecipeListTestHandler(
 16		t,
 17		"/api/user/returned-user/recipes",
 18		`[]`,
 19	))
 20	defer closeServer()
 21
 22	recipes, err := client.ListRecipes(context.Background(), 1, 10)
 23	if err != nil {
 24		t.Fatalf("ListRecipes() error = %v", err)
 25	}
 26	if len(recipes) != 0 {
 27		t.Fatalf("recipes length = %d, want 0", len(recipes))
 28	}
 29}
 30
 31func TestListRecipesACIDAPIClientResponses1RejectsNullRecipesList(t *testing.T) {
 32	client, closeServer := newTestClient(t, rawRecipeListTestHandler(
 33		t,
 34		"/api/user/returned-user/recipes",
 35		`null`,
 36	))
 37	defer closeServer()
 38
 39	_, err := client.ListRecipes(context.Background(), 1, 10)
 40	if err == nil {
 41		t.Fatal("ListRecipes() error = nil, want missing recipes error")
 42	}
 43}
 44
 45func TestSearchRecipesACIDAPIClientResponses1RejectsRecipeCardsWithoutIDOrTitle(t *testing.T) {
 46	tests := []struct {
 47		name string
 48		body string
 49	}{
 50		{name: "missing id", body: `[{"title":"Pasta"}]`},
 51		{name: "blank title", body: `[{"id":"recipe-1","title":"   "}]`},
 52	}
 53
 54	for _, tt := range tests {
 55		t.Run(tt.name, func(t *testing.T) {
 56			client, closeServer := newTestClient(t, rawRecipeListTestHandler(
 57				t,
 58				"/api/user/returned-user/recipes/search",
 59				tt.body,
 60			))
 61			defer closeServer()
 62
 63			_, err := client.SearchRecipes(context.Background(), "pasta", 1)
 64			if err == nil {
 65				t.Fatal("SearchRecipes() error = nil, want invalid recipe card error")
 66			}
 67		})
 68	}
 69}
 70
 71// api-client.RESPONSES.1 recipes.SAVE.7 recipes.SAVE.9
 72func TestSaveRecipeDraftACIDAPIClientResponses1RejectsMissingRecipeID(t *testing.T) {
 73	var saveCalls int
 74	client, closeServer := newTestClient(t, rawSaveRecipeDraftTestHandler(t, &saveCalls, `{}`))
 75	defer closeServer()
 76
 77	_, err := client.SaveRecipeDraft(
 78		context.Background(),
 79		"draft/with space",
 80		"# Pasta\n\n1. Boil pasta.",
 81		4,
 82	)
 83	if err == nil {
 84		t.Fatal("SaveRecipeDraft() error = nil, want missing recipe ID error")
 85	}
 86	if !strings.Contains(err.Error(), "missing recipe ID") {
 87		t.Fatalf("SaveRecipeDraft() error = %v, want missing recipe ID error", err)
 88	}
 89	if saveCalls != 1 {
 90		t.Fatalf("save calls = %d, want 1", saveCalls)
 91	}
 92}
 93
 94// api-client.RESPONSES.1 recipes.SAVE.2 recipes.SAVE.9
 95func TestSavePreparedRecipeACIDAPIClientResponses1RejectsMissingOrBlankRecipeID(t *testing.T) {
 96	tests := []struct {
 97		name string
 98		body string
 99	}{
100		{name: "missing recipe ID", body: `{}`},
101		{name: "blank recipe ID", body: `{"recipe-id":"   "}`},
102	}
103
104	for _, tt := range tests {
105		t.Run(tt.name, func(t *testing.T) {
106			var saveCalls int
107			client, closeServer := newTestClient(t, rawSavePreparedRecipeTestHandler(t, &saveCalls, tt.body))
108			defer closeServer()
109
110			_, err := client.SavePreparedRecipe(
111				context.Background(),
112				"Pasta",
113				"# Pasta\n\n1. Boil pasta.",
114				2,
115			)
116			if err == nil {
117				t.Fatal("SavePreparedRecipe() error = nil, want missing recipe ID error")
118			}
119			if !strings.Contains(err.Error(), "missing recipe ID") {
120				t.Fatalf("SavePreparedRecipe() error = %v, want missing recipe ID error", err)
121			}
122			if saveCalls != 1 {
123				t.Fatalf("save calls = %d, want 1", saveCalls)
124			}
125		})
126	}
127}
128
129func rawRecipeListTestHandler(t *testing.T, path, body string) http.HandlerFunc {
130	t.Helper()
131
132	return func(w http.ResponseWriter, r *http.Request) {
133		switch r.URL.Path {
134		case "/api/public/login":
135			writeLoginResponse(t, w)
136		case path:
137			w.Header().Set("Content-Type", "application/json")
138			if _, err := w.Write([]byte(body)); err != nil {
139				t.Fatalf("write raw recipe list response: %v", err)
140			}
141		default:
142			t.Fatalf("unexpected path %s", r.URL.Path)
143		}
144	}
145}
146
147func rawSavePreparedRecipeTestHandler(t *testing.T, saveCalls *int, body string) http.HandlerFunc {
148	t.Helper()
149
150	return func(w http.ResponseWriter, r *http.Request) {
151		switch r.URL.Path {
152		case "/api/public/login":
153			writeLoginResponse(t, w)
154		case "/api/recipe/import/save":
155			requireMethod(t, r, http.MethodPost, "save prepared recipe")
156			requireSessionCookie(t, r)
157			requireSavePreparedRecipeRequest(t, r)
158			(*saveCalls)++
159			w.Header().Set("Content-Type", "application/json")
160			if _, err := w.Write([]byte(body)); err != nil {
161				t.Fatalf("write raw save prepared response: %v", err)
162			}
163		default:
164			t.Fatalf("unexpected path %s", r.URL.Path)
165		}
166	}
167}
168
169func rawSaveRecipeDraftTestHandler(t *testing.T, saveCalls *int, body string) http.HandlerFunc {
170	t.Helper()
171
172	return func(w http.ResponseWriter, r *http.Request) {
173		switch r.URL.EscapedPath() {
174		case "/api/public/login":
175			writeLoginResponse(t, w)
176		case "/api/extract/draft%2Fwith%20space/save":
177			requireMethod(t, r, http.MethodPost, "save recipe draft")
178			requireSessionCookie(t, r)
179			requireRecipeContentRequest(t, r, "draft save")
180			(*saveCalls)++
181			w.Header().Set("Content-Type", "application/json")
182			if _, err := w.Write([]byte(body)); err != nil {
183				t.Fatalf("write raw save draft response: %v", err)
184			}
185		default:
186			t.Fatalf("unexpected path %s", r.URL.EscapedPath())
187		}
188	}
189}