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
129// api-client.RESPONSES.1 recipes.READ.7 recipes.READ.8
130func TestReadRecipeContentACIDAPIClientResponses1RejectsMissingContentOrPortions(t *testing.T) {
131 tests := []struct {
132 name string
133 body string
134 wantErr string
135 }{
136 {name: "missing content", body: `{"portions":2}`, wantErr: "missing content"},
137 {name: "blank content", body: `{"content":" ","portions":2}`, wantErr: "missing content"},
138 {name: "missing portions", body: `{"content":"# Pasta"}`, wantErr: "missing or invalid portions"},
139 {name: "zero portions", body: `{"content":"# Pasta","portions":0}`, wantErr: "missing or invalid portions"},
140 }
141
142 for _, tt := range tests {
143 t.Run(tt.name, func(t *testing.T) {
144 var contentCalls int
145 client, closeServer := newTestClient(t, rawRecipeContentTestHandler(t, &contentCalls, tt.body))
146 defer closeServer()
147
148 _, err := client.ReadRecipeContent(context.Background(), "recipe/with space")
149 if err == nil {
150 t.Fatal("ReadRecipeContent() error = nil, want invalid recipe content error")
151 }
152 if !strings.Contains(err.Error(), tt.wantErr) {
153 t.Fatalf("ReadRecipeContent() error = %v, want %q", err, tt.wantErr)
154 }
155 if contentCalls != 1 {
156 t.Fatalf("content calls = %d, want 1", contentCalls)
157 }
158 })
159 }
160}
161
162func rawRecipeListTestHandler(t *testing.T, path, body string) http.HandlerFunc {
163 t.Helper()
164
165 return func(w http.ResponseWriter, r *http.Request) {
166 switch r.URL.Path {
167 case "/api/public/login":
168 writeLoginResponse(t, w)
169 case path:
170 w.Header().Set("Content-Type", "application/json")
171 if _, err := w.Write([]byte(body)); err != nil {
172 t.Fatalf("write raw recipe list response: %v", err)
173 }
174 default:
175 t.Fatalf("unexpected path %s", r.URL.Path)
176 }
177 }
178}
179
180func rawRecipeContentTestHandler(t *testing.T, contentCalls *int, body string) http.HandlerFunc {
181 t.Helper()
182
183 return func(w http.ResponseWriter, r *http.Request) {
184 switch r.URL.EscapedPath() {
185 case "/api/public/login":
186 writeLoginResponse(t, w)
187 case "/api/recipe/recipe%2Fwith%20space/content":
188 requireMethod(t, r, http.MethodGet, "content")
189 requireSessionCookie(t, r)
190 (*contentCalls)++
191 w.Header().Set("Content-Type", "application/json")
192 if _, err := w.Write([]byte(body)); err != nil {
193 t.Fatalf("write raw recipe content response: %v", err)
194 }
195 default:
196 t.Fatalf("unexpected path %s", r.URL.EscapedPath())
197 }
198 }
199}
200
201func rawSavePreparedRecipeTestHandler(t *testing.T, saveCalls *int, body string) http.HandlerFunc {
202 t.Helper()
203
204 return func(w http.ResponseWriter, r *http.Request) {
205 switch r.URL.Path {
206 case "/api/public/login":
207 writeLoginResponse(t, w)
208 case "/api/recipe/import/save":
209 requireMethod(t, r, http.MethodPost, "save prepared recipe")
210 requireSessionCookie(t, r)
211 requireSavePreparedRecipeRequest(t, r)
212 (*saveCalls)++
213 w.Header().Set("Content-Type", "application/json")
214 if _, err := w.Write([]byte(body)); err != nil {
215 t.Fatalf("write raw save prepared response: %v", err)
216 }
217 default:
218 t.Fatalf("unexpected path %s", r.URL.Path)
219 }
220 }
221}
222
223func rawSaveRecipeDraftTestHandler(t *testing.T, saveCalls *int, body string) http.HandlerFunc {
224 t.Helper()
225
226 return func(w http.ResponseWriter, r *http.Request) {
227 switch r.URL.EscapedPath() {
228 case "/api/public/login":
229 writeLoginResponse(t, w)
230 case "/api/extract/draft%2Fwith%20space/save":
231 requireMethod(t, r, http.MethodPost, "save recipe draft")
232 requireSessionCookie(t, r)
233 requireRecipeContentRequest(t, r, "draft save")
234 (*saveCalls)++
235 w.Header().Set("Content-Type", "application/json")
236 if _, err := w.Write([]byte(body)); err != nil {
237 t.Fatalf("write raw save draft response: %v", err)
238 }
239 default:
240 t.Fatalf("unexpected path %s", r.URL.EscapedPath())
241 }
242 }
243}