feat(podcasts): Download All queues in the LISTENING order + add-modal order dropdown
Download All previously queued in feed-native order — 'getting the last chapter first doesn't help me'. Now the queue follows the feed's effective listening order (§0 item 5b): QueueFeedDownloadsAs reads the CALLER's order override and runs episodes through services.OrderEpisodes (same helper as the sequence/reader/page surfaces — the consolidation from item 5a landed with the reverse-order commit). Queue tickets (SEQUENCE-stamped, item 4) make that enqueue order the claim order, so chapters land in the order you'll actually listen. Add modal (§0 item 5c): with 'Download all episodes now' checked, a 'Download order' select appears (pure-CSS :checked sibling reveal — no JS island, CSP-clean). Its choice is the feed's INITIAL order override (reader_settings upsert; 'feed' = no row) AND the queue order for the initial batch. Per-book by design: the override IS the download order. Test TestPodcastAddModalDownloadOrder: add with download_all+reverse → the override row exists AND persisted tickets run position 3→1. Tickets are asserted (not live queued rows) because the shared dev server's worker legitimately races this test's queue — tickets survive every state flip.
This commit is contained in:
1 parent
0f3c4a8cec
commit
5c545534c2
7 files changed
+176
-9
No files matched your search
@@ -1159,3 +1159,84 @@ func TestLogoutClearsSession(t *testing.T) {
|
||||
}
|
||||
assert.True(t, cleared, "cookie clear must not depend on the request body")
|
||||
}
|
||||
|
||||
// ---- §0 item 5: add-modal Download order — override + queue order ----
|
||||
|
||||
func TestPodcastAddModalDownloadOrder(t *testing.T) {
|
||||
setup := setupTestServer(t)
|
||||
lib, _ := createPodcastsLibrary(t, setup, "test AddOrder Podcasts")
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/feed.xml", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/rss+xml")
|
||||
_, _ = w.Write([]byte(podcastFixture("AddOrder Feed", map[string]string{
|
||||
"ao-1": "AddOrder One", "ao-2": "AddOrder Two", "ao-3": "AddOrder Three",
|
||||
})))
|
||||
})
|
||||
srv := swapFeedServer(t, mux.ServeHTTP)
|
||||
|
||||
form := url.Values{}
|
||||
form.Set("library_id", uuid.UUID(lib.ID.Bytes).String())
|
||||
form.Set("url", srv.URL+"/feed.xml")
|
||||
form.Set("download_all", "true")
|
||||
form.Set("download_order", "reverse") // the add-modal dropdown (§0 item 5c)
|
||||
resp, body := podcastAuthedForm(t, setup, setup.RegularToken, "POST", "/api/podcasts/feeds", form)
|
||||
require.Equal(t, http.StatusCreated, resp, string(body))
|
||||
|
||||
regularID := getRegularUserID(t, setup.DB)
|
||||
regularPG := pgtype.UUID{Bytes: regularID, Valid: true}
|
||||
rctx := rls.WithUser(context.Background(), regularPG)
|
||||
workerCtx := adminSeedCtx(t, setup)
|
||||
|
||||
feeds, _ := setup.DB.ListPodcastFeedsForPolling(rctx)
|
||||
var feedID pgtype.UUID
|
||||
for _, f := range feeds {
|
||||
if f.Title == "AddOrder Feed" {
|
||||
feedID = f.ID
|
||||
}
|
||||
}
|
||||
require.True(t, feedID.Valid, "subscribed feed must exist")
|
||||
|
||||
// PIN 1: the override row exists (the dropdown IS the initial order).
|
||||
row, err := setup.DB.GetUserSettingByKey(rctx, database.GetUserSettingByKeyParams{
|
||||
UserID: regularPG, SettingKey: "podcast_order:" + feedID.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(row), "reverse", "PIN: the add-modal dropdown sets the caller's order override")
|
||||
|
||||
// PIN 2: the queue follows the LISTENING order — with reverse, the
|
||||
// tickets run position 3 → 1 (claim order = download order). Read
|
||||
// the PERSISTED tickets rather than live queued rows: the shared
|
||||
// dev server's worker races this test's queue and flips rows to
|
||||
// failed (its container can't reach the fixture URLs) — tickets
|
||||
// survive every state flip.
|
||||
var tickets []struct {
|
||||
ID string
|
||||
Pos int64
|
||||
}
|
||||
adminScope := database.NewScopedDB(setup.DBPool)
|
||||
ticketRows, err := adminScope.Query(workerCtx,
|
||||
`SELECT id::TEXT, download_queue_pos FROM media_items
|
||||
WHERE source_feed_id = $1 AND download_queue_pos IS NOT NULL
|
||||
ORDER BY download_queue_pos ASC`, feedID)
|
||||
require.NoError(t, err)
|
||||
for ticketRows.Next() {
|
||||
var id string
|
||||
var pos int64
|
||||
require.NoError(t, ticketRows.Scan(&id, &pos), "scan ticket")
|
||||
tickets = append(tickets, struct {
|
||||
ID string
|
||||
Pos int64
|
||||
}{id, pos})
|
||||
}
|
||||
ticketRows.Close()
|
||||
require.Len(t, tickets, 3, "all 3 episodes got tickets")
|
||||
eps, err := setup.DB.ListPodcastEpisodes(workerCtx, feedID)
|
||||
require.NoError(t, err)
|
||||
byID := map[string]int32{}
|
||||
for _, ep := range eps {
|
||||
byID[uuid.UUID(ep.ID.Bytes).String()] = ep.FeedPosition.Int32
|
||||
}
|
||||
assert.Equal(t, int32(3), byID[tickets[0].ID], "PIN: reverse download order — position 3 claims first")
|
||||
assert.Equal(t, int32(1), byID[tickets[2].ID])
|
||||
}
|
||||
Reference in new issue
Block a user