diff --git a/src-tauri/src/repository/online_integration_test.rs b/src-tauri/src/repository/online_integration_test.rs deleted file mode 100644 index 7fdf9649f..000000000 --- a/src-tauri/src/repository/online_integration_test.rs +++ /dev/null @@ -1,429 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::api::jellyfin::{ - GetItemsOptions, ImageType, ImageOptions, SortOrder, - }; - - /// Mock for testing URL construction without a real server - struct MockOnlineRepository { - server_url: String, - access_token: String, - } - - impl MockOnlineRepository { - fn new(server_url: &str, access_token: &str) -> Self { - Self { - server_url: server_url.to_string(), - access_token: access_token.to_string(), - } - } - - /// Test helper: construct image URL similar to backend - fn get_image_url( - &self, - item_id: &str, - image_type: &str, - options: Option<&ImageOptions>, - ) -> String { - let mut url = format!( - "{}/Items/{}/Images/{}", - self.server_url, item_id, image_type - ); - - // No api_key — image downloads use X-Emby-Authorization header - let mut params: Vec<(&str, String)> = Vec::new(); - - if let Some(opts) = options { - if let Some(max_width) = opts.max_width { - params.push(("maxWidth", max_width.to_string())); - } - if let Some(max_height) = opts.max_height { - params.push(("maxHeight", max_height.to_string())); - } - if let Some(quality) = opts.quality { - params.push(("quality", quality.to_string())); - } - if let Some(tag) = &opts.tag { - params.push(("tag", tag.clone())); - } - } - - let query_string = params - .iter() - .map(|(k, v)| format!("{}={}", k, v)) - .collect::>() - .join("&"); - - if !query_string.is_empty() { - url.push('?'); - url.push_str(&query_string); - } - - url - } - - /// Test helper: construct subtitle URL - fn get_subtitle_url( - &self, - item_id: &str, - media_source_id: &str, - stream_index: usize, - format: &str, - ) -> String { - format!( - "{}/Videos/{}/Subtitles/{}/{}/subtitles.{}?api_key={}", - self.server_url, - item_id, - media_source_id, - stream_index, - format, - self.access_token - ) - } - - /// Test helper: construct video download URL - fn get_video_download_url( - &self, - item_id: &str, - quality: &str, - ) -> String { - let (max_width, bitrate) = match quality { - "1080p" => ("1920", "15000k"), - "720p" => ("1280", "8000k"), - "480p" => ("854", "3000k"), - _ => ("0", ""), // original - }; - - if quality == "original" { - format!("{}/Videos/{}/stream.mp4?api_key={}", self.server_url, item_id, self.access_token) - } else { - format!( - "{}/Videos/{}/stream.mp4?maxWidth={}&videoBitrate={}&api_key={}", - self.server_url, item_id, max_width, bitrate, self.access_token - ) - } - } - } - - // ===== Image URL Tests ===== - - #[test] - fn test_image_url_basic() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let url = repo.get_image_url("item123", "Primary", None); - - assert!(url.contains("https://jellyfin.example.com")); - assert!(url.contains("/Items/item123/Images/Primary")); - assert!(url.contains("api_key=token123")); - } - - #[test] - fn test_image_url_with_max_width() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - let options = ImageOptions { - max_width: Some(300), - max_height: None, - quality: None, - tag: None, - }; - - let url = repo.get_image_url("item123", "Primary", Some(&options)); - - assert!(url.contains("maxWidth=300")); - assert!(url.contains("api_key=token123")); - } - - #[test] - fn test_image_url_with_all_options() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - let options = ImageOptions { - max_width: Some(1920), - max_height: Some(1080), - quality: Some(90), - tag: Some("abc123".to_string()), - }; - - let url = repo.get_image_url("item456", "Backdrop", Some(&options)); - - assert!(url.contains("/Items/item456/Images/Backdrop")); - assert!(url.contains("maxWidth=1920")); - assert!(url.contains("maxHeight=1080")); - assert!(url.contains("quality=90")); - assert!(url.contains("tag=abc123")); - assert!(url.contains("api_key=token123")); - } - - #[test] - fn test_image_url_different_image_types() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let image_types = vec!["Primary", "Backdrop", "Logo", "Thumb"]; - - for image_type in image_types { - let url = repo.get_image_url("item123", image_type, None); - assert!(url.contains(&format!("/Images/{}", image_type))); - } - } - - #[test] - fn test_image_url_credentials_included_in_backend() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "secret_token"); - - let url = repo.get_image_url("item123", "Primary", None); - - // Credentials should be included in backend-generated URL - assert!(url.contains("api_key=secret_token")); - } - - #[test] - fn test_image_url_proper_encoding() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - let options = ImageOptions { - max_width: Some(300), - max_height: None, - quality: None, - tag: Some("tag-with-special-chars".to_string()), - }; - - let url = repo.get_image_url("item123", "Primary", Some(&options)); - - // URL should be properly formatted - assert!(url.contains("?")); - assert!(url.contains("&") || !url.contains("&&")); // No double ampersands - assert!(!url.ends_with("&")); // No trailing ampersand - } - - // ===== Subtitle URL Tests ===== - - #[test] - fn test_subtitle_url_vtt_format() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let url = repo.get_subtitle_url("item123", "source456", 0, "vtt"); - - assert!(url.contains("Videos/item123")); - assert!(url.contains("Subtitles/source456/0")); - assert!(url.contains("subtitles.vtt")); - assert!(url.contains("api_key=token123")); - } - - #[test] - fn test_subtitle_url_srt_format() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let url = repo.get_subtitle_url("item123", "source456", 1, "srt"); - - assert!(url.contains("Subtitles/source456/1")); - assert!(url.contains("subtitles.srt")); - } - - #[test] - fn test_subtitle_url_multiple_streams() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - for stream_index in 0..5 { - let url = repo.get_subtitle_url("item123", "source456", stream_index, "vtt"); - assert!(url.contains(&format!("/{}/subtitles", stream_index))); - } - } - - #[test] - fn test_subtitle_url_different_media_sources() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let media_sources = vec!["src1", "src2", "src3"]; - - for media_source_id in media_sources { - let url = repo.get_subtitle_url("item123", media_source_id, 0, "vtt"); - assert!(url.contains(&format!("Subtitles/{}/", media_source_id))); - } - } - - // ===== Video Download URL Tests ===== - - #[test] - fn test_video_download_url_original_quality() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let url = repo.get_video_download_url("item123", "original"); - - assert!(url.contains("Videos/item123/stream.mp4")); - assert!(url.contains("api_key=token123")); - assert!(!url.contains("maxWidth")); // Original should have no transcoding params - } - - #[test] - fn test_video_download_url_1080p() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let url = repo.get_video_download_url("item123", "1080p"); - - assert!(url.contains("maxWidth=1920")); - assert!(url.contains("videoBitrate=15000k")); - } - - #[test] - fn test_video_download_url_720p() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let url = repo.get_video_download_url("item123", "720p"); - - assert!(url.contains("maxWidth=1280")); - assert!(url.contains("videoBitrate=8000k")); - } - - #[test] - fn test_video_download_url_480p() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let url = repo.get_video_download_url("item123", "480p"); - - assert!(url.contains("maxWidth=854")); - assert!(url.contains("videoBitrate=3000k")); - } - - #[test] - fn test_video_download_url_quality_presets() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - let qualities = vec!["original", "1080p", "720p", "480p"]; - - for quality in qualities { - let url = repo.get_video_download_url("item123", quality); - assert!(url.contains("Videos/item123/stream.mp4")); - } - } - - // ===== Security Tests ===== - - #[test] - fn test_credentials_never_exposed_in_frontend() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "super_secret_token"); - - let image_url = repo.get_image_url("item123", "Primary", None); - let subtitle_url = repo.get_subtitle_url("item123", "src123", 0, "vtt"); - let download_url = repo.get_video_download_url("item123", "720p"); - - // Image URLs no longer contain api_key — auth is via X-Emby-Authorization header - assert!(!image_url.contains("api_key=")); - // Subtitle and download URLs still use api_key (used directly, not via download_bytes) - assert!(subtitle_url.contains("api_key=super_secret_token")); - assert!(download_url.contains("api_key=super_secret_token")); - } - - #[test] - fn test_url_parameter_injection_prevention() { - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "token123"); - - // Try to inject parameters through item_id - let malicious_id = "item123&extraParam=malicious"; - let url = repo.get_image_url(malicious_id, "Primary", None); - - // URL should contain the full item_id, backend should handle escaping - assert!(url.contains(malicious_id)); - // Backend should be responsible for proper URL encoding - } - - // ===== URL Format Tests ===== - - #[test] - fn test_image_url_format_correctness() { - let repo = MockOnlineRepository::new("https://server.com", "token"); - - let url = repo.get_image_url("id123", "Primary", None); - - // Should be valid format (no api_key — auth via header) - assert!(url.starts_with("https://server.com")); - assert!(url.contains("/Items/id123/Images/Primary")); - assert!(!url.contains("api_key=")); - } - - #[test] - fn test_query_string_properly_separated() { - let repo = MockOnlineRepository::new("https://server.com", "token"); - let options = ImageOptions { - max_width: Some(300), - max_height: Some(200), - quality: None, - tag: None, - }; - - let url = repo.get_image_url("id123", "Primary", Some(&options)); - - // Should have single ? separator with params - let question_marks = url.matches('?').count(); - assert_eq!(question_marks, 1); - - // Should have params for maxWidth and maxHeight - assert!(url.contains("maxWidth=300")); - assert!(url.contains("maxHeight=200")); - } - - #[test] - fn test_special_characters_in_urls() { - let repo = MockOnlineRepository::new("https://server.com", "token_with_special-chars"); - - let url = repo.get_image_url("item-with-special_chars", "Primary", None); - - // Should handle special characters in id (no token in URL anymore) - assert!(url.contains("item-with-special_chars")); - } - - // ===== Backend vs Frontend Responsibility Tests ===== - - #[test] - fn test_backend_owns_url_construction() { - // This test documents that URL construction is ONLY in backend - let repo = MockOnlineRepository::new("https://jellyfin.example.com", "secret_token"); - - // Backend generates full URL with credentials - let url = repo.get_image_url("item123", "Primary", None); - - // URL is complete and ready to use (auth via header, not api_key) - assert!(url.starts_with("https://")); - assert!(url.contains("/Items/item123/Images/Primary")); - - // Frontend never constructs URLs directly - // Frontend only receives pre-constructed URLs from backend - } - - #[test] - fn test_url_includes_all_necessary_parameters() { - let repo = MockOnlineRepository::new("https://server.com", "token"); - let options = ImageOptions { - max_width: Some(300), - max_height: Some(200), - quality: Some(90), - tag: Some("abc".to_string()), - }; - - let url = repo.get_image_url("item123", "Primary", Some(&options)); - - // All provided options should be in URL - assert!(url.contains("maxWidth=300")); - assert!(url.contains("maxHeight=200")); - assert!(url.contains("quality=90")); - assert!(url.contains("tag=abc")); - } - - #[test] - fn test_optional_parameters_omitted_when_not_provided() { - let repo = MockOnlineRepository::new("https://server.com", "token"); - let options = ImageOptions { - max_width: None, - max_height: None, - quality: None, - tag: None, - }; - - let url = repo.get_image_url("item123", "Primary", Some(&options)); - - // Should have no query params (no api_key, no options) - assert!(!url.contains("?")); - assert!(!url.contains("maxWidth")); - assert!(!url.contains("maxHeight")); - assert!(!url.contains("quality")); - assert!(!url.contains("tag")); - } -}