fix(podcasts): order selector validation — bind the htmx form wire format (issue #2)

WHY: htmx sends non-GET requests urlencoded by default; the SetFeedOrder
request struct only carried a json tag, so the binder left Order empty
and 'required' fired — the exact error the order dropdown AND the
Serial-suggestion button produced (hx-vals JSON also arrives
form-encoded). One form tag fixes both controls.

Regression test drives the exact wire format: urlencoded PUT with
order=oldest/native → 200, empty/invalid → 400.
This commit is contained in:
John O'Keefe committed 2026-10-29 19:45:46 -04:00
1 parent 8d9fb0046c
commit c753780817
2 files changed
+35 -1

No files matched your search

+34
View File
@@ -19,6 +19,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
@@ -663,3 +664,36 @@ func adminScopeExec(t *testing.T, setup *TestServerSetup, adminID pgtype.UUID, s
}
// ---- task 1.9 residue: the htmx wire format is FORM-encoded (issue #2) ----
func TestPodcastOrderFormEncoding(t *testing.T) {
setup := setupTestServer(t)
lib, _ := createPodcastsLibrary(t, setup, "test OrderForm Podcasts")
feed, err := setup.DB.CreatePodcastFeed(adminSeedCtx(t, setup), database.CreatePodcastFeedParams{
LibraryID: lib.ID,
FeedUrl: "https://feed.invalid/order-form.xml",
Title: "Order Form Feed",
})
require.NoError(t, err)
feedIDStr := uuid.UUID(feed.ID.Bytes).String()
// The UI's hx-put submits urlencoded form bodies (htmx non-GET
// default) — the binder must map them (form tag), not just JSON.
formPut := func(body string) int {
req, _ := http.NewRequest(http.MethodPut,
setup.Server.URL+"/api/podcasts/feeds/"+feedIDStr+"/order",
strings.NewReader(body))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Authorization", "Bearer "+setup.Token)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer func() { _ = resp.Body.Close() }()
return resp.StatusCode
}
assert.Equal(t, http.StatusOK, formPut("order=oldest"), "form-encoded order must bind")
assert.Equal(t, http.StatusOK, formPut("order=native"), "native binds too")
assert.Equal(t, http.StatusBadRequest, formPut(""), "missing order stays rejected (required)")
assert.Equal(t, http.StatusBadRequest, formPut("order=sideways"), "invalid order stays rejected")
}
+1 -1
View File
@@ -459,7 +459,7 @@ func (h *PodcastHandler) SetFeedOrder(c *echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid feed id"})
}
var req struct {
Order string `json:"order" validate:"required,oneof=native oldest newest"`
Order string `json:"order" form:"order" validate:"required,oneof=native oldest newest"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})