aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/daemon.rs79
-rw-r--r--tests/database.rs411
-rw-r--r--tests/scrobbler.rs17
3 files changed, 506 insertions, 1 deletions
diff --git a/tests/daemon.rs b/tests/daemon.rs
new file mode 100644
index 0000000..e5cb270
--- /dev/null
+++ b/tests/daemon.rs
@@ -0,0 +1,79 @@
1/*
2 * This file is a part of RSPlayer
3 * Licensed under the GPL-3.0 License
4 * Copyright (C) 2026 Salmonik @ salmonik@tuta.io
5 * https://codeberg.org/salmonik/RSPlayer
6*/
7
8use std::path::PathBuf;
9
10use rsp::daemon::ScrobbleState;
11use rsp::queue::Track;
12
13#[test]
14fn test_scrobble_state_new() {
15 let s = ScrobbleState::new();
16 assert!(s.track.is_none());
17 assert!(s.start_time.is_none());
18 assert!(s.timestamp.is_none());
19}
20
21#[test]
22fn test_scrobble_state_clear() {
23 let mut s = ScrobbleState::new();
24 let t = Track {
25 path: PathBuf::from("/test/song.mp3"),
26 title: Some("Test".into()),
27 artist: Some("Artist".into()),
28 album: None,
29 duration_secs: Some(200.0),
30 is_stream: false,
31 };
32 s.start(t);
33 assert!(s.track.is_some());
34 s.clear();
35 assert!(s.track.is_none());
36 assert!(s.start_time.is_none());
37 assert!(s.timestamp.is_none());
38}
39
40#[test]
41fn test_should_scrobble_without_track_returns_false() {
42 assert!(!ScrobbleState::new().should_scrobble());
43}
44
45#[test]
46fn test_should_scrobble_threshold_below_30_seconds() {
47 assert!(!ScrobbleState::should_scrobble_with_params(29, Some(200.0)));
48}
49
50#[test]
51fn test_should_scrobble_threshold_half_duration() {
52 assert!(ScrobbleState::should_scrobble_with_params(100, Some(200.0)));
53}
54
55#[test]
56fn test_should_scrobble_threshold_below_half_duration() {
57 assert!(!ScrobbleState::should_scrobble_with_params(80, Some(200.0)));
58}
59
60#[test]
61fn test_should_scrobble_threshold_capped_at_4min() {
62 assert!(ScrobbleState::should_scrobble_with_params(240, Some(600.0)));
63 assert!(!ScrobbleState::should_scrobble_with_params(
64 239,
65 Some(600.0)
66 ));
67}
68
69#[test]
70fn test_should_scrobble_no_duration_requires_4min() {
71 assert!(ScrobbleState::should_scrobble_with_params(240, None));
72 assert!(!ScrobbleState::should_scrobble_with_params(239, None));
73}
74
75#[test]
76fn test_should_scrobble_short_song() {
77 assert!(ScrobbleState::should_scrobble_with_params(30, Some(60.0)));
78 assert!(!ScrobbleState::should_scrobble_with_params(15, Some(60.0)));
79}
diff --git a/tests/database.rs b/tests/database.rs
index 6cd870e..84eb663 100644
--- a/tests/database.rs
+++ b/tests/database.rs
@@ -5,7 +5,7 @@
5 * https://codeberg.org/salmonik/RSPlayer 5 * https://codeberg.org/salmonik/RSPlayer
6*/ 6*/
7 7
8use rsp::database::{extract_metadata, is_audio_file}; 8use rsp::database::{Database, extract_metadata, is_audio_file};
9use std::path::PathBuf; 9use std::path::PathBuf;
10 10
11#[test] 11#[test]
@@ -90,3 +90,412 @@ fn test_extract_metadata_no_parent() {
90 assert_eq!(album, "Unknown"); 90 assert_eq!(album, "Unknown");
91 assert_eq!(title, "song"); 91 assert_eq!(title, "song");
92} 92}
93
94fn seed_test_db(db: &Database) {
95 let conn: &rusqlite::Connection = db.conn();
96 conn.execute_batch(
97 "INSERT INTO artists (id, name) VALUES (1, 'Metallica'), (2, 'Miles Davis'), (3, 'Aphex Twin');
98 INSERT INTO albums (id, artist_id, title) VALUES
99 (1, 1, 'Master of Puppets'),
100 (2, 1, 'Ride the Lightning'),
101 (3, 2, 'Kind of Blue'),
102 (4, 3, 'Selected Ambient Works');
103 INSERT INTO tracks (id, album_id, title, path, duration, track_number) VALUES
104 (1, 1, 'Battery', '/m/Metallica/Master of Puppets/01-Battery.mp3', 313, 1),
105 (2, 1, 'Master of Puppets', '/m/Metallica/Master of Puppets/02-Master of Puppets.mp3', 515, 2),
106 (3, 2, 'Fight Fire with Fire', '/m/Metallica/Ride the Lightning/01-Fight Fire with Fire.mp3', 285, 1),
107 (4, 3, 'So What', '/m/Miles Davis/Kind of Blue/01-So What.mp3', 562, 1),
108 (5, 4, 'Xtal', '/m/Aphex Twin/Selected Ambient Works/01-Xtal.mp3', 437, 1);",
109 )
110 .unwrap();
111}
112
113#[test]
114fn test_complete_artists_prefix() {
115 let db = Database::open_in_memory().unwrap();
116 seed_test_db(&db);
117 let results = db.complete("M").unwrap();
118 assert!(results.contains(&"Metallica/".to_string()));
119 assert!(results.contains(&"Miles Davis/".to_string()));
120}
121
122#[test]
123fn test_complete_artists_prefix_lowercase() {
124 let db = Database::open_in_memory().unwrap();
125 seed_test_db(&db);
126 let results = db.complete("m").unwrap();
127 assert!(
128 results.contains(&"Metallica/".to_string()),
129 "LIKE should be case-insensitive"
130 );
131}
132
133#[test]
134fn test_complete_no_match() {
135 let db = Database::open_in_memory().unwrap();
136 seed_test_db(&db);
137 let results = db.complete("Z").unwrap();
138 assert!(results.is_empty());
139}
140
141#[test]
142fn test_complete_empty_prefix() {
143 let db = Database::open_in_memory().unwrap();
144 seed_test_db(&db);
145 let results = db.complete("").unwrap();
146 assert_eq!(results.len(), 3);
147 assert!(results.contains(&"Metallica/".to_string()));
148 assert!(results.contains(&"Miles Davis/".to_string()));
149 assert!(results.contains(&"Aphex Twin/".to_string()));
150}
151
152#[test]
153fn test_complete_albums_for_artist() {
154 let db = Database::open_in_memory().unwrap();
155 seed_test_db(&db);
156 let results = db.complete("Metallica/").unwrap();
157 assert_eq!(results.len(), 2);
158 assert!(results.contains(&"Metallica/Master of Puppets/".to_string()));
159 assert!(results.contains(&"Metallica/Ride the Lightning/".to_string()));
160}
161
162#[test]
163fn test_complete_albums_artist_lowercase() {
164 let db = Database::open_in_memory().unwrap();
165 seed_test_db(&db);
166 let results = db.complete("metallica/").unwrap();
167 assert_eq!(results.len(), 2, "artist match should be case-insensitive");
168 assert!(results.contains(&"metallica/Master of Puppets/".to_string()));
169}
170
171#[test]
172fn test_complete_albums_with_album_prefix() {
173 let db = Database::open_in_memory().unwrap();
174 seed_test_db(&db);
175 // "R" matches "Ride the Lightning" (prefix) and "Master of Puppets" (contains 'r')
176 let results = db.complete("Metallica/R").unwrap();
177 assert_eq!(results.len(), 2);
178 assert_eq!(results[0], "Metallica/Ride the Lightning/");
179 assert_eq!(results[1], "Metallica/Master of Puppets/");
180}
181
182#[test]
183fn test_complete_albums_substring_match() {
184 let db = Database::open_in_memory().unwrap();
185 seed_test_db(&db);
186 // "ind" matches "Kind of Blue" (contains "ind")
187 let results = db.complete("Miles Davis/ind").unwrap();
188 assert_eq!(results.len(), 1);
189 assert_eq!(results[0], "Miles Davis/Kind of Blue/");
190}
191
192#[test]
193fn test_complete_artists_substring_match() {
194 let db = Database::open_in_memory().unwrap();
195 seed_test_db(&db);
196 // "x" matches "Aphex Twin" (contains 'x')
197 let results = db.complete("x").unwrap();
198 assert!(results.contains(&"Aphex Twin/".to_string()));
199}
200
201#[test]
202fn test_complete_albums_unknown_artist() {
203 let db = Database::open_in_memory().unwrap();
204 seed_test_db(&db);
205 let results = db.complete("Unknown/").unwrap();
206 assert!(results.is_empty());
207}
208
209#[test]
210fn test_complete_tracks_for_album() {
211 let db = Database::open_in_memory().unwrap();
212 seed_test_db(&db);
213 let results = db.complete("Metallica/Master of Puppets/").unwrap();
214 assert_eq!(results.len(), 2);
215 assert!(results.contains(&"Metallica/Master of Puppets/Battery".to_string()));
216 assert!(results.contains(&"Metallica/Master of Puppets/Master of Puppets".to_string()));
217}
218
219#[test]
220fn test_complete_tracks_with_prefix() {
221 let db = Database::open_in_memory().unwrap();
222 seed_test_db(&db);
223 let results = db.complete("Metallica/Master of Puppets/B").unwrap();
224 assert_eq!(results.len(), 1);
225 assert_eq!(results[0], "Metallica/Master of Puppets/Battery");
226}
227
228#[test]
229fn test_complete_tracks_unknown_album() {
230 let db = Database::open_in_memory().unwrap();
231 seed_test_db(&db);
232 let results = db.complete("Metallica/Nonexistent/").unwrap();
233 assert!(results.is_empty());
234}
235
236#[test]
237fn test_complete_empty_db() {
238 let db = Database::open_in_memory().unwrap();
239 assert!(db.complete("").unwrap().is_empty());
240 assert!(db.complete("x/").unwrap().is_empty());
241 assert!(db.complete("x/y/").unwrap().is_empty());
242}
243
244// --- find_tracks_by_album tests ---
245
246#[test]
247fn test_find_tracks_by_album_all() {
248 let db = Database::open_in_memory().unwrap();
249 seed_test_db(&db);
250 let tracks = db
251 .find_tracks_by_album("Metallica", "Master of Puppets")
252 .unwrap();
253 assert_eq!(tracks.len(), 2);
254 assert_eq!(tracks[0].title, "Battery");
255 assert_eq!(tracks[1].title, "Master of Puppets");
256 assert_eq!(tracks[0].artist_name.as_deref(), Some("Metallica"));
257 assert_eq!(tracks[0].album_title.as_deref(), Some("Master of Puppets"));
258}
259
260#[test]
261fn test_find_tracks_by_album_single_track() {
262 let db = Database::open_in_memory().unwrap();
263 seed_test_db(&db);
264 let tracks = db
265 .find_tracks_by_album("Miles Davis", "Kind of Blue")
266 .unwrap();
267 assert_eq!(tracks.len(), 1);
268 assert_eq!(tracks[0].title, "So What");
269}
270
271#[test]
272fn test_find_tracks_by_album_unknown_artist() {
273 let db = Database::open_in_memory().unwrap();
274 seed_test_db(&db);
275 let tracks = db
276 .find_tracks_by_album("Nonexistent", "Master of Puppets")
277 .unwrap();
278 assert!(tracks.is_empty());
279}
280
281#[test]
282fn test_find_tracks_by_album_unknown_album() {
283 let db = Database::open_in_memory().unwrap();
284 seed_test_db(&db);
285 let tracks = db.find_tracks_by_album("Metallica", "Nonexistent").unwrap();
286 assert!(tracks.is_empty());
287}
288
289#[test]
290fn test_find_tracks_by_album_case_insensitive_artist() {
291 let db = Database::open_in_memory().unwrap();
292 seed_test_db(&db);
293 let tracks = db
294 .find_tracks_by_album("metallica", "Master of Puppets")
295 .unwrap();
296 assert_eq!(
297 tracks.len(),
298 2,
299 "artist name match should be case-insensitive"
300 );
301 assert_eq!(tracks[0].title, "Battery");
302}
303
304#[test]
305fn test_find_tracks_by_album_ordered_by_track_number() {
306 let db = Database::open_in_memory().unwrap();
307 seed_test_db(&db);
308 let tracks = db
309 .find_tracks_by_album("Metallica", "Master of Puppets")
310 .unwrap();
311 assert_eq!(tracks[0].track_number, Some(1));
312 assert_eq!(tracks[1].track_number, Some(2));
313}
314
315// --- find_track_by_parts tests ---
316
317#[test]
318fn test_find_track_by_parts_exact() {
319 let db = Database::open_in_memory().unwrap();
320 seed_test_db(&db);
321 let track = db
322 .find_track_by_parts("Metallica", "Master of Puppets", "Battery")
323 .unwrap();
324 assert!(track.is_some());
325 let t = track.unwrap();
326 assert_eq!(t.title, "Battery");
327 assert_eq!(t.artist_name.as_deref(), Some("Metallica"));
328 assert_eq!(t.album_title.as_deref(), Some("Master of Puppets"));
329}
330
331#[test]
332fn test_find_track_by_parts_prefix() {
333 let db = Database::open_in_memory().unwrap();
334 seed_test_db(&db);
335 let track = db
336 .find_track_by_parts("Metallica", "Master of Puppets", "B")
337 .unwrap();
338 assert!(track.is_some());
339 assert_eq!(track.unwrap().title, "Battery");
340}
341
342#[test]
343fn test_find_track_by_parts_no_match_track() {
344 let db = Database::open_in_memory().unwrap();
345 seed_test_db(&db);
346 let track = db
347 .find_track_by_parts("Metallica", "Master of Puppets", "Z")
348 .unwrap();
349 assert!(track.is_none());
350}
351
352#[test]
353fn test_find_track_by_parts_no_match_album() {
354 let db = Database::open_in_memory().unwrap();
355 seed_test_db(&db);
356 let track = db
357 .find_track_by_parts("Metallica", "Nonexistent", "B")
358 .unwrap();
359 assert!(track.is_none());
360}
361
362#[test]
363fn test_find_track_by_parts_no_match_artist() {
364 let db = Database::open_in_memory().unwrap();
365 seed_test_db(&db);
366 let track = db
367 .find_track_by_parts("Nonexistent", "Master of Puppets", "B")
368 .unwrap();
369 assert!(track.is_none());
370}
371
372#[test]
373fn test_find_track_by_parts_case_insensitive() {
374 let db = Database::open_in_memory().unwrap();
375 seed_test_db(&db);
376 let track = db
377 .find_track_by_parts("metallica", "Master of Puppets", "Battery")
378 .unwrap();
379 assert!(track.is_some(), "artist match should be case-insensitive");
380 assert_eq!(track.unwrap().title, "Battery");
381}
382
383// --- resolve_path tests ---
384
385#[test]
386fn test_resolve_path_two_parts_album() {
387 let db = Database::open_in_memory().unwrap();
388 seed_test_db(&db);
389 let result = db.resolve_path("Metallica/Master of Puppets").unwrap();
390 assert!(result.is_some());
391 let tracks = result.unwrap();
392 assert_eq!(tracks.len(), 2);
393 assert_eq!(tracks[0].title, "Battery");
394}
395
396#[test]
397fn test_resolve_path_two_parts_trailing_slash() {
398 let db = Database::open_in_memory().unwrap();
399 seed_test_db(&db);
400 let result = db.resolve_path("Metallica/Master of Puppets/").unwrap();
401 assert!(result.is_some());
402 let tracks = result.unwrap();
403 assert_eq!(tracks.len(), 2);
404}
405
406#[test]
407fn test_resolve_path_three_parts_track() {
408 let db = Database::open_in_memory().unwrap();
409 seed_test_db(&db);
410 let result = db
411 .resolve_path("Metallica/Master of Puppets/Battery")
412 .unwrap();
413 assert!(result.is_some());
414 let tracks = result.unwrap();
415 assert_eq!(tracks.len(), 1);
416 assert_eq!(tracks[0].title, "Battery");
417}
418
419#[test]
420fn test_resolve_path_three_parts_track_prefix() {
421 let db = Database::open_in_memory().unwrap();
422 seed_test_db(&db);
423 let result = db.resolve_path("Metallica/Master of Puppets/B").unwrap();
424 assert!(result.is_some());
425 let tracks = result.unwrap();
426 assert_eq!(tracks.len(), 1);
427 assert_eq!(tracks[0].title, "Battery");
428}
429
430#[test]
431fn test_resolve_path_single_part_returns_none() {
432 let db = Database::open_in_memory().unwrap();
433 seed_test_db(&db);
434 let result = db.resolve_path("Metallica").unwrap();
435 assert!(
436 result.is_none(),
437 "single part should return None for fallback"
438 );
439}
440
441#[test]
442fn test_resolve_path_no_match_album() {
443 let db = Database::open_in_memory().unwrap();
444 seed_test_db(&db);
445 let result = db.resolve_path("Metallica/Nonexistent").unwrap();
446 assert!(result.is_some());
447 assert!(result.unwrap().is_empty());
448}
449
450#[test]
451fn test_resolve_path_no_match_track() {
452 let db = Database::open_in_memory().unwrap();
453 seed_test_db(&db);
454 let result = db.resolve_path("Metallica/Master of Puppets/Z").unwrap();
455 assert!(result.is_some());
456 assert!(result.unwrap().is_empty());
457}
458
459#[test]
460fn test_resolve_path_empty_db() {
461 let db = Database::open_in_memory().unwrap();
462 assert!(db.resolve_path("Artist/Album").unwrap().unwrap().is_empty());
463 assert!(
464 db.resolve_path("Artist/Album/Track")
465 .unwrap()
466 .unwrap()
467 .is_empty()
468 );
469 assert!(db.resolve_path("Artist").unwrap().is_none());
470}
471
472// --- Track conversion helper tests ---
473
474#[test]
475fn test_find_tracks_by_album_returns_full_metadata() {
476 let db = Database::open_in_memory().unwrap();
477 seed_test_db(&db);
478 let tracks = db
479 .find_tracks_by_album("Metallica", "Ride the Lightning")
480 .unwrap();
481 assert_eq!(tracks.len(), 1);
482 let t = &tracks[0];
483 assert_eq!(t.artist_name.as_deref(), Some("Metallica"));
484 assert_eq!(t.album_title.as_deref(), Some("Ride the Lightning"));
485 assert_eq!(t.title, "Fight Fire with Fire");
486 assert!(t.path.to_string_lossy().contains("Ride the Lightning"));
487}
488
489#[test]
490fn test_find_track_by_parts_returns_full_metadata() {
491 let db = Database::open_in_memory().unwrap();
492 seed_test_db(&db);
493 let t = db
494 .find_track_by_parts("Aphex Twin", "Selected Ambient Works", "Xtal")
495 .unwrap()
496 .expect("track should be found");
497 assert_eq!(t.artist_name.as_deref(), Some("Aphex Twin"));
498 assert_eq!(t.album_title.as_deref(), Some("Selected Ambient Works"));
499 assert_eq!(t.title, "Xtal");
500 assert_eq!(t.track_number, Some(1));
501}
diff --git a/tests/scrobbler.rs b/tests/scrobbler.rs
index a4b3fda..237a024 100644
--- a/tests/scrobbler.rs
+++ b/tests/scrobbler.rs
@@ -5,6 +5,8 @@
5 * https://codeberg.org/salmonik/RSPlayer 5 * https://codeberg.org/salmonik/RSPlayer
6*/ 6*/
7 7
8use std::collections::BTreeMap;
9
8use rsp::scrobbler::Scrobbler; 10use rsp::scrobbler::Scrobbler;
9 11
10#[test] 12#[test]
@@ -80,3 +82,18 @@ fn test_scrobbler_skip_empty_credentials() {
80 scrobbler.now_playing("Artist", "Track", Some("Album")); 82 scrobbler.now_playing("Artist", "Track", Some("Album"));
81 scrobbler.scrobble("Artist", "Track", Some("Album"), 1234567890); 83 scrobbler.scrobble("Artist", "Track", Some("Album"), 1234567890);
82} 84}
85
86#[test]
87fn test_sign_lastfm() {
88 let params = BTreeMap::from([
89 ("api_key", "abc123"),
90 ("method", "track.scrobble"),
91 ("sk", "session_key"),
92 ("track", "Test Track"),
93 ("artist", "Test Artist"),
94 ("timestamp", "1234567890"),
95 ]);
96 let sig = Scrobbler::sign_lastfm(&params, "sekrit");
97 assert_eq!(sig.len(), 32);
98 assert!(sig.chars().all(|c| c.is_ascii_hexdigit()));
99}