@@ -71,6 +71,12 @@ type RecipeMetadata struct {
EditPermission bool `json:"edit-permission"`
}
+// RecipeContent is the Markdown-style body and portions for a Cooked recipe.
+type RecipeContent struct {
+ Content string `json:"content"`
+ Portions int `json:"portions"`
+}
+
// NewClient returns a Cooked client with an in-memory cookie jar.
func NewClient(baseURL *url.URL, username, password string) (*Client, error) {
jar, err := newCookieJar()
@@ -135,6 +141,20 @@ func (c *Client) ReadRecipeMetadata(ctx context.Context, recipeID string) (Recip
return metadata, nil
}
+// ReadRecipeContent logs in when needed and returns recipe content and portions.
+func (c *Client) ReadRecipeContent(ctx context.Context, recipeID string) (RecipeContent, error) {
+ var content RecipeContent
+ decodeContent := func(decoder *json.Decoder) error {
+ return decoder.Decode(&content)
+ }
+ path := "/api/recipe/" + url.PathEscape(recipeID) + "/content"
+ if err := c.doAuthenticated(ctx, http.MethodGet, path, nil, decodeContent); err != nil {
+ return RecipeContent{}, err
+ }
+
+ return content, nil
+}
+
func (c *Client) getRecipes(ctx context.Context, path string) ([]RecipeCard, error) {
var response recipeListResponse
decodeRecipes := func(decoder *json.Decoder) error {
@@ -241,6 +241,56 @@ func TestReadRecipeMetadataACIDRecipesRead6ReadsMetadata(t *testing.T) {
}
}
+func TestReadRecipeContentACIDRecipesRead7And8ReadsContentAndPortions(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.EscapedPath() {
+ case "/api/public/login":
+ http.SetCookie(w, &http.Cookie{Name: "cooked_session", Value: "session-value", Path: "/"})
+ writeJSON(t, w, loginResponse{Username: "returned-user"})
+ case "/api/recipe/recipe%2Fwith%20space/content":
+ if r.Method != http.MethodGet {
+ t.Fatalf("content method = %s, want GET", r.Method)
+ }
+ cookie, err := r.Cookie("cooked_session")
+ if err != nil {
+ t.Fatalf("missing session cookie: %v", err)
+ }
+ if cookie.Value != "session-value" {
+ t.Fatalf("session cookie = %q, want session-value", cookie.Value)
+ }
+
+ writeJSON(t, w, RecipeContent{
+ Content: "# Pasta\n\n- 200g pasta\n\n1. Boil pasta.",
+ Portions: 2,
+ })
+ default:
+ t.Fatalf("unexpected path %s", r.URL.EscapedPath())
+ }
+ }))
+ defer server.Close()
+
+ baseURL, err := url.Parse(server.URL)
+ if err != nil {
+ t.Fatalf("parse server URL: %v", err)
+ }
+ client, err := NewClient(baseURL, "configured-user", "configured-password")
+ if err != nil {
+ t.Fatalf("NewClient() error = %v", err)
+ }
+
+ content, err := client.ReadRecipeContent(context.Background(), "recipe/with space")
+ if err != nil {
+ t.Fatalf("ReadRecipeContent() error = %v", err)
+ }
+
+ if content.Content != "# Pasta\n\n- 200g pasta\n\n1. Boil pasta." {
+ t.Fatalf("content = %q, want recipe markdown", content.Content)
+ }
+ if content.Portions != 2 {
+ t.Fatalf("portions = %d, want 2", content.Portions)
+ }
+}
+
func TestUserPathEscapesAuthenticatedUsername(t *testing.T) {
client := &Client{authenticatedUsername: "user/name"}
@@ -267,7 +317,7 @@ func TestNewRequestPreservesEscapedUserPathWithQuery(t *testing.T) {
}
}
-func writeJSON[T loginResponse | shoppingListResponse | recipeListResponse](t *testing.T, w http.ResponseWriter, value T) {
+func writeJSON[T loginResponse | shoppingListResponse | recipeListResponse | RecipeContent](t *testing.T, w http.ResponseWriter, value T) {
t.Helper()
w.Header().Set("Content-Type", "application/json")