@@ -186,6 +186,24 @@ func (c *Client) PreviewRecipeText(ctx context.Context, title, text string) (Rec
return preview, nil
}
+// SavePreparedRecipe logs in when needed and saves prepared recipe markdown as a new recipe.
+func (c *Client) SavePreparedRecipe(ctx context.Context, title, markdown string, portions int) (string, error) {
+ body, err := json.Marshal(savePreparedRecipeRequest{Title: title, Description: markdown, Portions: portions})
+ if err != nil {
+ return "", fmt.Errorf("encode Cooked prepared recipe save request: %w", err)
+ }
+
+ var response saveRecipeResponse
+ decodeSave := func(decoder *json.Decoder) error {
+ return decoder.Decode(&response)
+ }
+ if err := c.doAuthenticated(ctx, http.MethodPost, "/api/recipe/import/save", body, decodeSave); err != nil {
+ return "", err
+ }
+
+ return response.RecipeID, nil
+}
+
func (c *Client) getRecipes(ctx context.Context, path string) ([]RecipeCard, error) {
var response recipeListResponse
decodeRecipes := func(decoder *json.Decoder) error {
@@ -335,6 +353,16 @@ type previewRecipeTextRequest struct {
RecipeText string `json:"recipe-text"`
}
+type savePreparedRecipeRequest struct {
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Portions int `json:"portions"`
+}
+
+type saveRecipeResponse struct {
+ RecipeID string `json:"recipe-id"`
+}
+
type loginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
@@ -126,6 +126,24 @@ func TestPreviewRecipeTextACIDRecipesPreviewText1To4PreviewsWithoutSaving(t *tes
}
}
+func TestSavePreparedRecipeACIDRecipesSave2And9And13SavesMarkdownAsDescription(t *testing.T) {
+ var saveCalls int
+ client, closeServer := newTestClient(t, savePreparedRecipeTestHandler(t, &saveCalls))
+ defer closeServer()
+
+ recipeID, err := client.SavePreparedRecipe(context.Background(), "Pasta", "# Pasta\n\n1. Boil pasta.", 2)
+ if err != nil {
+ t.Fatalf("SavePreparedRecipe() error = %v", err)
+ }
+
+ if saveCalls != 1 {
+ t.Fatalf("save calls = %d, want 1", saveCalls)
+ }
+ if recipeID != "recipe-1" {
+ t.Fatalf("recipe ID = %q, want recipe-1", recipeID)
+ }
+}
+
func TestUserPathEscapesAuthenticatedUsername(t *testing.T) {
client := &Client{authenticatedUsername: "user/name"}
@@ -285,6 +303,25 @@ func previewRecipeTextTestHandler(t *testing.T, previewCalls *int) http.HandlerF
}
}
+func savePreparedRecipeTestHandler(t *testing.T, saveCalls *int) http.HandlerFunc {
+ t.Helper()
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case "/api/public/login":
+ writeLoginResponse(t, w)
+ case "/api/recipe/import/save":
+ requireMethod(t, r, http.MethodPost, "save prepared recipe")
+ requireSessionCookie(t, r)
+ requireSavePreparedRecipeRequest(t, r)
+ (*saveCalls)++
+ writeJSON(t, w, saveRecipeResponse{RecipeID: "recipe-1"})
+ default:
+ t.Fatalf("unexpected path %s", r.URL.Path)
+ }
+ }
+}
+
func requirePreviewRecipeTextRequest(t *testing.T, r *http.Request) {
t.Helper()
@@ -297,6 +334,24 @@ func requirePreviewRecipeTextRequest(t *testing.T, r *http.Request) {
}
}
+func requireSavePreparedRecipeRequest(t *testing.T, r *http.Request) {
+ t.Helper()
+
+ var request savePreparedRecipeRequest
+ if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
+ t.Fatalf("decode save prepared recipe request: %v", err)
+ }
+ if request.Title != "Pasta" {
+ t.Fatalf("save title = %q, want Pasta", request.Title)
+ }
+ if request.Description != "# Pasta\n\n1. Boil pasta." {
+ t.Fatalf("save description = %q, want markdown", request.Description)
+ }
+ if request.Portions != 2 {
+ t.Fatalf("save portions = %d, want 2", request.Portions)
+ }
+}
+
func requireLoginRequest(t *testing.T, r *http.Request) {
t.Helper()
requireMethod(t, r, http.MethodPost, "login")
@@ -390,7 +445,9 @@ func writeRecipeMetadataResponse(t *testing.T, w http.ResponseWriter) {
}
}
-func writeJSON[T loginResponse | shoppingListResponse | recipeListResponse | RecipeContent | RecipeTextPreview](
+func writeJSON[
+ T loginResponse | shoppingListResponse | recipeListResponse | RecipeContent | RecipeTextPreview | saveRecipeResponse,
+](
t *testing.T,
w http.ResponseWriter,
value T,