1pub mod db_service;
7pub mod models;
8pub mod schema;
9
10use crate::utils::lock::MutexSafe;
11use std::path::PathBuf;
12use std::sync::{Arc, Mutex};
13
14use log::{debug, error, info};
15use rusqlite::{Connection, Result as SqliteResult};
16
17pub use db_service::{DatabaseService, RusqliteService};
18use schema::MIGRATIONS;
19
20pub struct Database {
22 conn: Arc<Mutex<Connection>>,
23 path: PathBuf,
24}
25
26impl Database {
27 pub fn open(path: &PathBuf) -> SqliteResult<Self> {
29 if let Some(parent) = path.parent() {
31 std::fs::create_dir_all(parent).ok();
32 }
33
34 let conn = Connection::open(path)?;
35
36 conn.execute_batch("PRAGMA foreign_keys = ON;")?;
38
39 conn.execute_batch("PRAGMA journal_mode = WAL;")?;
41
42 let db = Self {
43 conn: Arc::new(Mutex::new(conn)),
44 path: path.clone(),
45 };
46
47 db.migrate()?;
49
50 Ok(db)
51 }
52
53 #[cfg(test)]
55 pub fn open_in_memory() -> SqliteResult<Self> {
56 let conn = Connection::open_in_memory()?;
57
58 conn.execute_batch("PRAGMA foreign_keys = ON;")?;
60
61 let db = Self {
62 conn: Arc::new(Mutex::new(conn)),
63 path: PathBuf::from(":memory:"),
64 };
65
66 db.migrate()?;
68
69 Ok(db)
70 }
71
72 #[cfg(test)]
74 pub fn connection(&self) -> Arc<Mutex<Connection>> {
75 Arc::clone(&self.conn)
76 }
77
78 pub fn migrate(&self) -> SqliteResult<()> {
80 self.migrate_with(MIGRATIONS)
81 }
82
83 fn migrate_with(&self, migrations: &[(&str, &str)]) -> SqliteResult<()> {
103 info!("Starting database migrations...");
104 let conn = self.conn.lock_safe();
105
106 debug!("Creating _migrations table if it doesn't exist...");
108 match conn.execute(
109 "CREATE TABLE IF NOT EXISTS _migrations (
110 id INTEGER PRIMARY KEY,
111 name TEXT NOT NULL UNIQUE,
112 applied_at TEXT DEFAULT CURRENT_TIMESTAMP
113 )",
114 [],
115 ) {
116 Ok(_) => debug!("_migrations table ready"),
117 Err(e) => {
118 error!("Failed to create _migrations table: {}", e);
119 return Err(e);
120 }
121 }
122
123 debug!("Querying applied migrations...");
125 let mut stmt = conn.prepare("SELECT name FROM _migrations")?;
126 let applied: Vec<String> = stmt
127 .query_map([], |row: &rusqlite::Row| row.get(0))?
128 .filter_map(|r| r.ok())
129 .collect();
130 debug!("Found {} applied migrations", applied.len());
131
132 for (name, sql) in migrations {
134 if applied.contains(&name.to_string()) {
135 debug!("Skipping already applied migration: {}", name);
136 continue;
137 }
138
139 info!("Applying migration: {}", name);
140
141 let tx = conn.unchecked_transaction()?;
145
146 if let Err(e) = tx.execute_batch(sql) {
147 error!("Failed to apply migration {} (rolled back): {}", name, e);
148 return Err(e);
149 }
150 if let Err(e) = tx.execute("INSERT INTO _migrations (name) VALUES (?1)", [name]) {
151 error!("Failed to record migration {} (rolled back): {}", name, e);
152 return Err(e);
153 }
154 tx.commit()?;
155
156 info!("Successfully applied migration: {}", name);
157 }
158
159 info!("All migrations completed successfully");
160 Ok(())
161 }
162
163 pub fn service(&self) -> RusqliteService {
168 RusqliteService::new(Arc::clone(&self.conn))
169 }
170
171 pub fn path(&self) -> &PathBuf {
173 &self.path
174 }
175
176 pub fn file_size(&self) -> Option<u64> {
178 std::fs::metadata(&self.path).ok().map(|m| m.len())
179 }
180}
181
182#[cfg(test)]
184mod tests {
185 use super::*;
186 use rusqlite::params;
187
188 #[test]
189 fn test_open_in_memory() {
190 let db = Database::open_in_memory().unwrap();
191 assert_eq!(db.path().to_str(), Some(":memory:"));
192 }
193
194 #[test]
205 fn test_failed_migration_rolls_back_and_stays_retryable() {
206 let db = Database::open_in_memory().unwrap();
207
208 let poisoned = &[(
210 "900_partially_failing",
211 "ALTER TABLE downloads ADD COLUMN audit_a TEXT;
212 ALTER TABLE downloads ADD COLUMN audit_b TEXT FROM NOWHERE;
213 ALTER TABLE downloads ADD COLUMN audit_c TEXT;",
214 )][..];
215
216 let first = db.migrate_with(poisoned).unwrap_err();
217
218 assert!(
220 !has_column(&db, "downloads", "audit_a"),
221 "statement 1 of a failed migration was left applied: the batch did not roll back"
222 );
223 assert!(!has_column(&db, "downloads", "audit_c"));
224
225 let recorded: i64 = db
227 .connection()
228 .lock_safe()
229 .query_row(
230 "SELECT COUNT(*) FROM _migrations WHERE name = ?1",
231 ["900_partially_failing"],
232 |r| r.get(0),
233 )
234 .unwrap();
235 assert_eq!(recorded, 0, "a failed migration must not be recorded");
236
237 let second = db.migrate_with(poisoned).unwrap_err();
240 assert!(
241 !second.to_string().contains("duplicate column"),
242 "the retry hit leftovers from the failed run instead of the real error: {second}"
243 );
244 assert_eq!(first.to_string(), second.to_string());
245
246 let fixed = &[(
248 "900_partially_failing",
249 "ALTER TABLE downloads ADD COLUMN audit_a TEXT;
250 ALTER TABLE downloads ADD COLUMN audit_c TEXT;",
251 )][..];
252 db.migrate_with(fixed).unwrap();
253 assert!(has_column(&db, "downloads", "audit_a"));
254 assert!(has_column(&db, "downloads", "audit_c"));
255 }
256
257 #[test]
261 fn test_successful_migration_is_recorded_in_the_same_transaction() {
262 let db = Database::open_in_memory().unwrap();
263 let m = &[(
264 "901_adds_a_column",
265 "ALTER TABLE downloads ADD COLUMN audit_d TEXT;",
266 )][..];
267
268 db.migrate_with(m).unwrap();
269 db.migrate_with(m).unwrap();
271 assert!(has_column(&db, "downloads", "audit_d"));
272 }
273
274 fn has_column(db: &Database, table: &str, column: &str) -> bool {
275 let conn = db.connection();
276 let conn = conn.lock_safe();
277 let mut stmt = conn
278 .prepare(&format!("PRAGMA table_info({table})"))
279 .unwrap();
280 let mut names = stmt
281 .query_map([], |row| row.get::<_, String>(1))
282 .unwrap()
283 .filter_map(|r| r.ok());
284 names.any(|n| n == column)
285 }
286
287 #[test]
288 fn test_migrations_run() {
289 let db = Database::open_in_memory().unwrap();
290 let conn = db.connection();
291 let conn = conn.lock_safe();
292
293 let mut stmt = conn
295 .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='items'")
296 .unwrap();
297 let exists: Option<String> = stmt.query_row([], |row: &rusqlite::Row| row.get(0)).ok();
298 assert!(exists.is_some());
299 }
300
301 #[test]
302 fn test_all_tables_created() {
303 let db = Database::open_in_memory().unwrap();
304 let conn = db.connection();
305 let conn = conn.lock_safe();
306
307 let expected_tables = [
308 "servers",
309 "users",
310 "libraries",
311 "items",
312 "media_streams",
313 "user_data",
314 "downloads",
315 "sync_queue",
316 "thumbnails",
317 "playlists",
318 "playlist_items",
319 "genres",
320 ];
321
322 for table in expected_tables {
323 let exists: Option<String> = conn
324 .query_row(
325 "SELECT name FROM sqlite_master WHERE type='table' AND name=?1",
326 [table],
327 |row: &rusqlite::Row| row.get(0),
328 )
329 .ok();
330 assert!(exists.is_some(), "Table '{}' should exist", table);
331 }
332 }
333
334 #[test]
335 fn test_fts_table_created() {
336 let db = Database::open_in_memory().unwrap();
337 let conn = db.connection();
338 let conn = conn.lock_safe();
339
340 let exists: Option<String> = conn
341 .query_row(
342 "SELECT name FROM sqlite_master WHERE type='table' AND name='items_fts'",
343 [],
344 |row: &rusqlite::Row| row.get(0),
345 )
346 .ok();
347 assert!(exists.is_some(), "FTS table 'items_fts' should exist");
348 }
349
350 #[test]
351 fn test_server_crud() {
352 let db = Database::open_in_memory().unwrap();
353 let conn = db.connection();
354 let conn = conn.lock_safe();
355
356 conn.execute(
358 "INSERT INTO servers (id, name, url, version) VALUES (?1, ?2, ?3, ?4)",
359 params!["server1", "My Server", "http://localhost:8096", "10.8.0"],
360 )
361 .unwrap();
362
363 let (name, url): (String, String) = conn
365 .query_row(
366 "SELECT name, url FROM servers WHERE id = ?1",
367 ["server1"],
368 |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?)),
369 )
370 .unwrap();
371 assert_eq!(name, "My Server");
372 assert_eq!(url, "http://localhost:8096");
373
374 conn.execute(
376 "UPDATE servers SET name = ?1 WHERE id = ?2",
377 params!["Updated Server", "server1"],
378 )
379 .unwrap();
380
381 let name: String = conn
382 .query_row(
383 "SELECT name FROM servers WHERE id = ?1",
384 ["server1"],
385 |row: &rusqlite::Row| row.get(0),
386 )
387 .unwrap();
388 assert_eq!(name, "Updated Server");
389
390 conn.execute("DELETE FROM servers WHERE id = ?1", ["server1"])
392 .unwrap();
393
394 let count: i32 = conn
395 .query_row("SELECT COUNT(*) FROM servers", [], |row: &rusqlite::Row| {
396 row.get(0)
397 })
398 .unwrap();
399 assert_eq!(count, 0);
400 }
401
402 #[test]
403 fn test_user_crud() {
404 let db = Database::open_in_memory().unwrap();
405 let conn = db.connection();
406 let conn = conn.lock_safe();
407
408 conn.execute(
410 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
411 params!["server1", "Test Server", "http://localhost:8096"],
412 )
413 .unwrap();
414
415 conn.execute(
417 "INSERT INTO users (id, server_id, username, is_active)
418 VALUES (?1, ?2, ?3, ?4)",
419 params!["user1", "server1", "admin", 1],
420 )
421 .unwrap();
422
423 let (username, is_active): (String, i32) = conn
425 .query_row(
426 "SELECT username, is_active FROM users WHERE id = ?1",
427 ["user1"],
428 |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?)),
429 )
430 .unwrap();
431 assert_eq!(username, "admin");
432 assert_eq!(is_active, 1);
433
434 conn.execute("UPDATE users SET is_active = 0 WHERE id = ?1", ["user1"])
436 .unwrap();
437
438 let is_active: i32 = conn
439 .query_row(
440 "SELECT is_active FROM users WHERE id = ?1",
441 ["user1"],
442 |row: &rusqlite::Row| row.get(0),
443 )
444 .unwrap();
445 assert_eq!(is_active, 0);
446 }
447
448 #[test]
449 fn test_cascade_delete_server_removes_users() {
450 let db = Database::open_in_memory().unwrap();
451 let conn = db.connection();
452 let conn = conn.lock_safe();
453
454 conn.execute(
456 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
457 params!["server1", "Test Server", "http://localhost:8096"],
458 )
459 .unwrap();
460
461 conn.execute(
462 "INSERT INTO users (id, server_id, username) VALUES (?1, ?2, ?3)",
463 params!["user1", "server1", "admin"],
464 )
465 .unwrap();
466
467 let count: i32 = conn
469 .query_row(
470 "SELECT COUNT(*) FROM users WHERE server_id = ?1",
471 ["server1"],
472 |row: &rusqlite::Row| row.get(0),
473 )
474 .unwrap();
475 assert_eq!(count, 1);
476
477 conn.execute("DELETE FROM servers WHERE id = ?1", ["server1"])
479 .unwrap();
480
481 let count: i32 = conn
483 .query_row("SELECT COUNT(*) FROM users", [], |row: &rusqlite::Row| {
484 row.get(0)
485 })
486 .unwrap();
487 assert_eq!(count, 0);
488 }
489
490 #[test]
491 fn test_item_insert_and_fts_search() {
492 let db = Database::open_in_memory().unwrap();
493 let conn = db.connection();
494 let conn = conn.lock_safe();
495
496 conn.execute(
498 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
499 params!["server1", "Test Server", "http://localhost:8096"],
500 )
501 .unwrap();
502
503 conn.execute(
505 "INSERT INTO items (id, server_id, name, item_type, overview, album_name, artists)
506 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
507 params![
508 "item1",
509 "server1",
510 "Bohemian Rhapsody",
511 "Audio",
512 "A legendary rock song",
513 "A Night at the Opera",
514 "[\"Queen\"]"
515 ],
516 )
517 .unwrap();
518
519 let mut stmt = conn
521 .prepare(
522 "SELECT i.name FROM items i
523 JOIN items_fts ON i.rowid = items_fts.rowid
524 WHERE items_fts MATCH ?1",
525 )
526 .unwrap();
527
528 let result: Option<String> = stmt
530 .query_row(["Bohemian"], |row: &rusqlite::Row| row.get(0))
531 .ok();
532 assert_eq!(result, Some("Bohemian Rhapsody".to_string()));
533
534 let result: Option<String> = stmt
536 .query_row(["Opera"], |row: &rusqlite::Row| row.get(0))
537 .ok();
538 assert_eq!(result, Some("Bohemian Rhapsody".to_string()));
539
540 let result: Option<String> = stmt
542 .query_row(["Queen"], |row: &rusqlite::Row| row.get(0))
543 .ok();
544 assert_eq!(result, Some("Bohemian Rhapsody".to_string()));
545 }
546
547 #[test]
548 fn test_user_data_playback_position() {
549 let db = Database::open_in_memory().unwrap();
550 let conn = db.connection();
551 let conn = conn.lock_safe();
552
553 conn.execute(
555 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
556 params!["server1", "Test", "http://localhost"],
557 )
558 .unwrap();
559
560 conn.execute(
561 "INSERT INTO users (id, server_id, username) VALUES (?1, ?2, ?3)",
562 params!["user1", "server1", "admin"],
563 )
564 .unwrap();
565
566 conn.execute(
567 "INSERT INTO items (id, server_id, name, item_type) VALUES (?1, ?2, ?3, ?4)",
568 params!["item1", "server1", "Test Movie", "Movie"],
569 )
570 .unwrap();
571
572 conn.execute(
574 "INSERT INTO user_data (user_id, item_id, playback_position_ticks, is_played)
575 VALUES (?1, ?2, ?3, ?4)",
576 params!["user1", "item1", 12345678900_i64, 0],
577 )
578 .unwrap();
579
580 let (position, is_played): (i64, i32) = conn
582 .query_row(
583 "SELECT playback_position_ticks, is_played FROM user_data
584 WHERE user_id = ?1 AND item_id = ?2",
585 ["user1", "item1"],
586 |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?)),
587 )
588 .unwrap();
589 assert_eq!(position, 12345678900);
590 assert_eq!(is_played, 0);
591
592 conn.execute(
594 "UPDATE user_data SET is_played = 1, playback_position_ticks = 0
595 WHERE user_id = ?1 AND item_id = ?2",
596 ["user1", "item1"],
597 )
598 .unwrap();
599
600 let is_played: i32 = conn
601 .query_row(
602 "SELECT is_played FROM user_data WHERE user_id = ?1 AND item_id = ?2",
603 ["user1", "item1"],
604 |row: &rusqlite::Row| row.get(0),
605 )
606 .unwrap();
607 assert_eq!(is_played, 1);
608 }
609
610 #[test]
611 fn test_sync_queue_operations() {
612 let db = Database::open_in_memory().unwrap();
613 let conn = db.connection();
614 let conn = conn.lock_safe();
615
616 conn.execute(
618 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
619 params!["server1", "Test", "http://localhost"],
620 )
621 .unwrap();
622
623 conn.execute(
624 "INSERT INTO users (id, server_id, username) VALUES (?1, ?2, ?3)",
625 params!["user1", "server1", "admin"],
626 )
627 .unwrap();
628
629 conn.execute(
631 "INSERT INTO sync_queue (user_id, operation, item_id, payload, status)
632 VALUES (?1, ?2, ?3, ?4, ?5)",
633 params![
634 "user1",
635 "mark_favorite",
636 "item123",
637 r#"{"favorite": true}"#,
638 "pending"
639 ],
640 )
641 .unwrap();
642
643 let mut stmt = conn
645 .prepare("SELECT operation, item_id FROM sync_queue WHERE status = 'pending'")
646 .unwrap();
647
648 let ops: Vec<(String, String)> = stmt
649 .query_map([], |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?)))
650 .unwrap()
651 .filter_map(|r| r.ok())
652 .collect();
653
654 assert_eq!(ops.len(), 1);
655 assert_eq!(ops[0].0, "mark_favorite");
656 assert_eq!(ops[0].1, "item123");
657
658 conn.execute(
660 "UPDATE sync_queue SET status = 'completed' WHERE item_id = ?1",
661 ["item123"],
662 )
663 .unwrap();
664
665 let pending_count: i32 = conn
666 .query_row(
667 "SELECT COUNT(*) FROM sync_queue WHERE status = 'pending'",
668 [],
669 |row: &rusqlite::Row| row.get(0),
670 )
671 .unwrap();
672 assert_eq!(pending_count, 0);
673 }
674
675 #[test]
676 fn test_downloads_table() {
677 let db = Database::open_in_memory().unwrap();
678 let conn = db.connection();
679 let conn = conn.lock_safe();
680
681 conn.execute(
683 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
684 params!["server1", "Test", "http://localhost"],
685 )
686 .unwrap();
687
688 conn.execute(
689 "INSERT INTO users (id, server_id, username) VALUES (?1, ?2, ?3)",
690 params!["user1", "server1", "admin"],
691 )
692 .unwrap();
693
694 conn.execute(
695 "INSERT INTO items (id, server_id, name, item_type) VALUES (?1, ?2, ?3, ?4)",
696 params!["item1", "server1", "Test Song", "Audio"],
697 )
698 .unwrap();
699
700 conn.execute(
702 "INSERT INTO downloads (item_id, user_id, file_path, status, progress)
703 VALUES (?1, ?2, ?3, ?4, ?5)",
704 params!["item1", "user1", "/data/downloads/test.mp3", "pending", 0.0],
705 )
706 .unwrap();
707
708 conn.execute(
710 "UPDATE downloads SET status = 'downloading', progress = 0.5
711 WHERE item_id = ?1",
712 ["item1"],
713 )
714 .unwrap();
715
716 let (status, progress): (String, f64) = conn
717 .query_row(
718 "SELECT status, progress FROM downloads WHERE item_id = ?1",
719 ["item1"],
720 |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?)),
721 )
722 .unwrap();
723 assert_eq!(status, "downloading");
724 assert!((progress - 0.5).abs() < 0.001);
725
726 conn.execute(
728 "UPDATE downloads SET status = 'completed', progress = 1.0
729 WHERE item_id = ?1",
730 ["item1"],
731 )
732 .unwrap();
733
734 let status: String = conn
735 .query_row(
736 "SELECT status FROM downloads WHERE item_id = ?1",
737 ["item1"],
738 |row: &rusqlite::Row| row.get(0),
739 )
740 .unwrap();
741 assert_eq!(status, "completed");
742 }
743
744 #[test]
745 fn test_migrations_idempotent() {
746 let db = Database::open_in_memory().unwrap();
747
748 let result = db.migrate();
750 assert!(result.is_ok());
751
752 let conn = db.connection();
754 let conn = conn.lock_safe();
755
756 let count: i32 = conn
757 .query_row(
758 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='items'",
759 [],
760 |row: &rusqlite::Row| row.get(0),
761 )
762 .unwrap();
763 assert_eq!(count, 1);
764 }
765
766 #[test]
767 fn test_global_active_user_deactivation() {
768 let db = Database::open_in_memory().unwrap();
769 let conn = db.connection();
770 let conn = conn.lock_safe();
771
772 conn.execute(
774 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
775 params!["server1", "Server 1", "http://server1.com"],
776 )
777 .unwrap();
778
779 conn.execute(
780 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
781 params!["server2", "Server 2", "http://server2.com"],
782 )
783 .unwrap();
784
785 conn.execute(
787 "INSERT INTO users (id, server_id, username, is_active, last_login_at)
788 VALUES (?1, ?2, ?3, 1, '2024-01-01 10:00:00')",
789 params!["user1", "server1", "admin"],
790 )
791 .unwrap();
792
793 conn.execute(
794 "INSERT INTO users (id, server_id, username, is_active, last_login_at)
795 VALUES (?1, ?2, ?3, 1, '2024-01-01 11:00:00')",
796 params!["user2", "server2", "admin"],
797 )
798 .unwrap();
799
800 let active_count: i32 = conn
802 .query_row(
803 "SELECT COUNT(*) FROM users WHERE is_active = 1",
804 [],
805 |row: &rusqlite::Row| row.get(0),
806 )
807 .unwrap();
808 assert_eq!(active_count, 2);
809
810 conn.execute("UPDATE users SET is_active = 0", []).unwrap();
812 conn.execute(
813 "UPDATE users SET is_active = 1, last_login_at = CURRENT_TIMESTAMP WHERE id = ?1",
814 ["user1"],
815 )
816 .unwrap();
817
818 let active_count: i32 = conn
820 .query_row(
821 "SELECT COUNT(*) FROM users WHERE is_active = 1",
822 [],
823 |row: &rusqlite::Row| row.get(0),
824 )
825 .unwrap();
826 assert_eq!(active_count, 1);
827
828 let active_user: String = conn
830 .query_row(
831 "SELECT id FROM users WHERE is_active = 1",
832 [],
833 |row: &rusqlite::Row| row.get(0),
834 )
835 .unwrap();
836 assert_eq!(active_user, "user1");
837 }
838
839 #[test]
840 fn test_active_session_query_ordering() {
841 let db = Database::open_in_memory().unwrap();
842 let conn = db.connection();
843 let conn = conn.lock_safe();
844
845 conn.execute(
847 "INSERT INTO servers (id, name, url) VALUES (?1, ?2, ?3)",
848 params!["server1", "Test Server", "http://localhost:8096"],
849 )
850 .unwrap();
851
852 conn.execute(
854 "INSERT INTO users (id, server_id, username, is_active, last_login_at)
855 VALUES (?1, ?2, ?3, 0, '2024-01-01 10:00:00')",
856 params!["user1", "server1", "old_user"],
857 )
858 .unwrap();
859
860 conn.execute(
861 "INSERT INTO users (id, server_id, username, is_active, last_login_at)
862 VALUES (?1, ?2, ?3, 1, '2024-01-01 12:00:00')",
863 params!["user2", "server1", "recent_user"],
864 )
865 .unwrap();
866
867 let (user_id, username): (String, String) = conn
869 .query_row(
870 "SELECT u.id, u.username FROM users u
871 JOIN servers s ON u.server_id = s.id
872 WHERE u.is_active = 1
873 ORDER BY u.last_login_at DESC
874 LIMIT 1",
875 [],
876 |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?)),
877 )
878 .unwrap();
879
880 assert_eq!(user_id, "user2");
881 assert_eq!(username, "recent_user");
882 }
883}