//! Live validation against a real audiobookshelf server. `#[ignore]`d so it //! never runs in CI or hits the network by default; run it deliberately with //! //! ```sh //! ABS_BASE_URL=https://host ABS_API_KEY= \ //! cargo test -p absdy --test live -- --ignored --nocapture //! ``` //! //! It exercises the real endpoints end-to-end (libraries → items → search → //! detail) so the `AbsApi` DTOs are confirmed against the live JSON shapes — //! the drift risk called out in architecture/audiobookshelf-provider.md. use std::time::Duration; use absdy::api::{Abs, AbsApi}; fn creds() -> Option<(String, String)> { let base = std::env::var("ABS_BASE_URL").ok()?; let key = std::env::var("ABS_API_KEY").ok()?; (!base.is_empty() && !key.is_empty()).then_some((base, key)) } #[tokio::test] #[ignore = "hits a real audiobookshelf server; set ABS_BASE_URL and ABS_API_KEY"] async fn live_browse_search_and_detail() { let Some((base, key)) = creds() else { eprintln!("ABS_BASE_URL / ABS_API_KEY unset — skipping live test"); return; }; let api = AbsApi::new(base, key, Duration::from_secs(30)).expect("client"); let libraries = api.libraries().await.expect("libraries decode"); assert!(!libraries.is_empty(), "server has at least one library"); let book_lib = libraries .iter() .find(|l| l.is_book) .expect("at least one book library"); println!("book library: {} ({})", book_lib.name, book_lib.id); let items = api .library_items(&book_lib.id, 5) .await .expect("items decode"); assert!(!items.is_empty(), "book library has items"); let with_audio = items .iter() .find(|b| b.num_audio_files > 0) .expect("an audiobook item"); println!( "item: {} by {} ({} files)", with_audio.title, with_audio.author, with_audio.num_audio_files ); let detail = api .item_detail(&with_audio.id) .await .expect("detail decode"); assert!(!detail.tracks.is_empty(), "audiobook has tracks"); let first = &detail.tracks[0]; println!("track0: ino={} title={}", first.ino, first.title); // The stream URL is fully derivable and carries the token. let url = api.stream_url(&with_audio.id, &first.ino); assert!(url.contains(&format!("/api/items/{}/file/{}", with_audio.id, first.ino))); assert!(url.contains("token=")); // Search should decode too (term may legitimately match nothing). let hits = api .search_items(&book_lib.id, "a", 3) .await .expect("search decode"); println!("search 'a' -> {} hits", hits.len()); }