Files
db/tests/integration.rs
Connor Johnstone 305ddff278 Initial commit
2026-03-17 14:14:46 -04:00

278 lines
8.1 KiB
Rust

use chrono::Utc;
use sea_orm::*;
use shanty_db::entities::download_queue::DownloadStatus;
use shanty_db::entities::wanted_item::{ItemType, WantedStatus};
use shanty_db::{Database, queries};
async fn test_db() -> Database {
Database::new("sqlite::memory:").await.expect("failed to create test database")
}
#[tokio::test]
async fn test_database_creation_and_migrations() {
let _db = test_db().await;
}
#[tokio::test]
async fn test_artist_crud() {
let db = test_db().await;
let conn = db.conn();
// Create
let artist = queries::artists::upsert(conn, "Pink Floyd", None)
.await
.unwrap();
assert_eq!(artist.name, "Pink Floyd");
assert!(artist.musicbrainz_id.is_none());
// Read
let found = queries::artists::get_by_id(conn, artist.id).await.unwrap();
assert_eq!(found.name, "Pink Floyd");
// Find by name
let found = queries::artists::find_by_name(conn, "Pink Floyd")
.await
.unwrap();
assert!(found.is_some());
// Upsert (same name, should return existing)
let same = queries::artists::upsert(conn, "Pink Floyd", None)
.await
.unwrap();
assert_eq!(same.id, artist.id);
// Upsert with musicbrainz_id updates existing
let updated = queries::artists::upsert(conn, "Pink Floyd", Some("mb-123"))
.await
.unwrap();
assert_eq!(updated.id, artist.id);
assert_eq!(updated.musicbrainz_id.as_deref(), Some("mb-123"));
// List
let all = queries::artists::list(conn, 100, 0).await.unwrap();
assert_eq!(all.len(), 1);
// Update top songs
let updated = queries::artists::update_top_songs(conn, artist.id, r#"["Time","Money"]"#)
.await
.unwrap();
assert_eq!(updated.top_songs, r#"["Time","Money"]"#);
// Delete
queries::artists::delete(conn, artist.id).await.unwrap();
let all = queries::artists::list(conn, 100, 0).await.unwrap();
assert!(all.is_empty());
}
#[tokio::test]
async fn test_album_crud() {
let db = test_db().await;
let conn = db.conn();
let artist = queries::artists::upsert(conn, "Pink Floyd", None)
.await
.unwrap();
// Create album
let album = queries::albums::upsert(
conn,
"The Dark Side of the Moon",
"Pink Floyd",
None,
Some(artist.id),
)
.await
.unwrap();
assert_eq!(album.name, "The Dark Side of the Moon");
assert_eq!(album.artist_id, Some(artist.id));
// Upsert same album returns existing
let same = queries::albums::upsert(
conn,
"The Dark Side of the Moon",
"Pink Floyd",
None,
Some(artist.id),
)
.await
.unwrap();
assert_eq!(same.id, album.id);
// Get by artist
let albums = queries::albums::get_by_artist(conn, artist.id)
.await
.unwrap();
assert_eq!(albums.len(), 1);
}
#[tokio::test]
async fn test_track_upsert_and_search() {
let db = test_db().await;
let conn = db.conn();
let now = Utc::now().naive_utc();
// Insert a track with partial metadata
let active = shanty_db::entities::track::ActiveModel {
file_path: Set("/music/time.flac".to_string()),
title: Set(Some("Time".to_string())),
artist: Set(Some("Pink Floyd".to_string())),
album: Set(Some("The Dark Side of the Moon".to_string())),
file_size: Set(42_000_000),
added_at: Set(now),
updated_at: Set(now),
..Default::default()
};
let track = queries::tracks::upsert(conn, active).await.unwrap();
assert_eq!(track.title.as_deref(), Some("Time"));
assert!(track.artist_id.is_none()); // no FK yet
// Upsert same file_path should update, not duplicate
let active2 = shanty_db::entities::track::ActiveModel {
file_path: Set("/music/time.flac".to_string()),
title: Set(Some("Time".to_string())),
artist: Set(Some("Pink Floyd".to_string())),
album: Set(Some("The Dark Side of the Moon".to_string())),
file_size: Set(42_000_000),
bitrate: Set(Some(1411)),
..Default::default()
};
let updated = queries::tracks::upsert(conn, active2).await.unwrap();
assert_eq!(updated.id, track.id);
assert_eq!(updated.bitrate, Some(1411));
// Search
let results = queries::tracks::search(conn, "Time").await.unwrap();
assert_eq!(results.len(), 1);
let results = queries::tracks::search(conn, "Pink Floyd").await.unwrap();
assert_eq!(results.len(), 1);
// Untagged (no musicbrainz_id)
let untagged = queries::tracks::get_untagged(conn).await.unwrap();
assert_eq!(untagged.len(), 1);
}
#[tokio::test]
async fn test_wanted_items_lifecycle() {
let db = test_db().await;
let conn = db.conn();
let artist = queries::artists::upsert(conn, "Radiohead", None)
.await
.unwrap();
// Add wanted item
let item = queries::wanted::add(conn, ItemType::Artist, Some(artist.id), None, None)
.await
.unwrap();
assert_eq!(item.status, WantedStatus::Wanted);
assert_eq!(item.item_type, ItemType::Artist);
// List with filter
let wanted = queries::wanted::list(conn, Some(WantedStatus::Wanted))
.await
.unwrap();
assert_eq!(wanted.len(), 1);
let downloaded = queries::wanted::list(conn, Some(WantedStatus::Downloaded))
.await
.unwrap();
assert!(downloaded.is_empty());
// Update status
let updated = queries::wanted::update_status(conn, item.id, WantedStatus::Downloaded)
.await
.unwrap();
assert_eq!(updated.status, WantedStatus::Downloaded);
// Remove
queries::wanted::remove(conn, item.id).await.unwrap();
let all = queries::wanted::list(conn, None).await.unwrap();
assert!(all.is_empty());
}
#[tokio::test]
async fn test_download_queue_lifecycle() {
let db = test_db().await;
let conn = db.conn();
// Enqueue
let item = queries::downloads::enqueue(conn, "Pink Floyd Time", None, "ytdlp")
.await
.unwrap();
assert_eq!(item.status, DownloadStatus::Pending);
assert_eq!(item.query, "Pink Floyd Time");
// Get next pending
let next = queries::downloads::get_next_pending(conn).await.unwrap();
assert!(next.is_some());
assert_eq!(next.unwrap().id, item.id);
// Update to downloading
queries::downloads::update_status(conn, item.id, DownloadStatus::Downloading, None)
.await
.unwrap();
// No more pending
let next = queries::downloads::get_next_pending(conn).await.unwrap();
assert!(next.is_none());
// Fail it
queries::downloads::update_status(
conn,
item.id,
DownloadStatus::Failed,
Some("network error"),
)
.await
.unwrap();
// List failed
let failed = queries::downloads::list(conn, Some(DownloadStatus::Failed))
.await
.unwrap();
assert_eq!(failed.len(), 1);
assert_eq!(failed[0].error_message.as_deref(), Some("network error"));
// Retry
queries::downloads::retry_failed(conn, item.id).await.unwrap();
let pending = queries::downloads::list(conn, Some(DownloadStatus::Pending))
.await
.unwrap();
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].retry_count, 1);
}
#[tokio::test]
async fn test_search_cache_ttl() {
let db = test_db().await;
let conn = db.conn();
// Set cache with long TTL
queries::cache::set(conn, "test_query", "musicbrainz", r#"{"results":[]}"#, 3600)
.await
.unwrap();
// Get should return the value
let result = queries::cache::get(conn, "test_query").await.unwrap();
assert_eq!(result.as_deref(), Some(r#"{"results":[]}"#));
// Non-existent key
let result = queries::cache::get(conn, "nonexistent").await.unwrap();
assert!(result.is_none());
// Set cache with 0-second TTL (already expired)
queries::cache::set(conn, "expired_query", "musicbrainz", r#"{"old":true}"#, 0)
.await
.unwrap();
// Should not return expired entry
let result = queries::cache::get(conn, "expired_query").await.unwrap();
assert!(result.is_none());
// Purge expired
let purged = queries::cache::purge_expired(conn).await.unwrap();
assert_eq!(purged, 1);
}