diff --git a/rust-api/src/database.rs b/rust-api/src/database.rs index 55dd6058..7b30a0e9 100644 --- a/rust-api/src/database.rs +++ b/rust-api/src/database.rs @@ -12257,7 +12257,7 @@ impl DatabasePool { "Episodes".episodeid = "DownloadedEpisodes".episodeid AND "DownloadedEpisodes".userid = $1 WHERE "Podcasts".userid = $1 AND "Podcasts".podcastid = ANY($2){ep_search_clause} - ORDER BY {pg_order_col} {order_dir} NULLS LAST + ORDER BY {pg_order_col} {order_dir} NULLS LAST, "Episodeid" LIMIT {pb_lim_pos}) UNION ALL @@ -12296,10 +12296,10 @@ impl DatabasePool { "YouTubeVideos".videoid = "DownloadedVideos".videoid AND "DownloadedVideos".userid = $1 WHERE "Podcasts".userid = $1 AND "Podcasts".podcastid = ANY($2){yt_search_clause} - ORDER BY {pg_order_col} {order_dir} NULLS LAST + ORDER BY {pg_order_col} {order_dir} NULLS LAST, "Episodeid" LIMIT {pb_lim_pos}) ) combined{filter_clause} - ORDER BY {pg_order_col} {order_dir} NULLS LAST + ORDER BY {pg_order_col} {order_dir} NULLS LAST, is_youtube, "Episodeid" LIMIT {outer_lim_pos} OFFSET {outer_off_pos}"#, ep_search_clause = ep_search_clause, yt_search_clause = yt_search_clause, @@ -12482,7 +12482,7 @@ impl DatabasePool { Episodes.EpisodeID = DownloadedEpisodes.EpisodeID AND DownloadedEpisodes.UserID = ? WHERE Podcasts.UserID = ? AND Podcasts.PodcastID IN ({ph}){ep_search_clause} - ORDER BY {my_order_col} {order_dir} + ORDER BY {my_order_col} {order_dir}, Episodeid LIMIT ?) UNION ALL @@ -12521,10 +12521,10 @@ impl DatabasePool { YouTubeVideos.VideoID = DownloadedVideos.VideoID AND DownloadedVideos.UserID = ? WHERE Podcasts.UserID = ? AND Podcasts.PodcastID IN ({ph}){yt_search_clause} - ORDER BY {my_order_col} {order_dir} + ORDER BY {my_order_col} {order_dir}, Episodeid LIMIT ?) ) combined{mysql_filter} - ORDER BY {my_order_col} {order_dir} + ORDER BY {my_order_col} {order_dir}, is_youtube, Episodeid LIMIT ? OFFSET ?", ph = id_placeholders, ep_search_clause = ep_search_clause, diff --git a/rust-api/tests/integration/podcasts.rs b/rust-api/tests/integration/podcasts.rs index ede6fff5..a4b51c2e 100644 --- a/rust-api/tests/integration/podcasts.rs +++ b/rust-api/tests/integration/podcasts.rs @@ -401,3 +401,43 @@ async fn podcast_list_reports_each_podcasts_oldest_and_newest_episode_dates() { let active = extra["pods"].as_array().unwrap().iter().find(|p| p["podcastname"] == "Active").unwrap(); assert_eq!(active["oldest_episode_date"].as_str(), Some(active_old.as_str())); } + +#[tokio::test(flavor = "multi_thread")] +async fn paging_through_tied_episodes_shows_each_one_once() { + let app = TestApp::new().await; + let user = app.user("binger").await; + // A back catalogue published all at once: every episode has the same date and length. + let when = chrono::Utc::now() - chrono::Duration::days(30); + let mut feed = Feed::new("Bulk Upload"); + for n in 1..=60 { + feed = feed.episode(Episode::new(n).title("Same Title").pub_date(when).duration(Some(1800))); + } + let url = app.feed("tied.xml", &feed); + let pod = app.subscribe(&user, &url, 60).await; + + for sort_by in ["date", "duration", "title"] { + for order in ["asc", "desc"] { + let mut seen = Vec::new(); + for offset in (0..60).step_by(7) { + let body = app + .get("/api/data/podcast_episodes") + .key(&user) + .query("user_id", user.id) + .query("podcast_id", pod) + .query("limit", 7) + .query("offset", offset) + .query("sort_by", sort_by) + .query("sort_order", order) + .query("filter", "all") + .send() + .await + .ok_json(); + seen.extend(body["episodes"].as_array().unwrap().iter().map(|e| e["Episodeid"].as_i64().unwrap())); + } + let mut unique = seen.clone(); + unique.sort(); + unique.dedup(); + assert_eq!((seen.len(), unique.len()), (60, 60), "{sort_by} {order}: pages repeated or skipped episodes"); + } + } +}