914fb4fbee
Pin the duplicate-rule error to the match_value field and "has already been taken" message, matching the unique_constraint error_key in the changeset. Refs #176
472 lines
16 KiB
Elixir
472 lines
16 KiB
Elixir
defmodule MusicLibrary.ScrobbleRulesTest do
|
|
use MusicLibrary.DataCase
|
|
|
|
import MusicLibrary.ScrobbleRulesFixtures
|
|
import MusicLibrary.ScrobbledTracksFixtures
|
|
|
|
alias LastFm.Track
|
|
alias MusicLibrary.ScrobbleRules
|
|
alias MusicLibrary.ScrobbleRules.ScrobbleRule
|
|
|
|
describe "scrobble_rules" do
|
|
@valid_album_attrs %{
|
|
type: "album",
|
|
match_value: "Dark Side of the Moon",
|
|
target_musicbrainz_id: "12345678-1234-1234-1234-123456789012",
|
|
enabled: true,
|
|
description: "Fix Pink Floyd album"
|
|
}
|
|
|
|
@valid_artist_attrs %{
|
|
type: "artist",
|
|
match_value: "Pink Floyd",
|
|
target_musicbrainz_id: "87654321-4321-4321-4321-210987654321",
|
|
enabled: true,
|
|
description: "Fix Pink Floyd artist"
|
|
}
|
|
|
|
@invalid_attrs %{type: nil, match_value: nil, target_musicbrainz_id: nil}
|
|
|
|
test "list_scrobble_rules/0 returns all scrobble_rules" do
|
|
scrobble_rule = scrobble_rule_fixture()
|
|
assert ScrobbleRules.list_scrobble_rules() == [scrobble_rule]
|
|
end
|
|
|
|
test "list_scrobble_rules/1 filters by type" do
|
|
album_rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
_artist_rule = scrobble_rule_fixture(@valid_artist_attrs)
|
|
|
|
assert ScrobbleRules.list_scrobble_rules(type: "album") == [album_rule]
|
|
end
|
|
|
|
test "list_scrobble_rules/1 filters by enabled status" do
|
|
enabled_rule = scrobble_rule_fixture(%{enabled: true})
|
|
_disabled_rule = scrobble_rule_fixture(%{enabled: false, match_value: "Different Album"})
|
|
|
|
assert ScrobbleRules.list_scrobble_rules(enabled: true) == [enabled_rule]
|
|
end
|
|
|
|
test "get_scrobble_rule!/1 returns the scrobble_rule with given id" do
|
|
scrobble_rule = scrobble_rule_fixture()
|
|
assert ScrobbleRules.get_scrobble_rule!(scrobble_rule.id) == scrobble_rule
|
|
end
|
|
|
|
test "create_scrobble_rule/1 with valid data creates a scrobble_rule" do
|
|
valid_attrs = @valid_album_attrs
|
|
|
|
assert {:ok, %ScrobbleRule{} = scrobble_rule} =
|
|
ScrobbleRules.create_scrobble_rule(valid_attrs)
|
|
|
|
assert scrobble_rule.type == :album
|
|
assert scrobble_rule.match_value == "Dark Side of the Moon"
|
|
assert scrobble_rule.target_musicbrainz_id == "12345678-1234-1234-1234-123456789012"
|
|
assert scrobble_rule.enabled == true
|
|
assert scrobble_rule.description == "Fix Pink Floyd album"
|
|
end
|
|
|
|
test "create_scrobble_rule/1 doesn't allow duplicates for the same type" do
|
|
valid_attrs = Map.put(@valid_album_attrs, :type, :album)
|
|
|
|
assert {:ok, %ScrobbleRule{} = scrobble_rule} =
|
|
ScrobbleRules.create_scrobble_rule(valid_attrs)
|
|
|
|
assert scrobble_rule.type == :album
|
|
|
|
assert {:error, changeset} = ScrobbleRules.create_scrobble_rule(valid_attrs)
|
|
assert %{match_value: ["has already been taken"]} = errors_on(changeset)
|
|
end
|
|
|
|
test "create_scrobble_rule/1 with invalid data returns error changeset" do
|
|
assert {:error, %Ecto.Changeset{}} = ScrobbleRules.create_scrobble_rule(@invalid_attrs)
|
|
end
|
|
|
|
test "update_scrobble_rule/2 with valid data updates the scrobble_rule" do
|
|
scrobble_rule = scrobble_rule_fixture()
|
|
update_attrs = %{enabled: false, description: "Updated description"}
|
|
|
|
assert {:ok, %ScrobbleRule{} = scrobble_rule} =
|
|
ScrobbleRules.update_scrobble_rule(scrobble_rule, update_attrs)
|
|
|
|
assert scrobble_rule.enabled == false
|
|
assert scrobble_rule.description == "Updated description"
|
|
end
|
|
|
|
test "update_scrobble_rule/2 with invalid data returns error changeset" do
|
|
scrobble_rule = scrobble_rule_fixture()
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
ScrobbleRules.update_scrobble_rule(scrobble_rule, @invalid_attrs)
|
|
|
|
assert scrobble_rule == ScrobbleRules.get_scrobble_rule!(scrobble_rule.id)
|
|
end
|
|
|
|
test "delete_scrobble_rule/1 deletes the scrobble_rule" do
|
|
scrobble_rule = scrobble_rule_fixture()
|
|
assert {:ok, %ScrobbleRule{}} = ScrobbleRules.delete_scrobble_rule(scrobble_rule)
|
|
|
|
assert_raise Ecto.NoResultsError, fn ->
|
|
ScrobbleRules.get_scrobble_rule!(scrobble_rule.id)
|
|
end
|
|
end
|
|
|
|
test "change_scrobble_rule/1 returns a scrobble_rule changeset" do
|
|
scrobble_rule = scrobble_rule_fixture()
|
|
assert %Ecto.Changeset{} = ScrobbleRules.change_scrobble_rule(scrobble_rule)
|
|
end
|
|
|
|
test "list_enabled_rules/0 returns only enabled rules" do
|
|
enabled_rule = scrobble_rule_fixture(%{enabled: true})
|
|
_disabled_rule = scrobble_rule_fixture(%{enabled: false, match_value: "Different Album"})
|
|
|
|
assert ScrobbleRules.list_enabled_rules() == [enabled_rule]
|
|
end
|
|
|
|
test "list_scrobble_rules/1 filters by query on match_value" do
|
|
rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
|
|
_other =
|
|
scrobble_rule_fixture(%{
|
|
match_value: "Unrelated Album",
|
|
description: "Other rule"
|
|
})
|
|
|
|
assert ScrobbleRules.list_scrobble_rules(query: "Dark Side") == [rule]
|
|
end
|
|
|
|
test "list_scrobble_rules/1 filters by query on target_musicbrainz_id" do
|
|
rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
|
|
_other =
|
|
scrobble_rule_fixture(%{
|
|
match_value: "Unrelated Album",
|
|
target_musicbrainz_id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
|
description: "Other rule"
|
|
})
|
|
|
|
assert ScrobbleRules.list_scrobble_rules(query: "12345678") == [rule]
|
|
end
|
|
|
|
test "list_scrobble_rules/1 filters by query on description" do
|
|
rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
_other = scrobble_rule_fixture(%{match_value: "Unrelated Album", description: "Other rule"})
|
|
|
|
assert ScrobbleRules.list_scrobble_rules(query: "Fix Pink Floyd album") == [rule]
|
|
end
|
|
|
|
test "count_scrobble_rules/1 filters by query" do
|
|
_rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
_other = scrobble_rule_fixture(%{match_value: "Unrelated Album", description: "Other rule"})
|
|
|
|
assert ScrobbleRules.count_scrobble_rules(query: "Dark Side") == 1
|
|
assert ScrobbleRules.count_scrobble_rules(query: "") == 2
|
|
assert ScrobbleRules.count_scrobble_rules() == 2
|
|
end
|
|
|
|
test "list_scrobble_rules/1 orders alphabetically" do
|
|
rule_b = scrobble_rule_fixture(%{match_value: "Beta Album"})
|
|
rule_a = scrobble_rule_fixture(%{match_value: "Alpha Album"})
|
|
|
|
assert ScrobbleRules.list_scrobble_rules(order: :alphabetical) == [rule_a, rule_b]
|
|
end
|
|
|
|
test "list_scrobble_rules/1 orders by inserted_at descending by default" do
|
|
rule_first = scrobble_rule_fixture(%{match_value: "First Album"})
|
|
rule_second = scrobble_rule_fixture(%{match_value: "Second Album"})
|
|
|
|
# Both may have the same inserted_at due to SQLite second precision,
|
|
# so set them explicitly
|
|
import Ecto.Query
|
|
|
|
now = DateTime.utc_now()
|
|
earlier = DateTime.add(now, -60, :second)
|
|
|
|
from(r in ScrobbleRule, where: r.id == ^rule_first.id)
|
|
|> Repo.update_all(set: [inserted_at: earlier])
|
|
|
|
from(r in ScrobbleRule, where: r.id == ^rule_second.id)
|
|
|> Repo.update_all(set: [inserted_at: now])
|
|
|
|
rules = ScrobbleRules.list_scrobble_rules(order: :inserted_at)
|
|
assert Enum.map(rules, & &1.id) == [rule_second.id, rule_first.id]
|
|
end
|
|
end
|
|
|
|
describe "rule application" do
|
|
test "apply_album_rule/1 updates matching tracks" do
|
|
rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
|
|
# Create matching track
|
|
track1 =
|
|
scrobbled_track_fixture(%{
|
|
album: %{musicbrainz_id: "", title: "Dark Side of the Moon"}
|
|
})
|
|
|
|
# Create non-matching track
|
|
_track2 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
album: %{musicbrainz_id: "", title: "Wish You Were Here"}
|
|
})
|
|
|
|
assert {:ok, 1} = ScrobbleRules.apply_album_rule(rule)
|
|
|
|
updated_track = Repo.get_by(Track, scrobbled_at_uts: track1.scrobbled_at_uts)
|
|
assert updated_track.album.musicbrainz_id == rule.target_musicbrainz_id
|
|
end
|
|
|
|
test "apply_artist_rule/1 updates matching tracks" do
|
|
rule = scrobble_rule_fixture(@valid_artist_attrs)
|
|
|
|
# Create matching track
|
|
track1 =
|
|
scrobbled_track_fixture(%{
|
|
artist: %{musicbrainz_id: "", name: "Pink Floyd"}
|
|
})
|
|
|
|
# Create non-matching track
|
|
_track2 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
artist: %{musicbrainz_id: "", name: "Led Zeppelin"}
|
|
})
|
|
|
|
assert {:ok, 1} = ScrobbleRules.apply_artist_rule(rule)
|
|
|
|
updated_track = Repo.get_by(Track, scrobbled_at_uts: track1.scrobbled_at_uts)
|
|
assert updated_track.artist.musicbrainz_id == rule.target_musicbrainz_id
|
|
end
|
|
|
|
test "apply_rule/1 delegates to correct function based on type" do
|
|
album_rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
artist_rule = scrobble_rule_fixture(@valid_artist_attrs)
|
|
|
|
# Create test tracks that only match their respective rules
|
|
_album_track =
|
|
scrobbled_track_fixture(%{
|
|
album: %{musicbrainz_id: "", title: "Dark Side of the Moon"},
|
|
artist: %{musicbrainz_id: "", name: "Different Artist"}
|
|
})
|
|
|
|
_artist_track =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
artist: %{musicbrainz_id: "", name: "Pink Floyd"},
|
|
album: %{musicbrainz_id: "", title: "Different Album"}
|
|
})
|
|
|
|
assert {:ok, 1} = ScrobbleRules.apply_rule(album_rule)
|
|
assert {:ok, 1} = ScrobbleRules.apply_rule(artist_rule)
|
|
end
|
|
|
|
test "apply_all_rules/0 applies all enabled rules" do
|
|
_album_rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
_artist_rule = scrobble_rule_fixture(@valid_artist_attrs)
|
|
_disabled_rule = scrobble_rule_fixture(%{enabled: false, match_value: "Disabled Album"})
|
|
|
|
# Create test tracks that only match their respective rules
|
|
_album_track =
|
|
scrobbled_track_fixture(%{
|
|
album: %{musicbrainz_id: "", title: "Dark Side of the Moon"},
|
|
artist: %{musicbrainz_id: "", name: "Different Artist"}
|
|
})
|
|
|
|
_artist_track =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
artist: %{musicbrainz_id: "", name: "Pink Floyd"},
|
|
album: %{musicbrainz_id: "", title: "Different Album"}
|
|
})
|
|
|
|
assert results = ScrobbleRules.apply_all_rules()
|
|
assert length(results) == 2
|
|
|
|
# Verify all results are successful
|
|
Enum.each(results, fn result ->
|
|
assert {:ok, {_type, _match_value, _count}} = result
|
|
end)
|
|
end
|
|
|
|
test "count_album_matches/1 returns correct count" do
|
|
rule = scrobble_rule_fixture(@valid_album_attrs)
|
|
|
|
# Create matching tracks
|
|
_track1 =
|
|
scrobbled_track_fixture(%{
|
|
album: %{musicbrainz_id: "", title: "Dark Side of the Moon"}
|
|
})
|
|
|
|
_track2 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
album: %{musicbrainz_id: "", title: "Dark Side of the Moon"}
|
|
})
|
|
|
|
# Create non-matching track
|
|
_track3 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 2,
|
|
album: %{musicbrainz_id: "", title: "Wish You Were Here"}
|
|
})
|
|
|
|
assert ScrobbleRules.count_album_matches(rule) == 2
|
|
end
|
|
|
|
test "count_artist_matches/1 returns correct count" do
|
|
rule = scrobble_rule_fixture(@valid_artist_attrs)
|
|
|
|
# Create matching tracks
|
|
_track1 =
|
|
scrobbled_track_fixture(%{
|
|
artist: %{musicbrainz_id: "", name: "Pink Floyd"}
|
|
})
|
|
|
|
_track2 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
artist: %{musicbrainz_id: "", name: "Pink Floyd"}
|
|
})
|
|
|
|
# Create non-matching track
|
|
_track3 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 2,
|
|
artist: %{musicbrainz_id: "", name: "Led Zeppelin"}
|
|
})
|
|
|
|
assert ScrobbleRules.count_artist_matches(rule) == 2
|
|
end
|
|
|
|
test "apply_all_album_rules/1 applies multiple album rules in one query" do
|
|
# Create two album rules
|
|
rule1 = scrobble_rule_fixture(@valid_album_attrs)
|
|
|
|
rule2 =
|
|
scrobble_rule_fixture(%{
|
|
match_value: "Wish You Were Here",
|
|
target_musicbrainz_id: "abcdef12-3456-7890-abcd-ef1234567890"
|
|
})
|
|
|
|
# Create tracks matching each rule
|
|
track1 =
|
|
scrobbled_track_fixture(%{
|
|
album: %{musicbrainz_id: "", title: "Dark Side of the Moon"}
|
|
})
|
|
|
|
track2 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
album: %{musicbrainz_id: "", title: "Wish You Were Here"}
|
|
})
|
|
|
|
_track3 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 2,
|
|
album: %{musicbrainz_id: "", title: "The Wall"}
|
|
})
|
|
|
|
assert {:ok, 2} = ScrobbleRules.apply_all_album_rules([rule1, rule2])
|
|
|
|
updated_track1 = Repo.get(Track, track1.scrobbled_at_uts)
|
|
assert updated_track1.album.musicbrainz_id == rule1.target_musicbrainz_id
|
|
|
|
updated_track2 = Repo.get(Track, track2.scrobbled_at_uts)
|
|
assert updated_track2.album.musicbrainz_id == rule2.target_musicbrainz_id
|
|
end
|
|
|
|
test "apply_all_artist_rules/1 applies multiple artist rules in one query" do
|
|
# Create two artist rules
|
|
rule1 = scrobble_rule_fixture(@valid_artist_attrs)
|
|
|
|
rule2 =
|
|
scrobble_rule_fixture(%{
|
|
type: :artist,
|
|
match_value: "Led Zeppelin",
|
|
target_musicbrainz_id: "fedcba98-7654-3210-fedc-ba9876543210"
|
|
})
|
|
|
|
# Create tracks matching each rule
|
|
track1 =
|
|
scrobbled_track_fixture(%{
|
|
artist: %{musicbrainz_id: "", name: "Pink Floyd"}
|
|
})
|
|
|
|
track2 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
artist: %{musicbrainz_id: "", name: "Led Zeppelin"}
|
|
})
|
|
|
|
_track3 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 2,
|
|
artist: %{musicbrainz_id: "", name: "The Beatles"}
|
|
})
|
|
|
|
# Apply both rules at once
|
|
assert {:ok, 2} = ScrobbleRules.apply_all_artist_rules([rule1, rule2])
|
|
|
|
# Verify both tracks were updated by fetching them again
|
|
updated_track1 = Repo.get(Track, track1.scrobbled_at_uts)
|
|
assert updated_track1.artist.musicbrainz_id == rule1.target_musicbrainz_id
|
|
|
|
updated_track2 = Repo.get(Track, track2.scrobbled_at_uts)
|
|
assert updated_track2.artist.musicbrainz_id == rule2.target_musicbrainz_id
|
|
end
|
|
|
|
test "apply_all_album_rules/1 with empty list returns 0" do
|
|
assert {:ok, 0} = ScrobbleRules.apply_all_album_rules([])
|
|
end
|
|
|
|
test "apply_all_artist_rules/1 with empty list returns 0" do
|
|
assert {:ok, 0} = ScrobbleRules.apply_all_artist_rules([])
|
|
end
|
|
|
|
test "apply_all_rules/0 batches rules by type" do
|
|
# Create multiple rules of each type
|
|
_album_rule1 = scrobble_rule_fixture(@valid_album_attrs)
|
|
|
|
_album_rule2 =
|
|
scrobble_rule_fixture(%{
|
|
match_value: "Wish You Were Here",
|
|
target_musicbrainz_id: "abcdef12-3456-7890-abcd-ef1234567890"
|
|
})
|
|
|
|
_artist_rule1 = scrobble_rule_fixture(@valid_artist_attrs)
|
|
|
|
_artist_rule2 =
|
|
scrobble_rule_fixture(%{
|
|
type: :artist,
|
|
match_value: "Led Zeppelin",
|
|
target_musicbrainz_id: "fedcba98-7654-3210-fedc-ba9876543210"
|
|
})
|
|
|
|
# Create tracks matching the rules
|
|
_track1 =
|
|
scrobbled_track_fixture(%{
|
|
album: %{musicbrainz_id: "", title: "Dark Side of the Moon"},
|
|
artist: %{musicbrainz_id: "", name: "Pink Floyd"}
|
|
})
|
|
|
|
_track2 =
|
|
scrobbled_track_fixture(%{
|
|
scrobbled_at_uts: System.system_time(:second) + 1,
|
|
album: %{musicbrainz_id: "", title: "Wish You Were Here"},
|
|
artist: %{musicbrainz_id: "", name: "Led Zeppelin"}
|
|
})
|
|
|
|
# Apply all rules
|
|
results = ScrobbleRules.apply_all_rules()
|
|
|
|
# Should have 4 results (one for each rule)
|
|
assert length(results) == 4
|
|
|
|
# All results should be successful
|
|
Enum.each(results, fn result ->
|
|
assert {:ok, {_type, _match_value, count}} = result
|
|
# Count should be > 0 since we have matching tracks
|
|
assert count > 0
|
|
end)
|
|
end
|
|
end
|
|
end
|