use super::*;
#[test]
fn get_playlists_maps_entries_and_skips_missing_ids() {
let page1 = serde_json::json!({
"playlists": [
{"id": "pl1", "name": "Road Trip", "num_total_results": 12},
{"id": "", "name": "No Id", "num_total_results": 3},
{"name": "Also No Id"}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/me?page=1", 200, page1));
rules.push(Rule::new(
"/api/playlist/me?page=2",
200,
r#"{"playlists": []}"#.to_string(),
));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let playlists = pollster::block_on(client.get_playlists(&http)).unwrap();
assert_eq!(playlists.len(), 1, "entries without an id are dropped");
assert_eq!(
playlists[0],
Playlist {
id: "pl1".to_owned(),
name: "Road Trip".to_owned(),
num_clips: 12,
}
);
}
#[test]
fn get_playlists_defaults_a_missing_name_to_untitled() {
let page1 = serde_json::json!({
"playlists": [{"id": "pl9", "num_total_results": 1}]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/me?page=1", 200, page1));
rules.push(Rule::new(
"/api/playlist/me?page=2",
200,
r#"{"playlists": []}"#.to_string(),
));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let playlists = pollster::block_on(client.get_playlists(&http)).unwrap();
assert_eq!(playlists[0].name, "Untitled");
}
#[test]
fn get_playlist_clips_preserves_order_and_unwraps_clip() {
let body = serde_json::json!({
"num_total_results": 2,
"playlist_clips": [
{"clip": {
"id": "second", "title": "Second", "status": "complete",
"metadata": {"duration": 60.0, "type": "gen"}
}},
{"clip": {
"id": "first", "title": "First", "status": "complete",
"metadata": {"duration": 30.0, "task": "infill", "type": "gen"}
}}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/pl1/", 200, body));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) = pollster::block_on(client.get_playlist_clips(&http, "pl1")).unwrap();
assert_eq!(clips.len(), 2, "an infill member is not filtered out");
assert_eq!(clips[0].id, "second");
assert_eq!(clips[1].id, "first");
assert!(
complete,
"returned == num_total_results is fully enumerated"
);
}
#[test]
fn get_playlist_clips_short_page_is_not_complete() {
let body = serde_json::json!({
"num_total_results": 5,
"playlist_clips": [
{"clip": {
"id": "only", "title": "Only", "status": "complete",
"metadata": {"duration": 60.0, "type": "gen"}
}}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/pl1/", 200, body));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) = pollster::block_on(client.get_playlist_clips(&http, "pl1")).unwrap();
assert_eq!(clips.len(), 1);
assert!(!complete, "a short page is not fully enumerated");
}
#[test]
fn get_playlist_clips_is_empty_for_a_playlist_with_no_members() {
let mut rules = auth_rules();
rules.push(Rule::new(
"/api/playlist/empty/",
200,
r#"{"num_total_results": 0, "playlist_clips": []}"#.to_string(),
));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) = pollster::block_on(client.get_playlist_clips(&http, "empty")).unwrap();
assert!(clips.is_empty());
assert!(
complete,
"an empty playlist reporting zero total is complete"
);
}
#[test]
fn get_playlist_clips_missing_total_is_not_complete() {
let mut rules = auth_rules();
rules.push(Rule::new(
"/api/playlist/pl1/",
200,
r#"{"playlist_clips": []}"#.to_string(),
));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) = pollster::block_on(client.get_playlist_clips(&http, "pl1")).unwrap();
assert!(clips.is_empty());
assert!(!complete, "a missing total is never fully enumerated");
}
#[test]
fn get_playlist_clips_dropped_member_disarms_authority() {
let missing_id = serde_json::json!({
"num_total_results": 2,
"playlist_clips": [
{"clip": {
"id": "a", "title": "A", "status": "complete",
"metadata": {"duration": 60.0, "type": "gen"}
}},
{"clip": {
"title": "No Id", "status": "complete",
"metadata": {"duration": 30.0, "type": "gen"}
}}
]
})
.to_string();
let empty_id = serde_json::json!({
"num_total_results": 2,
"playlist_clips": [
{"clip": {
"id": "a", "title": "A", "status": "complete",
"metadata": {"duration": 60.0, "type": "gen"}
}},
{"clip": {
"id": "", "title": "Empty Id", "status": "complete",
"metadata": {"duration": 30.0, "type": "gen"}
}}
]
})
.to_string();
for body in [missing_id, empty_id] {
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/pl1/", 200, body));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) =
pollster::block_on(client.get_playlist_clips(&http, "pl1")).unwrap();
assert_eq!(clips.len(), 1, "the member with no id is dropped");
assert!(
!complete,
"a dropped member disarms authority even when raw_len == total"
);
}
}
#[test]
fn get_playlist_clips_over_count_is_not_complete() {
let body = serde_json::json!({
"num_total_results": 2,
"playlist_clips": [
{"clip": {
"id": "a", "title": "A", "status": "complete",
"metadata": {"duration": 60.0, "type": "gen"}
}},
{"clip": {
"id": "b", "title": "B", "status": "complete",
"metadata": {"duration": 30.0, "type": "gen"}
}},
{"clip": {
"id": "", "title": "Empty Id", "status": "complete",
"metadata": {"duration": 45.0, "type": "gen"}
}}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/pl1/", 200, body));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) = pollster::block_on(client.get_playlist_clips(&http, "pl1")).unwrap();
assert_eq!(clips.len(), 2, "the empty-id member is dropped");
assert!(
!complete,
"raw_len (3) diverging from the total (2) is not authoritative"
);
}
#[test]
fn get_playlist_clips_ignores_song_count() {
let body = serde_json::json!({
"num_total_results": 1,
"song_count": 0,
"playlist_clips": [
{"clip": {
"id": "only", "title": "Only", "status": "complete",
"metadata": {"duration": 60.0, "type": "gen"}
}}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/pl1/", 200, body));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) = pollster::block_on(client.get_playlist_clips(&http, "pl1")).unwrap();
assert_eq!(clips.len(), 1);
assert!(
complete,
"completeness uses num_total_results, not song_count"
);
}
#[test]
fn get_playlists_num_clips_ignores_song_count() {
let page1 = serde_json::json!({
"playlists": [
{"id": "pl1", "name": "Road Trip", "num_total_results": 15, "song_count": 0}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/me?page=1", 200, page1));
rules.push(Rule::new(
"/api/playlist/me?page=2",
200,
r#"{"playlists": []}"#.to_string(),
));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let playlists = pollster::block_on(client.get_playlists(&http)).unwrap();
assert_eq!(
playlists[0].num_clips, 15,
"num_clips reads num_total_results, not song_count"
);
}
#[test]
fn get_playlists_dedupes_a_page_ignoring_server() {
let same_body = serde_json::json!({
"playlists": [
{"id": "pl1", "name": "Road Trip", "num_total_results": 12},
{"id": "pl2", "name": "Chill", "num_total_results": 7}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/me", 200, same_body));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let playlists = pollster::block_on(client.get_playlists(&http)).unwrap();
assert_eq!(
playlists.len(),
2,
"duplicates from a page-ignoring server are collapsed"
);
assert_eq!(playlists[0].id, "pl1");
assert_eq!(playlists[1].id, "pl2");
}
#[test]
fn get_playlist_clips_preserves_array_order_over_created_at() {
let body = serde_json::json!({
"num_total_results": 3,
"playlist_clips": [
{"clip": {
"id": "a", "title": "A", "status": "complete",
"metadata": {"duration": 60.0, "type": "gen"}
}, "relative_index": 1.0, "created_at": "2026-06-08T00:00:00.000Z"},
{"clip": {
"id": "b", "title": "B", "status": "complete",
"metadata": {"duration": 30.0, "type": "gen"}
}, "relative_index": 2.0, "created_at": "2026-01-11T00:00:00.000Z"},
{"clip": {
"id": "c", "title": "C", "status": "complete",
"metadata": {"duration": 45.0, "type": "gen"}
}, "relative_index": 3.0, "created_at": "2026-05-15T00:00:00.000Z"}
]
})
.to_string();
let mut rules = auth_rules();
rules.push(Rule::new("/api/playlist/pl1/", 200, body));
let http = MockHttp::new(rules);
let client = authed_client(&http);
let (clips, complete) = pollster::block_on(client.get_playlist_clips(&http, "pl1")).unwrap();
assert_eq!(
clips.iter().map(|c| c.id.as_str()).collect::<Vec<_>>(),
["a", "b", "c"],
"array order is preserved despite non-monotonic created_at"
);
assert!(complete, "three intact members equal the declared total");
}