Add test coverage for ScrobbleActivity

Closes #127
This commit is contained in:
Claudio Ortolina
2026-03-23 07:42:39 +00:00
parent ea2cc9ebc6
commit 0f9a2d28cb
@@ -0,0 +1,290 @@
defmodule MusicLibrary.ScrobbleActivityTest do
@moduledoc false
use MusicLibrary.DataCase
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
alias MusicBrainz.Release
alias MusicBrainz.Release.{Artist, Medium, Track}
alias MusicLibrary.ScrobbleActivity
alias MusicLibrary.Secrets
@release ReleaseFixtures.release_with_media(:marbles) |> Release.from_api_response()
defp store_session_key(_) do
Secrets.store("last_fm_session_key", "test_session_key")
:ok
end
defp stub_lastfm_success(_) do
Req.Test.stub(LastFm.API, fn conn ->
Req.Test.json(conn, %{"scrobbles" => %{"@attr" => %{"accepted" => 1}}})
end)
:ok
end
describe "can_scrobble?/0" do
test "returns false when no session key is stored" do
refute ScrobbleActivity.can_scrobble?()
end
test "returns true when session key exists" do
Secrets.store("last_fm_session_key", "test_session_key")
assert ScrobbleActivity.can_scrobble?()
end
end
describe "scrobble_release/3" do
setup [:store_session_key, :stub_lastfm_success]
test "scrobbles all tracks with :started_at" do
started_at = DateTime.utc_now()
assert {:ok, _} = ScrobbleActivity.scrobble_release(@release, :started_at, started_at)
end
test "scrobbles all tracks with :finished_at" do
finished_at = DateTime.utc_now()
assert {:ok, _} = ScrobbleActivity.scrobble_release(@release, :finished_at, finished_at)
end
test "returns error when release has zero duration" do
zero_duration_release = %{@release | media: [%{tracks: [%{length: 0}]}]}
started_at = DateTime.utc_now()
assert {:error, :no_duration} =
ScrobbleActivity.scrobble_release(zero_duration_release, :started_at, started_at)
end
end
describe "scrobble_release/3 without session key" do
setup [:stub_lastfm_success]
test "returns error" do
started_at = DateTime.utc_now()
assert {:error, :no_session_key} =
ScrobbleActivity.scrobble_release(@release, :started_at, started_at)
end
end
describe "scrobble_medium/4" do
setup [:store_session_key, :stub_lastfm_success]
test "scrobbles tracks from a specific medium with :started_at" do
started_at = DateTime.utc_now()
assert {:ok, _} = ScrobbleActivity.scrobble_medium(1, @release, :started_at, started_at)
end
test "scrobbles tracks from a specific medium with :finished_at" do
finished_at = DateTime.utc_now()
assert {:ok, _} = ScrobbleActivity.scrobble_medium(1, @release, :finished_at, finished_at)
end
test "returns error when medium not found" do
started_at = DateTime.utc_now()
assert {:error, :medium_not_found} =
ScrobbleActivity.scrobble_medium(99, @release, :started_at, started_at)
end
test "returns error when medium not found with :finished_at" do
finished_at = DateTime.utc_now()
assert {:error, :medium_not_found} =
ScrobbleActivity.scrobble_medium(99, @release, :finished_at, finished_at)
end
test "returns error when medium has zero duration" do
zero_duration_medium = %Medium{
title: "",
format: "CD",
number: 1,
track_count: 1,
tracks: [
%Track{id: "t1", title: "Silent", length: 0, artists: [], number: "1", position: 1}
]
}
release = %{@release | media: [zero_duration_medium]}
started_at = DateTime.utc_now()
assert {:error, :no_duration} =
ScrobbleActivity.scrobble_medium(1, release, :started_at, started_at)
end
end
describe "scrobble_medium/4 without session key" do
setup [:stub_lastfm_success]
test "returns error" do
started_at = DateTime.utc_now()
assert {:error, :no_session_key} =
ScrobbleActivity.scrobble_medium(1, @release, :started_at, started_at)
end
end
describe "scrobble_tracks/4" do
setup [:store_session_key, :stub_lastfm_success]
setup do
track_ids =
@release
|> Release.tracks()
|> Enum.take(2)
|> Enum.map(& &1.id)
|> MapSet.new()
%{track_ids: track_ids}
end
test "scrobbles selected tracks with :started_at", %{track_ids: track_ids} do
started_at = DateTime.utc_now()
assert {:ok, _} =
ScrobbleActivity.scrobble_tracks(track_ids, @release, :started_at, started_at)
end
test "scrobbles selected tracks with :finished_at", %{track_ids: track_ids} do
finished_at = DateTime.utc_now()
assert {:ok, _} =
ScrobbleActivity.scrobble_tracks(track_ids, @release, :finished_at, finished_at)
end
test "returns error when no tracks selected" do
started_at = DateTime.utc_now()
assert {:error, :no_duration} =
ScrobbleActivity.scrobble_tracks(MapSet.new(), @release, :started_at, started_at)
end
test "returns error when selected tracks have zero length" do
zero_track = %Track{
id: "zero",
title: "Silent",
length: 0,
artists: [],
number: "1",
position: 1
}
release = %{
@release
| media: [
%Medium{title: "", format: "CD", number: 1, track_count: 1, tracks: [zero_track]}
]
}
started_at = DateTime.utc_now()
assert {:error, :no_duration} =
ScrobbleActivity.scrobble_tracks(
MapSet.new(["zero"]),
release,
:started_at,
started_at
)
end
end
describe "scrobble_tracks/4 without session key" do
setup [:stub_lastfm_success]
test "returns error" do
track_ids =
@release
|> Release.tracks()
|> Enum.take(1)
|> Enum.map(& &1.id)
|> MapSet.new()
started_at = DateTime.utc_now()
assert {:error, :no_session_key} =
ScrobbleActivity.scrobble_tracks(track_ids, @release, :started_at, started_at)
end
end
describe "scrobble construction" do
setup [:store_session_key]
test "populates correct fields and increments timestamps" do
started_at = ~U[2024-01-01 12:00:00Z]
Req.Test.stub(LastFm.API, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
params = URI.decode_query(body)
# First track should have timestamp = started_at + track length
first_track = Enum.at(Release.tracks(@release), 0)
expected_timestamp = DateTime.add(started_at, first_track.length, :millisecond)
assert params["track[0]"] == first_track.title
assert params["artist[0]"] == first_track.artists |> List.first() |> Map.get(:name)
assert params["album[0]"] == @release.title
assert params["timestamp[0]"] == Integer.to_string(DateTime.to_unix(expected_timestamp))
Req.Test.json(conn, %{"scrobbles" => %{"@attr" => %{"accepted" => 1}}})
end)
assert {:ok, _} = ScrobbleActivity.scrobble_release(@release, :started_at, started_at)
end
test "sets album_artist when track artist differs from release artist" do
different_artist = %Artist{id: "a2", name: "Guest Artist", sort_name: "Guest"}
track = %Track{
id: "t1",
title: "Collab",
length: 60_000,
artists: [different_artist],
number: "1",
position: 1
}
medium = %Medium{title: "", format: "CD", number: 1, track_count: 1, tracks: [track]}
release = %{@release | media: [medium]}
Req.Test.stub(LastFm.API, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
params = URI.decode_query(body)
release_artist = @release.artists |> List.first() |> Map.get(:name)
assert params["album_artist[0]"] == release_artist
Req.Test.json(conn, %{"scrobbles" => %{"@attr" => %{"accepted" => 1}}})
end)
assert {:ok, _} =
ScrobbleActivity.scrobble_release(release, :started_at, ~U[2024-01-01 12:00:00Z])
end
test "does not set album_artist when track artist matches release artist" do
track_ids =
@release
|> Release.tracks()
|> Enum.take(1)
|> Enum.map(& &1.id)
|> MapSet.new()
Req.Test.stub(LastFm.API, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
params = URI.decode_query(body)
refute Map.has_key?(params, "album_artist[0]")
Req.Test.json(conn, %{"scrobbles" => %{"@attr" => %{"accepted" => 1}}})
end)
assert {:ok, _} =
ScrobbleActivity.scrobble_tracks(
track_ids,
@release,
:started_at,
~U[2024-01-01 12:00:00Z]
)
end
end
end