use cider_api::CiderClient;
fn live_client() -> Option<CiderClient> {
let port: u16 = std::env::var("CIDER_TEST_PORT").ok()?.parse().ok()?;
let mut client = CiderClient::with_port(port);
if let Ok(token) = std::env::var("CIDER_TEST_TOKEN") {
client = client.with_token(token);
}
Some(client)
}
macro_rules! skip_unless_live {
() => {
match live_client() {
Some(c) => c,
None => {
eprintln!("Skipping: CIDER_TEST_PORT not set");
return;
}
}
};
}
#[tokio::test]
async fn live_is_active() {
let client = skip_unless_live!();
client.is_active().await.unwrap();
}
#[tokio::test]
async fn live_is_playing() {
let client = skip_unless_live!();
let _ = client.is_playing().await.unwrap();
}
#[tokio::test]
async fn live_now_playing() {
let client = skip_unless_live!();
let result = client.now_playing().await.unwrap();
if let Some(track) = result {
assert!(!track.name.is_empty());
eprintln!("Now playing: {} - {}", track.name, track.artist_name);
let _ms = track.current_position_ms();
let _url = track.artwork_url(300);
let _id = track.song_id();
}
}
#[tokio::test]
async fn live_play_pause_cycle() {
let client = skip_unless_live!();
let was_playing = client.is_playing().await.unwrap();
client.pause().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
assert!(!client.is_playing().await.unwrap());
client.play().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
assert!(client.is_playing().await.unwrap());
client.play_pause().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let after_toggle = client.is_playing().await.unwrap();
client.play_pause().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let after_second_toggle = client.is_playing().await.unwrap();
assert_ne!(after_toggle, after_second_toggle);
if was_playing {
client.play().await.unwrap();
} else {
client.pause().await.unwrap();
}
}
#[tokio::test]
async fn live_next_previous() {
let client = skip_unless_live!();
let before = client.now_playing().await.unwrap();
client.next().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
client.previous().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let _ = client.now_playing().await.unwrap();
let _ = before; }
#[tokio::test]
async fn live_seek() {
let client = skip_unless_live!();
client.seek(10.0).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
if let Some(track) = client.now_playing().await.unwrap() {
let pos = track.current_playback_time;
assert!(pos >= 9.0 && pos <= 15.0, "Position was {pos}s after seeking to 10s");
}
client.seek_ms(5000).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
if let Some(track) = client.now_playing().await.unwrap() {
let pos = track.current_playback_time;
assert!(pos >= 4.0 && pos <= 10.0, "Position was {pos}s after seeking to 5s");
}
}
#[tokio::test]
async fn live_volume_set_and_restore() {
let client = skip_unless_live!();
let original = client.get_volume().await.unwrap();
assert!((0.0..=1.0).contains(&original));
client.set_volume(0.42).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let after = client.get_volume().await.unwrap();
assert!(
(after - 0.42).abs() < 0.05,
"Volume was {after} after setting to 0.42"
);
client.set_volume(original).await.unwrap();
}
#[tokio::test]
async fn live_get_queue() {
let client = skip_unless_live!();
let queue = client.get_queue().await.unwrap();
eprintln!("Queue has {} items", queue.len());
if !queue.is_empty() {
let current_count = queue.iter().filter(|i| i.is_current()).count();
eprintln!("Current items: {current_count}");
for (i, item) in queue.iter().enumerate() {
if let Some(attrs) = &item.attributes {
eprintln!(" [{i}] {} - {} {}", attrs.name, attrs.artist_name,
if item.is_current() { "(current)" } else { "" });
}
}
}
}
#[tokio::test]
async fn live_repeat_mode_toggle_and_restore() {
let client = skip_unless_live!();
let original = client.get_repeat_mode().await.unwrap();
assert!(original <= 2);
client.toggle_repeat().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let after_1 = client.get_repeat_mode().await.unwrap();
client.toggle_repeat().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let after_2 = client.get_repeat_mode().await.unwrap();
client.toggle_repeat().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let after_3 = client.get_repeat_mode().await.unwrap();
eprintln!("Repeat cycle: {original} -> {after_1} -> {after_2} -> {after_3}");
assert_eq!(after_3, original, "Three toggles should return to original mode");
}
#[tokio::test]
async fn live_shuffle_mode_toggle_and_restore() {
let client = skip_unless_live!();
let original = client.get_shuffle_mode().await.unwrap();
assert!(original <= 1);
client.toggle_shuffle().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let toggled = client.get_shuffle_mode().await.unwrap();
assert_ne!(original, toggled, "Shuffle should have changed");
client.toggle_shuffle().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let restored = client.get_shuffle_mode().await.unwrap();
assert_eq!(original, restored, "Shuffle should be back to original");
}
#[tokio::test]
async fn live_autoplay_toggle_and_restore() {
let client = skip_unless_live!();
let original = client.get_autoplay().await.unwrap();
client.toggle_autoplay().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let toggled = client.get_autoplay().await.unwrap();
assert_ne!(original, toggled, "Autoplay should have changed");
client.toggle_autoplay().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let restored = client.get_autoplay().await.unwrap();
assert_eq!(original, restored, "Autoplay should be back to original");
}
#[tokio::test]
async fn live_add_to_library() {
let client = skip_unless_live!();
if client.now_playing().await.unwrap().is_some() {
client.add_to_library().await.unwrap();
}
}
#[tokio::test]
async fn live_set_rating_and_clear() {
let client = skip_unless_live!();
if client.now_playing().await.unwrap().is_some() {
client.set_rating(1).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
client.set_rating(0).await.unwrap();
}
}
#[tokio::test]
async fn live_valid_token_is_accepted() {
let client = skip_unless_live!();
client.is_active().await.unwrap();
}
#[tokio::test]
async fn live_bad_token_is_rejected() {
let port: u16 = match std::env::var("CIDER_TEST_PORT").ok().and_then(|p| p.parse().ok()) {
Some(p) => p,
None => {
eprintln!("Skipping: CIDER_TEST_PORT not set");
return;
}
};
if std::env::var("CIDER_TEST_TOKEN").is_err() {
eprintln!("Skipping: CIDER_TEST_TOKEN not set, can't test auth rejection");
return;
}
let bad_client = CiderClient::with_port(port).with_token("definitely-wrong-token");
let result = bad_client.play().await;
assert!(
result.is_err(),
"Expected error with bad token, got: {result:?}"
);
}
#[tokio::test]
async fn live_amapi_run_v3() {
let client = skip_unless_live!();
let result = client
.amapi_run_v3("/v1/catalog/us/search?term=flume&types=songs&limit=1")
.await
.unwrap();
assert!(result.is_object(), "Expected JSON object, got: {result}");
eprintln!("AMAPI response keys: {:?}", result.as_object().map(|o| o.keys().collect::<Vec<_>>()));
}
#[tokio::test]
async fn live_stop_and_resume() {
let client = skip_unless_live!();
let was_playing = client.is_playing().await.unwrap();
if !was_playing {
eprintln!("Skipping stop test: nothing playing");
return;
}
client.stop().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
assert!(!client.is_playing().await.unwrap());
client.play().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
}