ML-169.10.1: Add delete coverage

Cover current delete behavior across LiveView index and show paths, and strengthen existing delete tests with persistence assertions.\n\nValidation: mise run dev:precommit
This commit is contained in:
Claudio Ortolina
2026-05-20 18:04:29 +01:00
parent 981099511f
commit 23960b5b23
12 changed files with 241 additions and 1 deletions
@@ -0,0 +1,99 @@
---
id: ML-169.10.1
title: Add baseline delete coverage for current LiveView paths
status: Done
assignee:
- Codex
created_date: "2026-05-20 16:31"
updated_date: "2026-05-20 16:37"
labels:
- testing
- coverage
dependencies: []
references:
- ML-169.10
documentation:
- docs/architecture.md
- docs/project-conventions.md
modified_files:
- test/music_library/records_test.exs
- test/music_library_web/live/collection_live/index_test.exs
- test/music_library_web/live/wishlist_live/index_test.exs
- test/music_library_web/live/scrobbled_tracks_live/index_test.exs
- test/music_library_web/live/collection_live/show_test.exs
- test/music_library_web/live/wishlist_live/show_test.exs
- test/music_library_web/live/artist_live/show_test.exs
- test/music_library_web/live/record_set_live/index_test.exs
- test/music_library_web/live/record_set_live/show_test.exs
- test/music_library_web/live/online_store_template_live/index_test.exs
- test/music_library_web/live/scrobble_rules_live/index_test.exs
parent_task_id: ML-169.10
priority: medium
ordinal: 34000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add focused test coverage for current deletion behavior without implementing the async optimistic UI changes from ML-169.10. This task covers the three stream-backed index paths called out in ML-169.10 and verifies whether other existing user-facing deletion paths lack tests. Scope is limited to tests for current behavior; production delete handlers should not be changed here unless a test reveals an existing broken path that cannot be tested as-is.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Collection index delete is covered by a LiveView test that removes the record from the page and verifies the record is deleted through the Records context.
- [x] #2 Wishlist index delete is covered by a LiveView test that removes the record from the page and verifies the record is deleted through the Records context.
- [x] #3 Scrobbled tracks index delete is covered by a LiveView test that removes the track from the page and verifies the track is deleted through ListeningStats.
- [x] #4 Existing user-facing delete handlers are audited for missing coverage, and any untested current-behavior paths found in that audit receive focused tests or are documented as intentionally out of scope.
- [x] #5 Existing delete tests that only assert UI removal are strengthened with persistence assertions where practical.
- [x] #6 Focused tests for the touched delete paths pass.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
## Implementation Plan
1. Add baseline LiveView delete tests for the three ML-169.10 stream-backed index paths without changing production code:
- `test/music_library_web/live/collection_live/index_test.exs`
- `test/music_library_web/live/wishlist_live/index_test.exs`
- `test/music_library_web/live/scrobbled_tracks_live/index_test.exs`
2. Audit current user-facing delete handlers for missing deletion tests. Based on the initial scan, add focused current-behavior tests for untested deletion paths in:
- `test/music_library_web/live/collection_live/show_test.exs`
- `test/music_library_web/live/wishlist_live/show_test.exs`
- `test/music_library_web/live/artist_live/show_test.exs`
- `test/music_library_web/live/record_set_live/index_test.exs`
3. Strengthen existing UI-only deletion tests with persistence assertions where practical:
- `test/music_library_web/live/online_store_template_live/index_test.exs`
- `test/music_library_web/live/scrobble_rules_live/index_test.exs`
4. Run focused tests for the touched files, then update the task with modified files, audit notes, and checked acceptance criteria.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Audited current deletion coverage by scanning user-facing delete handlers and existing delete tests. Added baseline coverage for the three ML-169.10 stream-backed index paths: collection index, wishlist index, and scrobbled tracks index. Added current-behavior coverage for untested delete paths found in collection show, wishlist show, artist record list deletion, and record set index deletion. Strengthened existing UI-only delete tests for online store templates, scrobble rules, and record set show deletion with persistence assertions. Also strengthened Records.delete_record/1 to assert the record is actually gone while preserving the PruneArtistInfo enqueue assertion.
Did not add async success/error/exit handle_async tests from ML-169.10 because those paths do not exist yet; adding them would require implementing the async optimistic UI issue, which is intentionally out of scope for this task.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added baseline test coverage for current delete behavior without changing production delete handlers.
Covered the three ML-169.10 stream-backed index paths: collection index record deletion, wishlist index record deletion, and scrobbled tracks deletion. Also covered additional untested current user-facing delete paths found during the audit: collection show, wishlist show, artist record list deletion, and record set index deletion. Strengthened existing delete tests for online store templates, scrobble rules, record set show deletion, and Records.delete_record/1 so they assert persisted deletion through context lookups.
Validation: `mix test --max-failures 5 test/music_library/records_test.exs test/music_library_web/live/collection_live/index_test.exs test/music_library_web/live/wishlist_live/index_test.exs test/music_library_web/live/scrobbled_tracks_live/index_test.exs test/music_library_web/live/collection_live/show_test.exs test/music_library_web/live/wishlist_live/show_test.exs test/music_library_web/live/artist_live/show_test.exs test/music_library_web/live/record_set_live/index_test.exs test/music_library_web/live/record_set_live/show_test.exs test/music_library_web/live/online_store_template_live/index_test.exs test/music_library_web/live/scrobble_rules_live/index_test.exs` passed with 115 tests.
<!-- SECTION:FINAL_SUMMARY:END -->
+6 -1
View File
@@ -69,7 +69,12 @@ defmodule MusicLibrary.RecordsTest do
[artist] = record.artists [artist] = record.artists
Records.delete_record(record) assert {:ok, deleted} = Records.delete_record(record)
assert deleted.id == record.id
assert_raise Ecto.NoResultsError, fn ->
Records.get_record!(record.id)
end
assert_enqueued worker: MusicLibrary.Worker.PruneArtistInfo, assert_enqueued worker: MusicLibrary.Worker.PruneArtistInfo,
args: %{id: artist.musicbrainz_id} args: %{id: artist.musicbrainz_id}
@@ -17,6 +17,7 @@ defmodule MusicLibraryWeb.ArtistLive.ShowTest do
alias MusicLibrary.Artists alias MusicLibrary.Artists
alias MusicLibrary.Assets alias MusicLibrary.Assets
alias MusicLibrary.Assets.Asset alias MusicLibrary.Assets.Asset
alias MusicLibrary.Records
alias Req.Test alias Req.Test
defp fill_collection(_config) do defp fill_collection(_config) do
@@ -164,6 +165,26 @@ defmodule MusicLibraryWeb.ArtistLive.ShowTest do
|> refute_has("#collection p", escape(other_collection_record.title)) |> refute_has("#collection p", escape(other_collection_record.title))
|> refute_has("#wishlist p", escape(other_collection_record.title)) |> refute_has("#wishlist p", escape(other_collection_record.title))
end end
test "deletes a record from the artist record list", %{
conn: conn,
collection_record: collection_record,
artist_musicbrainz_id: artist_musicbrainz_id
} do
stub_last_fm(%{})
conn
|> visit(~p"/artists/#{artist_musicbrainz_id}")
|> render_async()
|> assert_has("#collection p", escape(collection_record.title))
|> click_link("a[data-confirm='Are you sure?']", "Delete")
|> refute_has("#collection p", escape(collection_record.title))
|> assert_has("#toast-group", "Record deleted")
assert_raise Ecto.NoResultsError, fn ->
Records.get_record!(collection_record.id)
end
end
end end
describe "Edit artist image" do describe "Edit artist image" do
@@ -127,6 +127,22 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
end end
end end
describe "Delete record" do
test "deletes a record from the listing", %{conn: conn} do
record = record(%{title: "Delete From Collection Index"})
conn
|> visit(~p"/collection")
|> assert_has("#records-#{record.id}")
|> click_link("#records-#{record.id} a[data-confirm='Are you sure?']", "Delete")
|> refute_has("#records-#{record.id}")
assert_raise Ecto.NoResultsError, fn ->
Records.get_record!(record.id)
end
end
end
describe "Search and pagination" do describe "Search and pagination" do
setup [:fill_collection] setup [:fill_collection]
@@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do
alias MusicBrainz.Fixtures alias MusicBrainz.Fixtures
alias MusicLibrary.Assets.Transform alias MusicLibrary.Assets.Transform
alias MusicLibrary.Records
alias MusicLibrary.Records.Record alias MusicLibrary.Records.Record
alias Phoenix.PubSub alias Phoenix.PubSub
@@ -69,6 +70,26 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do
end end
end end
describe "Delete record" do
test "deletes the record and navigates back to collection", %{conn: conn} do
record = record()
release_response = Fixtures.Release.release(:marbles)
Req.Test.stub(MusicBrainz.API, fn conn ->
Req.Test.json(conn, release_response)
end)
conn
|> visit(~p"/collection/#{record.id}")
|> click_link("a[data-confirm='Are you sure?']", "Delete")
|> assert_path(~p"/collection")
assert_raise Ecto.NoResultsError, fn ->
Records.get_record!(record.id)
end
end
end
describe "handle_info({:update, record}) with live_action guard" do describe "handle_info({:update, record}) with live_action guard" do
test "updates record when showing (live_action is :show)", %{conn: conn} do test "updates record when showing (live_action is :show)", %{conn: conn} do
record = record() record = record()
@@ -3,6 +3,8 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.IndexTest do
import MusicLibrary.Fixtures.OnlineStoreTemplates import MusicLibrary.Fixtures.OnlineStoreTemplates
alias MusicLibrary.OnlineStoreTemplates
describe "Index" do describe "Index" do
test "lists all templates", %{conn: conn} do test "lists all templates", %{conn: conn} do
online_store_template(%{name: "Amazon UK"}) online_store_template(%{name: "Amazon UK"})
@@ -63,6 +65,10 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.IndexTest do
|> visit(~p"/online-store-templates") |> visit(~p"/online-store-templates")
|> click_button("button[phx-click='delete'][phx-value-id='#{template.id}']", "Delete") |> click_button("button[phx-click='delete'][phx-value-id='#{template.id}']", "Delete")
|> refute_has("p", "To Delete") |> refute_has("p", "To Delete")
assert_raise Ecto.NoResultsError, fn ->
OnlineStoreTemplates.get_template!(template.id)
end
end end
end end
@@ -80,4 +80,20 @@ defmodule MusicLibraryWeb.RecordSetLive.IndexTest do
assert updated.name == "Updated Name" assert updated.name == "Updated Name"
end end
end end
describe "Delete set" do
test "deletes a set from the listing", %{conn: conn} do
set = record_set(%{name: "Delete From Index"})
conn
|> visit(~p"/record-sets")
|> assert_has("h2", "Delete From Index")
|> click_button("button[phx-click='delete_set'][phx-value-id='#{set.id}']", "Delete")
|> refute_has("h2", "Delete From Index")
assert_raise Ecto.NoResultsError, fn ->
RecordSets.get_record_set!(set.id)
end
end
end
end end
@@ -97,6 +97,10 @@ defmodule MusicLibraryWeb.RecordSetLive.ShowTest do
|> visit(~p"/record-sets/#{set}") |> visit(~p"/record-sets/#{set}")
|> click_button("button[phx-click='delete_set']", "Delete") |> click_button("button[phx-click='delete_set']", "Delete")
|> assert_path(~p"/record-sets") |> assert_path(~p"/record-sets")
assert_raise Ecto.NoResultsError, fn ->
RecordSets.get_record_set!(set.id)
end
end end
end end
@@ -4,6 +4,8 @@ defmodule MusicLibraryWeb.ScrobbleRulesLiveTest do
import MusicLibrary.ScrobbleRulesFixtures import MusicLibrary.ScrobbleRulesFixtures
import Phoenix.LiveViewTest, only: [render_change: 1, form: 3] import Phoenix.LiveViewTest, only: [render_change: 1, form: 3]
alias MusicLibrary.ScrobbleRules
defp create_scrobble_rule(_) do defp create_scrobble_rule(_) do
scrobble_rule = scrobble_rule_fixture() scrobble_rule = scrobble_rule_fixture()
%{scrobble_rule: scrobble_rule} %{scrobble_rule: scrobble_rule}
@@ -92,6 +94,10 @@ defmodule MusicLibraryWeb.ScrobbleRulesLiveTest do
"Delete" "Delete"
) )
|> refute_has("#scrobble_rule-#{scrobble_rule.id}") |> refute_has("#scrobble_rule-#{scrobble_rule.id}")
assert_raise Ecto.NoResultsError, fn ->
ScrobbleRules.get_scrobble_rule!(scrobble_rule.id)
end
end end
test "toggles rule enabled status", %{conn: conn, scrobble_rule: scrobble_rule} do test "toggles rule enabled status", %{conn: conn, scrobble_rule: scrobble_rule} do
@@ -86,6 +86,22 @@ defmodule MusicLibraryWeb.ScrobbledTracksLiveTest do
end end
end end
describe "Delete track" do
setup [:create_track]
test "deletes a scrobbled track from the listing", %{conn: conn, track: track} do
conn
|> visit(~p"/scrobbled-tracks")
|> assert_has("p", track.title)
|> click_button("button[data-confirm='Are you sure?']", "Delete")
|> refute_has("p", track.title)
assert_raise Ecto.NoResultsError, fn ->
ListeningStats.get_track!(track.scrobbled_at_uts)
end
end
end
describe "Pagination" do describe "Pagination" do
test "navigates to next page", %{conn: conn} do test "navigates to next page", %{conn: conn} do
create_test_tracks(21) create_test_tracks(21)
@@ -45,6 +45,20 @@ defmodule MusicLibraryWeb.WishlistLive.IndexTest do
describe "Wishlist" do describe "Wishlist" do
setup [:fill_wishlist] setup [:fill_wishlist]
test "deletes a record from the listing", %{conn: conn} do
record = record(%{title: "Delete From Wishlist Index", purchased_at: nil})
conn
|> visit(~p"/wishlist")
|> assert_has("#records-#{record.id}")
|> click_link("#records-#{record.id} a[data-confirm='Are you sure?']", "Delete")
|> refute_has("#records-#{record.id}")
assert_raise Ecto.NoResultsError, fn ->
Records.get_record!(record.id)
end
end
test "can purchase a record and move it to the collection", %{ test "can purchase a record and move it to the collection", %{
conn: conn, conn: conn,
wishlist: wishlist_records wishlist: wishlist_records
@@ -5,6 +5,7 @@ defmodule MusicLibraryWeb.WishlistLive.ShowTest do
import MusicLibraryWeb.RecordComponents, only: [format_label: 1, type_label: 1] import MusicLibraryWeb.RecordComponents, only: [format_label: 1, type_label: 1]
alias MusicLibrary.Assets.Transform alias MusicLibrary.Assets.Transform
alias MusicLibrary.Records
alias MusicLibrary.Records.Record alias MusicLibrary.Records.Record
alias Phoenix.PubSub alias Phoenix.PubSub
@@ -53,6 +54,21 @@ defmodule MusicLibraryWeb.WishlistLive.ShowTest do
end end
end end
describe "Delete record" do
test "deletes the record and navigates back to wishlist", %{conn: conn} do
record = record(purchased_at: nil)
conn
|> visit(~p"/wishlist/#{record.id}")
|> click_link("a[data-confirm='Are you sure?']", "Delete")
|> assert_path(~p"/wishlist")
assert_raise Ecto.NoResultsError, fn ->
Records.get_record!(record.id)
end
end
end
describe "handle_info({:update, record}) with live_action guard" do describe "handle_info({:update, record}) with live_action guard" do
test "updates record when showing (live_action is :show)", %{conn: conn} do test "updates record when showing (live_action is :show)", %{conn: conn} do
record = record(purchased_at: nil) record = record(purchased_at: nil)