feat(podcasts): the rules page retires — the collection builder IS the filter builder (item 3.4)
The smart-collection editor at /collections/:id/rules now hosts the SAME builder island as All-Books (FilterBuilderRows — the rows markup extracted into one shared templ both mounts render; the shared filterBuilderExtra component). One vocabulary, one engine, one row UI; the collection differences are exactly three: the payload is the collection's scope (episode trees get the episode fields — books trees hide them), Apply writes the tree to the collection (PUT filter_tree through the validated write path), and a legacy rules array preloads as its CONVERTED tree (RulesToFilterTree rendered server-side) so the upgrade lands at the first Apply — read-compat never mutates data silently. Retired with the page: the per-rule CRUD endpoints and test-rules (their handler bodies, routes, api-docs rows, and the dead collections.ts island functions — the file's LIVE halves, the collection modal/navigate/ grid-refresh, stay; the dead rules half went). match-mode endpoint and preview survive (legacy channel + the dashboard custom-section-builder, flagged for its own migration). Contract-pin changelog documents the deletions per the asymmetry rule. The builder component gains collection mounts: scopeKey (the row universe for counts/apply), data-tree prefill, treeJSON (the apply wire with scope riding the top level), and an apply hook (the page owns the write; All-Books never sets one). ensureReadyRule defaults to the scope's first field instead of hardcoding author. Tests migrated onto trees: TestSmartCollections' live-membership / reject-mutations / include-auto / system-collection subtests now drive TREE collections; the deleted-endpoint subtests die with them.
This commit is contained in:
1 parent
eb68672390
commit
4f71eda6be
16 files changed
+518
-1422
No files matched your search
@@ -869,31 +869,11 @@ func TestPodcastSmartCollections(t *testing.T) {
|
||||
assert.Equal(t, http.StatusForbidden, code, "only the owner flips the mode: %s", string(body))
|
||||
})
|
||||
|
||||
t.Run("test_rules_preview_honors_mode", func(t *testing.T) {
|
||||
// The rules page preview speaks the same evaluator: all-mode
|
||||
// requires every rule (aNew passes feed∧unplayed∧<7d; aOld
|
||||
// fails published_within).
|
||||
code, body := podcastAuthed(t, setup, regToken, "POST", "/api/collections/test-rules",
|
||||
`{"match_mode":"all","rules":[
|
||||
{"field":"feed","operator":"in","value":"`+feedAID+`"},
|
||||
{"field":"played","operator":"equals","value":"false"},
|
||||
{"field":"published_within","operator":"greater_than","value":"7"}
|
||||
]}`)
|
||||
require.Equal(t, http.StatusOK, code, "%s", string(body))
|
||||
assert.Contains(t, string(body), "SmartColl A New")
|
||||
assert.NotContains(t, string(body), "SmartColl A Old")
|
||||
})
|
||||
|
||||
t.Run("downloaded_and_contains_rules", func(t *testing.T) {
|
||||
code, body := podcastAuthed(t, setup, regToken, "POST", "/api/collections/test-rules",
|
||||
`{"match_mode":"all","rules":[
|
||||
{"field":"downloaded","operator":"equals","value":"true"},
|
||||
{"field":"title","operator":"contains","value":"SmartColl A"}
|
||||
]}`)
|
||||
require.Equal(t, http.StatusOK, code, "%s", string(body))
|
||||
assert.Contains(t, string(body), "SmartColl A New")
|
||||
assert.Contains(t, string(body), "SmartColl A Played", "played but downloaded — the downloaded rule alone doesn't exclude it")
|
||||
assert.NotContains(t, string(body), "SmartColl B Ep")
|
||||
t.Run("match_mode_endpoint_still_serves_legacy", func(t *testing.T) {
|
||||
// Item 3.4 retired the rules PAGE, not the legacy channel: the
|
||||
// match-mode endpoint keeps serving rules-array collections.
|
||||
code, body := podcastAuthed(t, setup, regToken, "PUT", "/api/collections/"+collID+"/match-mode", `{"mode":"any"}`)
|
||||
assert.Equal(t, http.StatusOK, code, "%s", string(body))
|
||||
})
|
||||
|
||||
// ---- ACCEPTANCE (plan item 2.5): the briefing PLAYS back-to-back ----
|
||||
|
||||
Reference in new issue
Block a user