Add audit tooling for scrobbled tracks data quality (#89)
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
defmodule Mix.Tasks.Scrobble.AuditTest do
|
||||
use MusicLibrary.DataCase
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
import MusicLibrary.ScrobbledTracksFixtures
|
||||
|
||||
alias Mix.Tasks.Scrobble.Audit
|
||||
|
||||
describe "run/1" do
|
||||
test "generates audit report with no tracks" do
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run([])
|
||||
end)
|
||||
|
||||
assert output =~ "Scrobbled Tracks Data Quality Audit"
|
||||
assert output =~ "Total scrobbled tracks: 0"
|
||||
end
|
||||
|
||||
test "raises error for invalid type" do
|
||||
assert_raise Mix.Error, ~r/Invalid type/, fn ->
|
||||
capture_io(fn ->
|
||||
Audit.run(["--type", "invalid"])
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
test "raises error for invalid format" do
|
||||
assert_raise Mix.Error, ~r/Invalid format/, fn ->
|
||||
capture_io(fn ->
|
||||
Audit.run(["--format", "invalid"])
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
test "generates audit report with tracks having missing artist musicbrainz_ids" do
|
||||
# Create tracks with empty artist musicbrainz_id
|
||||
track_fixture(%{
|
||||
artist_name: "Test Artist 1",
|
||||
artist_musicbrainz_id: "",
|
||||
title: "Track 1"
|
||||
})
|
||||
|
||||
track_fixture(%{
|
||||
artist_name: "Test Artist 1",
|
||||
artist_musicbrainz_id: "",
|
||||
title: "Track 2"
|
||||
})
|
||||
|
||||
track_fixture(%{
|
||||
artist_name: "Test Artist 2",
|
||||
artist_musicbrainz_id: "",
|
||||
title: "Track 3"
|
||||
})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run([])
|
||||
end)
|
||||
|
||||
assert output =~ "Artists with Missing MusicBrainz IDs"
|
||||
assert output =~ "Unique artists: 2"
|
||||
assert output =~ "Affected tracks: 3"
|
||||
assert output =~ "Test Artist 1"
|
||||
assert output =~ "Test Artist 2"
|
||||
end
|
||||
|
||||
test "generates audit report with tracks having missing album musicbrainz_ids" do
|
||||
# Create tracks with empty album musicbrainz_id
|
||||
track_fixture(%{
|
||||
album_title: "Test Album 1",
|
||||
album_musicbrainz_id: "",
|
||||
artist_name: "Test Artist",
|
||||
title: "Track 1"
|
||||
})
|
||||
|
||||
track_fixture(%{
|
||||
album_title: "Test Album 1",
|
||||
album_musicbrainz_id: "",
|
||||
artist_name: "Test Artist",
|
||||
title: "Track 2"
|
||||
})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run([])
|
||||
end)
|
||||
|
||||
assert output =~ "Albums with Missing MusicBrainz IDs"
|
||||
assert output =~ "Unique albums: 1"
|
||||
assert output =~ "Affected tracks: 2"
|
||||
assert output =~ "Test Album 1"
|
||||
end
|
||||
|
||||
test "generates audit report with --verbose flag" do
|
||||
track_fixture(%{
|
||||
artist_name: "Test Artist",
|
||||
artist_musicbrainz_id: "",
|
||||
title: "Test Track",
|
||||
album_title: "Test Album"
|
||||
})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run(["--verbose"])
|
||||
end)
|
||||
|
||||
assert output =~ "Sample tracks:"
|
||||
assert output =~ "Test Artist"
|
||||
assert output =~ "Test Track"
|
||||
end
|
||||
|
||||
test "generates audit report for artists only with --type artist" do
|
||||
track_fixture(%{
|
||||
artist_name: "Test Artist",
|
||||
artist_musicbrainz_id: "",
|
||||
album_musicbrainz_id: "",
|
||||
title: "Track 1"
|
||||
})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run(["--type", "artist"])
|
||||
end)
|
||||
|
||||
assert output =~ "Artists with Missing MusicBrainz IDs"
|
||||
refute output =~ "Albums with Missing MusicBrainz IDs"
|
||||
end
|
||||
|
||||
test "generates audit report for albums only with --type album" do
|
||||
track_fixture(%{
|
||||
album_title: "Test Album",
|
||||
album_musicbrainz_id: "",
|
||||
artist_musicbrainz_id: "",
|
||||
title: "Track 1"
|
||||
})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run(["--type", "album"])
|
||||
end)
|
||||
|
||||
assert output =~ "Albums with Missing MusicBrainz IDs"
|
||||
refute output =~ "Artists with Missing MusicBrainz IDs"
|
||||
end
|
||||
|
||||
test "generates JSON output with --format json" do
|
||||
track_fixture(%{
|
||||
artist_name: "Test Artist",
|
||||
artist_musicbrainz_id: "",
|
||||
title: "Track 1"
|
||||
})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run(["--format", "json"])
|
||||
end)
|
||||
|
||||
assert output =~ ~s("total_tracks")
|
||||
assert output =~ ~s("artist_issues")
|
||||
assert output =~ ~s("album_issues")
|
||||
|
||||
# Validate it's valid JSON
|
||||
assert {:ok, _parsed} = Jason.decode(output)
|
||||
end
|
||||
|
||||
test "audit report shows summary with remediation steps" do
|
||||
track_fixture(%{
|
||||
artist_name: "Test Artist",
|
||||
artist_musicbrainz_id: "",
|
||||
title: "Track 1"
|
||||
})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Audit.run([])
|
||||
end)
|
||||
|
||||
assert output =~ "Summary"
|
||||
assert output =~ "To fix these issues:"
|
||||
assert output =~ "Create scrobble rules"
|
||||
assert output =~ "MusicLibrary.ScrobbleRules.apply_all_rules()"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -414,6 +414,172 @@ defmodule MusicLibrary.ScrobbleActivityTest do
|
||||
|> Enum.map(fn r -> r.track end)
|
||||
end
|
||||
|
||||
describe "count_tracks_missing_artist_musicbrainz_id/0" do
|
||||
test "returns zero when no tracks exist" do
|
||||
count = ScrobbleActivity.count_tracks_missing_artist_musicbrainz_id()
|
||||
|
||||
assert count == 0
|
||||
end
|
||||
|
||||
test "returns count of tracks with empty artist musicbrainz_id" do
|
||||
track_fixture(%{artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_musicbrainz_id: "valid-id"})
|
||||
|
||||
count = ScrobbleActivity.count_tracks_missing_artist_musicbrainz_id()
|
||||
|
||||
assert count == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "count_tracks_missing_album_musicbrainz_id/0" do
|
||||
test "returns zero when no tracks exist" do
|
||||
count = ScrobbleActivity.count_tracks_missing_album_musicbrainz_id()
|
||||
|
||||
assert count == 0
|
||||
end
|
||||
|
||||
test "returns count of tracks with empty album musicbrainz_id" do
|
||||
track_fixture(%{album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_musicbrainz_id: "valid-id"})
|
||||
|
||||
count = ScrobbleActivity.count_tracks_missing_album_musicbrainz_id()
|
||||
|
||||
assert count == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_artists_missing_musicbrainz_id/1" do
|
||||
test "returns empty list when no tracks exist" do
|
||||
result = ScrobbleActivity.get_artists_missing_musicbrainz_id()
|
||||
|
||||
assert result == []
|
||||
end
|
||||
|
||||
test "returns artists grouped by name with track counts" do
|
||||
track_fixture(%{artist_name: "Artist A", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist A", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist B", artist_musicbrainz_id: ""})
|
||||
|
||||
result = ScrobbleActivity.get_artists_missing_musicbrainz_id()
|
||||
|
||||
assert length(result) == 2
|
||||
|
||||
artist_a = Enum.find(result, fn r -> r.artist_name == "Artist A" end)
|
||||
artist_b = Enum.find(result, fn r -> r.artist_name == "Artist B" end)
|
||||
|
||||
assert artist_a.track_count == 2
|
||||
assert artist_b.track_count == 1
|
||||
end
|
||||
|
||||
test "orders artists by track count descending" do
|
||||
track_fixture(%{artist_name: "Artist A", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist B", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist B", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist B", artist_musicbrainz_id: ""})
|
||||
|
||||
result = ScrobbleActivity.get_artists_missing_musicbrainz_id()
|
||||
|
||||
assert length(result) == 2
|
||||
assert List.first(result).artist_name == "Artist B"
|
||||
assert List.first(result).track_count == 3
|
||||
end
|
||||
|
||||
test "limits results when limit option provided" do
|
||||
track_fixture(%{artist_name: "Artist A", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist B", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist C", artist_musicbrainz_id: ""})
|
||||
|
||||
result = ScrobbleActivity.get_artists_missing_musicbrainz_id(limit: 2)
|
||||
|
||||
assert length(result) == 2
|
||||
end
|
||||
|
||||
test "excludes artists with valid musicbrainz_id" do
|
||||
track_fixture(%{artist_name: "Artist A", artist_musicbrainz_id: ""})
|
||||
track_fixture(%{artist_name: "Artist B", artist_musicbrainz_id: "valid-id"})
|
||||
|
||||
result = ScrobbleActivity.get_artists_missing_musicbrainz_id()
|
||||
|
||||
assert length(result) == 1
|
||||
assert List.first(result).artist_name == "Artist A"
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_albums_missing_musicbrainz_id/1" do
|
||||
test "returns empty list when no tracks exist" do
|
||||
result = ScrobbleActivity.get_albums_missing_musicbrainz_id()
|
||||
|
||||
assert result == []
|
||||
end
|
||||
|
||||
test "returns albums grouped by title and artist with track counts" do
|
||||
track_fixture(%{
|
||||
album_title: "Album A",
|
||||
artist_name: "Artist X",
|
||||
album_musicbrainz_id: ""
|
||||
})
|
||||
|
||||
track_fixture(%{
|
||||
album_title: "Album A",
|
||||
artist_name: "Artist X",
|
||||
album_musicbrainz_id: ""
|
||||
})
|
||||
|
||||
track_fixture(%{
|
||||
album_title: "Album B",
|
||||
artist_name: "Artist Y",
|
||||
album_musicbrainz_id: ""
|
||||
})
|
||||
|
||||
result = ScrobbleActivity.get_albums_missing_musicbrainz_id()
|
||||
|
||||
assert length(result) == 2
|
||||
|
||||
album_a = Enum.find(result, fn r -> r.album_title == "Album A" end)
|
||||
album_b = Enum.find(result, fn r -> r.album_title == "Album B" end)
|
||||
|
||||
assert album_a.track_count == 2
|
||||
assert album_a.artist_name == "Artist X"
|
||||
assert album_b.track_count == 1
|
||||
assert album_b.artist_name == "Artist Y"
|
||||
end
|
||||
|
||||
test "orders albums by track count descending" do
|
||||
track_fixture(%{album_title: "Album A", album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_title: "Album B", album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_title: "Album B", album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_title: "Album B", album_musicbrainz_id: ""})
|
||||
|
||||
result = ScrobbleActivity.get_albums_missing_musicbrainz_id()
|
||||
|
||||
assert length(result) == 2
|
||||
assert List.first(result).album_title == "Album B"
|
||||
assert List.first(result).track_count == 3
|
||||
end
|
||||
|
||||
test "limits results when limit option provided" do
|
||||
track_fixture(%{album_title: "Album A", album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_title: "Album B", album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_title: "Album C", album_musicbrainz_id: ""})
|
||||
|
||||
result = ScrobbleActivity.get_albums_missing_musicbrainz_id(limit: 2)
|
||||
|
||||
assert length(result) == 2
|
||||
end
|
||||
|
||||
test "excludes albums with valid musicbrainz_id" do
|
||||
track_fixture(%{album_title: "Album A", album_musicbrainz_id: ""})
|
||||
track_fixture(%{album_title: "Album B", album_musicbrainz_id: "valid-id"})
|
||||
|
||||
result = ScrobbleActivity.get_albums_missing_musicbrainz_id()
|
||||
|
||||
assert length(result) == 1
|
||||
assert List.first(result).album_title == "Album A"
|
||||
end
|
||||
end
|
||||
|
||||
defp list_tracks(params) do
|
||||
ScrobbleActivity.list_tracks(params)
|
||||
|> Enum.map(fn r -> r.track end)
|
||||
|
||||
Reference in New Issue
Block a user