Skip to main content

jellytau_lib/jellyfin/
types.rs

1use serde::Serialize;
2
3/// Configuration for Jellyfin API client
4#[derive(Debug, Clone)]
5pub struct JellyfinConfig {
6    pub server_url: String,
7    pub access_token: String,
8    pub device_id: String,
9}
10
11/// Request body for reporting playback start
12#[derive(Debug, Serialize)]
13#[serde(rename_all = "PascalCase")]
14pub struct PlaybackStartRequest {
15    pub item_id: String,
16    pub position_ticks: i64,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub play_session_id: Option<String>,
19    pub play_command: String,
20    pub is_paused: bool,
21}
22
23/// Request body for reporting playback stopped
24#[derive(Debug, Serialize)]
25#[serde(rename_all = "PascalCase")]
26pub struct PlaybackStoppedRequest {
27    pub item_id: String,
28    pub position_ticks: i64,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub play_session_id: Option<String>,
31}
32
33/// Request body for reporting playback progress
34#[derive(Debug, Serialize)]
35#[serde(rename_all = "PascalCase")]
36#[allow(dead_code)] // Will be used when playback_reporting is integrated
37pub struct PlaybackProgressRequest {
38    pub item_id: String,
39    pub position_ticks: i64,
40    pub is_paused: bool,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub play_session_id: Option<String>,
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn test_jellyfin_config_creation() {
51        let config = JellyfinConfig {
52            server_url: "https://jellyfin.example.com".to_string(),
53            access_token: "token-123".to_string(),
54            device_id: "device-456".to_string(),
55        };
56
57        assert_eq!(config.server_url, "https://jellyfin.example.com");
58        assert_eq!(config.access_token, "token-123");
59        assert_eq!(config.device_id, "device-456");
60    }
61
62    #[test]
63    fn test_jellyfin_config_clone() {
64        let config = JellyfinConfig {
65            server_url: "https://server.local".to_string(),
66            access_token: "token-abc".to_string(),
67            device_id: "device-xyz".to_string(),
68        };
69
70        let cloned = config.clone();
71        assert_eq!(config.server_url, cloned.server_url);
72        assert_eq!(config.access_token, cloned.access_token);
73        assert_eq!(config.device_id, cloned.device_id);
74    }
75
76    #[test]
77    fn test_playback_start_request_serialization() {
78        let request = PlaybackStartRequest {
79            item_id: "item-123".to_string(),
80            position_ticks: 0,
81            play_session_id: Some("session-456".to_string()),
82            play_command: "PlayNow".to_string(),
83            is_paused: false,
84        };
85
86        let json = serde_json::to_string(&request);
87        assert!(json.is_ok());
88        let serialized = json.unwrap();
89        assert!(serialized.contains("item-123"));
90        assert!(serialized.contains("PlayNow"));
91    }
92
93    #[test]
94    fn test_playback_start_request_without_session() {
95        let request = PlaybackStartRequest {
96            item_id: "item-789".to_string(),
97            position_ticks: 1_000_000,
98            play_session_id: None,
99            play_command: "Resume".to_string(),
100            is_paused: true,
101        };
102
103        let json = serde_json::to_string(&request).unwrap();
104        assert!(json.contains("true"));
105        assert!(!json.contains("playSessionId"));
106    }
107
108    #[test]
109    fn test_playback_start_request_pascal_case() {
110        let request = PlaybackStartRequest {
111            item_id: "item-1".to_string(),
112            position_ticks: 100,
113            play_session_id: Some("session-1".to_string()),
114            play_command: "Play".to_string(),
115            is_paused: false,
116        };
117
118        let json = serde_json::to_string(&request).unwrap();
119        // Verify PascalCase serialization
120        assert!(json.contains("ItemId"));
121        assert!(json.contains("PositionTicks"));
122        assert!(json.contains("PlaySessionId"));
123        assert!(json.contains("PlayCommand"));
124        assert!(json.contains("IsPaused"));
125    }
126
127    #[test]
128    fn test_playback_stopped_request_serialization() {
129        let request = PlaybackStoppedRequest {
130            item_id: "item-555".to_string(),
131            position_ticks: 5_000_000,
132            play_session_id: Some("session-789".to_string()),
133        };
134
135        let json = serde_json::to_string(&request);
136        assert!(json.is_ok());
137        let serialized = json.unwrap();
138        assert!(serialized.contains("item-555"));
139        assert!(serialized.contains("5000000"));
140    }
141
142    #[test]
143    fn test_playback_stopped_request_without_session() {
144        let request = PlaybackStoppedRequest {
145            item_id: "item-000".to_string(),
146            position_ticks: 0,
147            play_session_id: None,
148        };
149
150        let json = serde_json::to_string(&request).unwrap();
151        assert!(json.contains("item-000"));
152        assert!(!json.contains("playSessionId"));
153    }
154
155    #[test]
156    fn test_playback_stopped_request_pascal_case() {
157        let request = PlaybackStoppedRequest {
158            item_id: "i1".to_string(),
159            position_ticks: 500,
160            play_session_id: Some("s1".to_string()),
161        };
162
163        let json = serde_json::to_string(&request).unwrap();
164        assert!(json.contains("ItemId"));
165        assert!(json.contains("PositionTicks"));
166        assert!(json.contains("PlaySessionId"));
167    }
168
169    #[test]
170    fn test_playback_progress_request_serialization() {
171        let request = PlaybackProgressRequest {
172            item_id: "item-999".to_string(),
173            position_ticks: 150_000_000,
174            is_paused: false,
175            play_session_id: Some("session-111".to_string()),
176        };
177
178        let json = serde_json::to_string(&request);
179        assert!(json.is_ok());
180        let serialized = json.unwrap();
181        assert!(serialized.contains("item-999"));
182        assert!(serialized.contains("150000000"));
183        assert!(serialized.contains("false"));
184    }
185
186    #[test]
187    fn test_playback_progress_request_paused() {
188        let request = PlaybackProgressRequest {
189            item_id: "item-paused".to_string(),
190            position_ticks: 75_000_000,
191            is_paused: true,
192            play_session_id: None,
193        };
194
195        let json = serde_json::to_string(&request).unwrap();
196        assert!(json.contains("true"));
197        assert!(json.contains("item-paused"));
198    }
199
200    #[test]
201    fn test_playback_progress_request_pascal_case() {
202        let request = PlaybackProgressRequest {
203            item_id: "i2".to_string(),
204            position_ticks: 200,
205            is_paused: true,
206            play_session_id: Some("s2".to_string()),
207        };
208
209        let json = serde_json::to_string(&request).unwrap();
210        assert!(json.contains("ItemId"));
211        assert!(json.contains("PositionTicks"));
212        assert!(json.contains("IsPaused"));
213        assert!(json.contains("PlaySessionId"));
214    }
215
216    #[test]
217    fn test_playback_start_request_various_position_values() {
218        let positions = vec![0, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000];
219
220        for pos in positions {
221            let request = PlaybackStartRequest {
222                item_id: "item-test".to_string(),
223                position_ticks: pos,
224                play_session_id: None,
225                play_command: "Play".to_string(),
226                is_paused: false,
227            };
228
229            let json = serde_json::to_string(&request).unwrap();
230            assert!(json.contains("item-test"));
231            assert!(json.contains(&pos.to_string()));
232        }
233    }
234
235    #[test]
236    fn test_jellyfin_config_debug() {
237        let config = JellyfinConfig {
238            server_url: "https://debug.example.com".to_string(),
239            access_token: "token-debug".to_string(),
240            device_id: "device-debug".to_string(),
241        };
242
243        let debug_str = format!("{:?}", config);
244        assert!(debug_str.contains("JellyfinConfig"));
245        assert!(debug_str.contains("https://debug.example.com"));
246    }
247
248    #[test]
249    fn test_playback_requests_creation_patterns() {
250        // Test various creation patterns
251        let start = PlaybackStartRequest {
252            item_id: "i1".to_string(),
253            position_ticks: 0,
254            play_session_id: None,
255            play_command: "PlayNow".to_string(),
256            is_paused: false,
257        };
258
259        let stopped = PlaybackStoppedRequest {
260            item_id: "i1".to_string(),
261            position_ticks: 1_000_000,
262            play_session_id: None,
263        };
264
265        let progress = PlaybackProgressRequest {
266            item_id: "i1".to_string(),
267            position_ticks: 500_000,
268            is_paused: false,
269            play_session_id: None,
270        };
271
272        // All should serialize successfully
273        assert!(serde_json::to_string(&start).is_ok());
274        assert!(serde_json::to_string(&stopped).is_ok());
275        assert!(serde_json::to_string(&progress).is_ok());
276    }
277}