243 lines
7.0 KiB
Rust
243 lines
7.0 KiB
Rust
use chrono::Utc;
|
|
use sea_orm::ActiveValue::Set;
|
|
|
|
use shanty_data::DataResult;
|
|
use shanty_db::{Database, queries};
|
|
use shanty_tag::provider::{
|
|
ArtistSearchResult, DiscographyEntry, MetadataProvider, RecordingDetails, RecordingMatch,
|
|
ReleaseMatch, ReleaseRef,
|
|
};
|
|
use shanty_tag::{TagConfig, run_tagging};
|
|
|
|
/// A mock metadata provider for testing without hitting MusicBrainz.
|
|
struct MockProvider;
|
|
|
|
impl MetadataProvider for MockProvider {
|
|
async fn search_recording(&self, artist: &str, title: &str) -> DataResult<Vec<RecordingMatch>> {
|
|
// Return a match for "Pink Floyd - Time"
|
|
if artist.contains("Pink Floyd") && title.contains("Time") {
|
|
Ok(vec![RecordingMatch {
|
|
mbid: "rec-123".into(),
|
|
title: "Time".into(),
|
|
artist: "Pink Floyd".into(),
|
|
artist_mbid: Some("artist-456".into()),
|
|
releases: vec![ReleaseRef {
|
|
mbid: "release-789".into(),
|
|
title: "The Dark Side of the Moon".into(),
|
|
date: Some("1973-03-01".into()),
|
|
track_number: Some(4),
|
|
}],
|
|
score: 100,
|
|
}])
|
|
} else {
|
|
Ok(vec![])
|
|
}
|
|
}
|
|
|
|
async fn search_release(&self, _artist: &str, _album: &str) -> DataResult<Vec<ReleaseMatch>> {
|
|
Ok(vec![])
|
|
}
|
|
|
|
async fn get_recording(&self, mbid: &str) -> DataResult<RecordingDetails> {
|
|
if mbid == "rec-123" {
|
|
Ok(RecordingDetails {
|
|
mbid: "rec-123".into(),
|
|
title: "Time".into(),
|
|
artist: "Pink Floyd".into(),
|
|
artist_mbid: Some("artist-456".into()),
|
|
releases: vec![ReleaseRef {
|
|
mbid: "release-789".into(),
|
|
title: "The Dark Side of the Moon".into(),
|
|
date: Some("1973-03-01".into()),
|
|
track_number: Some(4),
|
|
}],
|
|
duration_ms: Some(413_000),
|
|
genres: vec!["Progressive Rock".into()],
|
|
secondary_artists: vec![],
|
|
})
|
|
} else {
|
|
Err(shanty_data::DataError::Other("not found".into()))
|
|
}
|
|
}
|
|
|
|
async fn search_artist(
|
|
&self,
|
|
_query: &str,
|
|
_limit: u32,
|
|
) -> DataResult<Vec<ArtistSearchResult>> {
|
|
Ok(vec![])
|
|
}
|
|
|
|
async fn get_artist_releases(
|
|
&self,
|
|
_artist_mbid: &str,
|
|
_limit: u32,
|
|
) -> DataResult<Vec<DiscographyEntry>> {
|
|
Ok(vec![])
|
|
}
|
|
|
|
async fn get_release_tracks(
|
|
&self,
|
|
_release_mbid: &str,
|
|
) -> DataResult<Vec<shanty_tag::provider::ReleaseTrack>> {
|
|
Ok(vec![])
|
|
}
|
|
|
|
async fn get_artist_release_groups(
|
|
&self,
|
|
_artist_mbid: &str,
|
|
) -> DataResult<Vec<shanty_tag::provider::ReleaseGroupEntry>> {
|
|
Ok(vec![])
|
|
}
|
|
}
|
|
|
|
async fn test_db() -> Database {
|
|
Database::new("sqlite::memory:")
|
|
.await
|
|
.expect("failed to create test database")
|
|
}
|
|
|
|
async fn insert_untagged_track(
|
|
db: &Database,
|
|
file_path: &str,
|
|
title: Option<&str>,
|
|
artist: Option<&str>,
|
|
) -> i32 {
|
|
let now = Utc::now().naive_utc();
|
|
let active = shanty_db::entities::track::ActiveModel {
|
|
file_path: Set(file_path.to_string()),
|
|
title: Set(title.map(String::from)),
|
|
artist: Set(artist.map(String::from)),
|
|
file_size: Set(1_000_000),
|
|
added_at: Set(now),
|
|
updated_at: Set(now),
|
|
..Default::default()
|
|
};
|
|
let track = queries::tracks::upsert(db.conn(), active).await.unwrap();
|
|
track.id
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_tag_track_with_match() {
|
|
let db = test_db().await;
|
|
let provider = MockProvider;
|
|
|
|
let track_id =
|
|
insert_untagged_track(&db, "/music/time.mp3", Some("Time"), Some("Pink Floyd")).await;
|
|
|
|
let config = TagConfig {
|
|
dry_run: false,
|
|
write_tags: false,
|
|
confidence: 0.8,
|
|
};
|
|
|
|
let stats = run_tagging(db.conn(), &provider, &config, Some(track_id))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(stats.tracks_processed, 1);
|
|
assert_eq!(stats.tracks_matched, 1);
|
|
assert_eq!(stats.tracks_updated, 1);
|
|
|
|
// Verify the track was updated
|
|
let track = queries::tracks::get_by_id(db.conn(), track_id)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(track.musicbrainz_id.as_deref(), Some("rec-123"));
|
|
assert_eq!(track.title.as_deref(), Some("Time"));
|
|
assert_eq!(track.artist.as_deref(), Some("Pink Floyd"));
|
|
assert_eq!(track.album.as_deref(), Some("The Dark Side of the Moon"));
|
|
assert_eq!(track.year, Some(1973));
|
|
assert_eq!(track.genre.as_deref(), Some("Progressive Rock"));
|
|
|
|
// Verify artist was created with MusicBrainz ID
|
|
let artist = queries::artists::find_by_name(db.conn(), "Pink Floyd")
|
|
.await
|
|
.unwrap();
|
|
assert!(artist.is_some());
|
|
assert_eq!(
|
|
artist.unwrap().musicbrainz_id.as_deref(),
|
|
Some("artist-456")
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_tag_track_no_match() {
|
|
let db = test_db().await;
|
|
let provider = MockProvider;
|
|
|
|
let track_id = insert_untagged_track(
|
|
&db,
|
|
"/music/unknown.mp3",
|
|
Some("Unknown Song"),
|
|
Some("Nobody"),
|
|
)
|
|
.await;
|
|
|
|
let config = TagConfig {
|
|
dry_run: false,
|
|
write_tags: false,
|
|
confidence: 0.8,
|
|
};
|
|
|
|
let stats = run_tagging(db.conn(), &provider, &config, Some(track_id))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(stats.tracks_processed, 1);
|
|
assert_eq!(stats.tracks_skipped, 1);
|
|
|
|
// Track should be unchanged
|
|
let track = queries::tracks::get_by_id(db.conn(), track_id)
|
|
.await
|
|
.unwrap();
|
|
assert!(track.musicbrainz_id.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_dry_run_does_not_update() {
|
|
let db = test_db().await;
|
|
let provider = MockProvider;
|
|
|
|
let track_id =
|
|
insert_untagged_track(&db, "/music/time.mp3", Some("Time"), Some("Pink Floyd")).await;
|
|
|
|
let config = TagConfig {
|
|
dry_run: true,
|
|
write_tags: false,
|
|
confidence: 0.8,
|
|
};
|
|
|
|
let stats = run_tagging(db.conn(), &provider, &config, Some(track_id))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(stats.tracks_matched, 1);
|
|
assert_eq!(stats.tracks_updated, 0); // dry run
|
|
|
|
// Track should be unchanged
|
|
let track = queries::tracks::get_by_id(db.conn(), track_id)
|
|
.await
|
|
.unwrap();
|
|
assert!(track.musicbrainz_id.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_tag_all_untagged() {
|
|
let db = test_db().await;
|
|
let provider = MockProvider;
|
|
|
|
insert_untagged_track(&db, "/music/time.mp3", Some("Time"), Some("Pink Floyd")).await;
|
|
insert_untagged_track(&db, "/music/unknown.mp3", Some("Unknown"), Some("Nobody")).await;
|
|
|
|
let config = TagConfig {
|
|
dry_run: false,
|
|
write_tags: false,
|
|
confidence: 0.8,
|
|
};
|
|
|
|
let stats = run_tagging(db.conn(), &provider, &config, None)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(stats.tracks_processed, 2);
|
|
assert_eq!(stats.tracks_matched, 1); // only Pink Floyd matched
|
|
assert_eq!(stats.tracks_skipped, 1);
|
|
}
|