ML-177: implementation

This commit is contained in:
Claudio Ortolina
2026-05-10 20:38:45 +01:00
parent e322f75079
commit ba62d54cc5
5 changed files with 277 additions and 17 deletions
@@ -3,6 +3,10 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
import MusicLibrary.Fixtures.Records
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
alias MusicLibrary.Secrets
alias Req.Test
defp create_record(_) do
%{record: record_with_artist("Steven Wilson", %{release_date: "2025-06-21"})}
end
@@ -14,14 +18,21 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
describe "authentication" do
test "all API endpoints require a bearer token", %{conn: conn} do
for path <- [
~p"/api/v1/collection/latest",
~p"/api/v1/collection/random",
~p"/api/v1/collection",
~p"/api/v1/collection/on_this_day"
for {method, path} <- [
{:get, ~p"/api/v1/collection/latest"},
{:get, ~p"/api/v1/collection/random"},
{:get, ~p"/api/v1/collection"},
{:get, ~p"/api/v1/collection/on_this_day"},
{:post, ~p"/api/v1/collection/nonexistent/scrobble"}
] do
assert get(conn, path).status == 401,
"expected 401 for unauthenticated GET #{path}"
conn =
case method do
:get -> get(conn, path)
:post -> post(conn, path)
end
assert conn.status == 401,
"expected 401 for unauthenticated #{String.upcase(to_string(method))} #{path}"
end
end
end
@@ -161,6 +172,143 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
end
end
describe "POST /api/v1/collection/:record_id/scrobble" do
defp stub_musicbrainz_release(_) do
Test.stub(MusicBrainz.API, fn conn ->
case conn.request_path do
"/ws/2/release/" <> _id ->
Test.json(conn, ReleaseFixtures.release_with_media(:marbles))
_ ->
Test.json(conn, %{})
end
end)
:ok
end
defp stub_lastfm_success(_) do
Test.stub(LastFm.API, fn conn ->
Test.json(conn, %{"scrobbles" => %{"@attr" => %{"accepted" => 1}}})
end)
:ok
end
defp stub_lastfm_error(_) do
Test.stub(LastFm.API, fn conn ->
Test.json(conn, %{
"error" => 16,
"message" => "The service is temporarily unavailable"
})
end)
:ok
end
defp stub_musicbrainz_error(_) do
Test.stub(MusicBrainz.API, fn conn ->
Plug.Conn.send_resp(conn, 503, "service unavailable")
end)
:ok
end
defp store_session_key(_) do
Secrets.store("last_fm_session_key", "test_session_key")
on_exit(fn ->
Secrets.store("last_fm_session_key", nil)
end)
:ok
end
defp auth_header(conn) do
put_req_header(conn, "authorization", "Bearer #{api_token()}")
end
setup [:create_record]
test "returns 401 without bearer token", %{conn: conn, record: record} do
conn = post(conn, ~p"/api/v1/collection/#{record.id}/scrobble")
assert conn.status == 401
end
test "returns 404 for non-existent record", %{conn: conn} do
conn =
conn
|> auth_header()
|> post(~p"/api/v1/collection/00000000-0000-0000-0000-000000000000/scrobble")
assert json_response(conn, 404) == %{
"status" => "error",
"reason" => "not_found"
}
end
test "returns 422 when record has no selected_release_id", %{conn: conn} do
record = record_with_artist("No Release Artist", %{selected_release_id: nil})
conn =
conn
|> auth_header()
|> post(~p"/api/v1/collection/#{record.id}/scrobble")
assert json_response(conn, 422) == %{
"status" => "error",
"reason" => "no_selected_release"
}
end
@tag scrobble_success: true
test "returns 200 when scrobble succeeds", %{conn: conn, record: record} do
stub_musicbrainz_release(nil)
stub_lastfm_success(nil)
store_session_key(nil)
conn =
conn
|> auth_header()
|> post(~p"/api/v1/collection/#{record.id}/scrobble")
assert json_response(conn, 200) == %{"status" => "ok"}
end
@tag :capture_log
test "returns 502 when MusicBrainz API fails", %{conn: conn, record: record} do
stub_musicbrainz_error(nil)
conn =
conn
|> auth_header()
|> post(~p"/api/v1/collection/#{record.id}/scrobble")
assert json_response(conn, 502) == %{
"status" => "error",
"reason" => "musicbrainz_error"
}
end
@tag :capture_log
test "returns 502 when Last.fm API fails", %{conn: conn, record: record} do
stub_musicbrainz_release(nil)
stub_lastfm_error(nil)
store_session_key(nil)
conn =
conn
|> auth_header()
|> post(~p"/api/v1/collection/#{record.id}/scrobble")
assert json_response(conn, 502) == %{
"status" => "error",
"reason" => "lastfm_error"
}
end
end
defp expected_record_json(record) do
%{
"id" => record.id,
@@ -169,6 +317,7 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
"musicbrainz_id" => record.musicbrainz_id,
"genres" => record.genres,
"release_date" => record.release_date,
"selected_release_id" => record.selected_release_id,
"purchased_at" => record.purchased_at |> DateTime.to_iso8601(),
"artists" => Enum.map(record.artists, & &1.name),
"title" => record.title,