use shanty_db::Database; use shanty_search::cache; use shanty_search::error::SearchResult; use shanty_search::provider::*; /// Mock search provider for testing. struct MockSearch; impl SearchProvider for MockSearch { async fn search_artist(&self, query: &str, _limit: u32) -> SearchResult> { if query.contains("Pink Floyd") { Ok(vec![ArtistResult { id: "83d91898-7763-47d7-b03b-b92132375c47".into(), name: "Pink Floyd".into(), disambiguation: Some("English rock band".into()), country: Some("GB".into()), artist_type: Some("Group".into()), score: 100, }]) } else { Ok(vec![]) } } async fn search_album( &self, query: &str, _artist_hint: Option<&str>, _limit: u32, ) -> SearchResult> { if query.contains("Dark Side") { Ok(vec![AlbumResult { id: "release-123".into(), title: "The Dark Side of the Moon".into(), artist: "Pink Floyd".into(), artist_id: Some("83d91898".into()), year: Some("1973".into()), track_count: Some(10), score: 100, }]) } else { Ok(vec![]) } } async fn search_track( &self, query: &str, _artist_hint: Option<&str>, _limit: u32, ) -> SearchResult> { if query.contains("Time") { Ok(vec![TrackResult { id: "rec-456".into(), title: "Time".into(), artist: "Pink Floyd".into(), artist_id: Some("83d91898".into()), album: Some("The Dark Side of the Moon".into()), duration_ms: Some(413_000), score: 100, }]) } else { Ok(vec![]) } } async fn get_discography(&self, _artist_id: &str) -> SearchResult { Ok(Discography { artist_name: "Pink Floyd".into(), artist_id: "83d91898".into(), releases: vec![ DiscographyEntry { id: "r1".into(), title: "The Dark Side of the Moon".into(), date: Some("1973-03-01".into()), release_type: Some("Album".into()), track_count: Some(10), }, DiscographyEntry { id: "r2".into(), title: "Wish You Were Here".into(), date: Some("1975-09-12".into()), release_type: Some("Album".into()), track_count: Some(5), }, ], }) } async fn get_release_groups(&self, _artist_id: &str) -> SearchResult> { Ok(vec![]) } } #[tokio::test] async fn test_search_artist() { let provider = MockSearch; let results = provider.search_artist("Pink Floyd", 10).await.unwrap(); assert_eq!(results.len(), 1); assert_eq!(results[0].name, "Pink Floyd"); assert_eq!(results[0].country.as_deref(), Some("GB")); } #[tokio::test] async fn test_search_artist_no_results() { let provider = MockSearch; let results = provider.search_artist("Nonexistent Band", 10).await.unwrap(); assert!(results.is_empty()); } #[tokio::test] async fn test_search_album() { let provider = MockSearch; let results = provider.search_album("Dark Side", Some("Pink Floyd"), 10).await.unwrap(); assert_eq!(results.len(), 1); assert_eq!(results[0].title, "The Dark Side of the Moon"); } #[tokio::test] async fn test_search_track() { let provider = MockSearch; let results = provider.search_track("Time", Some("Pink Floyd"), 10).await.unwrap(); assert_eq!(results.len(), 1); assert_eq!(results[0].title, "Time"); assert_eq!(results[0].album.as_deref(), Some("The Dark Side of the Moon")); } #[tokio::test] async fn test_discography() { let provider = MockSearch; let disco = provider.get_discography("83d91898").await.unwrap(); assert_eq!(disco.artist_name, "Pink Floyd"); assert_eq!(disco.releases.len(), 2); } #[tokio::test] async fn test_result_serialization() { let result = ArtistResult { id: "test-id".into(), name: "Test Artist".into(), disambiguation: None, country: Some("US".into()), artist_type: Some("Person".into()), score: 95, }; let json = serde_json::to_string(&result).unwrap(); let back: ArtistResult = serde_json::from_str(&json).unwrap(); assert_eq!(back.name, "Test Artist"); assert_eq!(back.score, 95); } #[tokio::test] async fn test_cache_roundtrip() { let db = Database::new("sqlite::memory:").await.unwrap(); let results = vec![ArtistResult { id: "123".into(), name: "Cached Artist".into(), disambiguation: None, country: None, artist_type: None, score: 90, }]; // Cache miss let cached: Option> = cache::get_cached(Some(db.conn()), "test:artist:query").await.unwrap(); assert!(cached.is_none()); // Store cache::set_cached(Some(db.conn()), "test:artist:query", "musicbrainz", &results) .await .unwrap(); // Cache hit let cached: Option> = cache::get_cached(Some(db.conn()), "test:artist:query").await.unwrap(); assert!(cached.is_some()); assert_eq!(cached.unwrap()[0].name, "Cached Artist"); } #[tokio::test] async fn test_cache_none_conn() { // With no DB connection, caching is a no-op let cached: Option> = cache::get_cached(None, "anything").await.unwrap(); assert!(cached.is_none()); // set_cached with None conn should not error cache::set_cached::>(None, "key", "provider", &vec![]) .await .unwrap(); }