from pathlib import Path from serato_doctor.health import analyze_health from serato_doctor.models.crate import Crate, CrateKind from serato_doctor.models.database import DatabaseTrack, SeratoDatabase from serato_doctor.models.filesystem import BrokenSymlink from serato_doctor.models.library import Library from serato_doctor.models.reference import TrackReference from serato_doctor.models.track import DiskTrack def reference(filename): return TrackReference( Path("Test.crate"), Path("/old/House") / filename, filename ) def track(filename, folder="House"): path = Path("/new") / folder / filename return DiskTrack(path, filename, 100, path.suffix.lower()) def test_health_report_exposes_each_metric(): library = Library.build( [ reference("Found.mp3"), reference("Conflict.mp3"), reference("Absent.mp3"), ], [ track("Found.mp3"), track("Conflict 2.mp3"), track("Conflict 2.mp3", "Backup"), track("Unused.mp3"), ], ) report = analyze_health(library) assert report.score == 33.3 assert report.score_basis == ( "Resolved non-dynamic references / non-dynamic references scored" ) assert report.total_references == 3 assert report.scored_references == 3 assert report.healthy_references == 1 assert report.missing_references == 2 assert report.unique_missing_filenames == 2 assert report.disk_tracks == 4 assert report.duplicate_filename_groups == 1 assert report.duplicate_files == 1 assert report.unused_tracks == 3 assert report.orphan_candidates is None assert report.suggested_matches == 1 def test_empty_library_has_no_health_score(): report = analyze_health(Library.build([], [])) assert report.score is None assert report.total_references == 0 assert report.scored_references == 0 assert not report.database_present assert report.tracks_missing_from_database == 0 assert report.orphan_candidates is None def test_orphan_candidates_exclude_tracks_known_to_serato_or_crates(): tracks = [ track("Crated.mp3"), track("LibraryOnly.mp3"), track("OutsideEverything.mp3"), ] database = SeratoDatabase( Path("database V2"), "test", ( DatabaseTrack( Path("/new/House/LibraryOnly.mp3"), "LibraryOnly.mp3" ), ), ) library = Library.build( [reference("Crated.mp3")], tracks, database=database ) report = analyze_health(library) assert report.unused_tracks == 2 assert report.orphan_candidates == 1 def test_smart_crate_references_are_reported_but_not_scored(): static_reference = reference("Found.mp3") smart_reference = TrackReference( Path("SmartCrates/Dynamic.scrate"), Path("/old/House/Dynamic.mp3"), "Dynamic.mp3", ) crates = [ Crate(Path("Subcrates/Static.crate"), (static_reference,), CrateKind.STATIC), Crate( Path("SmartCrates/Dynamic.scrate"), (smart_reference,), CrateKind.SMART, ), ] library = Library.from_crates(crates, [track("Found.mp3")]) report = analyze_health(library) assert report.score == 100.0 assert report.total_references == 2 assert report.scored_references == 1 assert report.missing_references == 0 assert report.static_crates == 1 assert report.smart_crates == 1 assert report.smart_crate_containers == 0 assert report.dynamic_references_excluded == 1 def test_health_reports_cloud_conflicts_separately_from_exact_duplicates(): library = Library.build( [], [ track("Track.mp3", "Original"), track("Track 2.mp3", "Conflict"), track("Copy.mp3", "First"), track("Copy.mp3", "Second"), ], ) report = analyze_health(library) assert report.duplicate_filename_groups == 1 assert report.duplicate_files == 1 assert report.suspected_cloud_conflict_groups == 1 assert report.suspected_cloud_conflict_files == 1 def test_health_reports_broken_symlinks_without_changing_score(): library = Library.build( [reference("Found.mp3")], [track("Found.mp3")], [BrokenSymlink(Path("/music/Broken.mp3"), Path("missing.mp3"))], ) report = analyze_health(library) assert report.score == 100.0 assert report.broken_symlinks == 1