From 07f43e59bb4f2ec57b660591b53086a487616230 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Wed, 20 May 2026 22:41:34 +0100 Subject: [PATCH] ML-192: plan refinement --- ...Add-high-value-behavioral-test-coverage.md | 142 +++++++++++++++++- 1 file changed, 134 insertions(+), 8 deletions(-) diff --git a/backlog/tasks/ml-192 - Add-high-value-behavioral-test-coverage.md b/backlog/tasks/ml-192 - Add-high-value-behavioral-test-coverage.md index 04031d7c..f364e6f6 100644 --- a/backlog/tasks/ml-192 - Add-high-value-behavioral-test-coverage.md +++ b/backlog/tasks/ml-192 - Add-high-value-behavioral-test-coverage.md @@ -4,7 +4,7 @@ title: Add high-value behavioral test coverage status: To Do assignee: [] created_date: "2026-05-20 17:19" -updated_date: "2026-05-20 17:20" +updated_date: "2026-05-20 21:54" labels: - testing - coverage @@ -43,11 +43,137 @@ Increase coverage where current tests would catch meaningful regressions in user -1. Read docs/architecture.md, docs/project-conventions.md, .agents/skills/testing/SKILL.md, and relevant UI/SQLite guidance before changing tests. -2. Add focused PhoenixTest/LiveView tests around existing record edit flows for genre events and cover search/download, stubbing BraveSearch.API and asserting persisted record state through MusicLibrary.Records. -3. Add BarcodeScanner tests through the collection scan LiveView or component harness, using Req.Test/Oban assertions to verify scan failure, remove/clear behavior, and async import enqueues. -4. Add Notes component coverage through record or artist show pages, asserting persisted notes through MusicLibrary.Notes rather than UI text alone. -5. Add ScrobbleRules subset tests with explicit scrobbled_at_uts values so only supplied tracks are updated. -6. Add Assets.Image tests for convert/3 and invalid data using existing image fixtures or fallback data. -7. Run targeted test files first, then run the relevant project test command if scope/time permits. +1. Read `docs/architecture.md`, `docs/project-conventions.md`, `.agents/skills/testing/SKILL.md`, and the relevant UI/SQLite guidance before changing tests. + +2. **Add `RecordForm` genre behavioral coverage** through existing record edit paths in `test/music_library_web/live/collection_live/show_test.exs` and/or `test/music_library_web/live/wishlist_live/show_test.exs`. Prefer PhoenixTest, using `unwrap/2` + LiveViewTest only for non-standard click targets (Fluxon custom selects, `
  • ` elements with `phx-click`). The record edit form is reached via `click_link("Edit")` from either show page and renders the `RecordForm` LiveComponent. Cover genre search suggestions (type into `#genre-input`, assert `#genre-suggestions` list items appear), creating a normalized new genre (click "Create" suggestion, assert badge appears), duplicate/blank genre no-ops (attempt adding same genre twice, assert no duplicate badge; attempt adding empty string, assert badges unchanged), and removing an existing genre (click badge's remove icon, assert badge disappears). Assert both visible form state and persisted record state through `MusicLibrary.Records.get_record!/1` after form save. + +3. **Add `RecordForm` cover-search/download coverage** through the same edit path. The `RecordForm` uses `start_async` for both BraveSearch image search and image download, which runs in a separate Task process. `Req.Test.stub/2` stores stubs per-process by default and will not be visible inside the Task. **Use `mode: :global`** on all BraveSearch stubs to make them available across process boundaries: + + ```elixir + # Successful search stub + Req.Test.stub(BraveSearch.API, fn conn -> + Req.Test.json(conn, %{ + "results" => [ + %{ + "thumbnail" => %{"src" => "https://example.com/thumb1.jpg"}, + "properties" => %{ + "url" => "https://example.com/full1.jpg", + "width" => 800, "height" => 600 + }, + "title" => "Test Cover 1", "source" => "example.com" + }, + %{ + "thumbnail" => %{"src" => "https://example.com/thumb2.jpg"}, + "properties" => %{ + "url" => "https://example.com/full2.jpg", + "width" => 1024, "height" => 1024 + }, + "title" => "Test Cover 2", "source" => "example.com" + } + ] + }) + end, mode: :global) + + # Search failure stub + Req.Test.stub(BraveSearch.API, fn conn -> + conn + |> Plug.Conn.put_status(429) + |> Req.Test.json(conn, %{"error" => "Rate limit exceeded"}) + end, mode: :global) + + # Image download stub + Req.Test.stub(BraveSearch.API, fn conn -> + Plug.Conn.send_resp(conn, 200, Image.fallback_data()) + end, mode: :global) + ``` + + **Important:** Register `on_exit` cleanup to remove global stubs after each test to maintain test isolation: + + ```elixir + on_exit(fn -> + Req.Test.stub(BraveSearch.API, nil, mode: :global) + end) + ``` + + Test flow: navigate to edit form → type query into cover search input → click "Search" button → call `render_async()` to wait for the async task → assert cover search result thumbnails render in `#cover-search-results` → click a result thumbnail → call `render_async()` again to wait for download async → assert success toast and verify `record.cover_hash` changed via `MusicLibrary.Records.get_record!/1`. For search failure: stub an error response, click search, call `render_async()`, and assert the friendly error text appears in the `p.text-red-600` selector containing "Search failed". For selecting a search result: stub both search and download, verify the downloaded cover hash is persisted on the record and an asset exists via `MusicLibrary.Assets.get(hash)`. + +4. **Add `BarcodeScanner` coverage** in `test/music_library_web/live/collection_live/index_test.exs` under the existing `"Add via barcode scan"` describe block (or a new describe block). Use `trigger_hook/3` to simulate scan events, following the existing pattern in that file: + - **Scan failure toast**: Stub `MusicBrainz.API` to return a `Req.Test.transport_error(conn, :timeout)`, trigger `trigger_hook("#barcode-scanner", "barcode_scanned", %{"number" => "123"})`, assert error toast appears (matching `gettext("Failed to search release for barcode %{number}", number: "123")`). + - **Remove one scanned result**: Stub a successful scan, trigger hook, assert cart item appears. Trigger `render_hook` with `"remove_result"` event and `%{"number" => "123"}`, assert that item is removed while other items remain. + - **Clear all scanned results**: Add multiple scan results, trigger `render_hook` with `"clear_results"`, assert all cart items are gone. + - **2+ new-release async import branch**: Add 2+ new scan results, click `"Add N releases"` button. Assert `assert_enqueued(worker: MusicLibrary.Worker.ImportFromMusicbrainzRelease, args: %{"release_id" => _, "format" => _, "purchased_at" => _, "selected_release_id" => _})` with one job per new scan result. Assert no `Records.Record` rows were synchronously inserted via `MusicLibrary.Repo.all(Record)`. + + For the successful scan stub, follow the existing pattern in the `"Add via barcode scan"` test that stubs `MusicBrainz.API` for both the barcode search and the release/release-group endpoints. The plan only needs the barcode search path for scan-result display; full import requires release/release-group/cover stubs as well. + +5. **Add `Notes` component coverage** through the `test/music_library_web/live/collection_live/show_test.exs` record show page. The Notes component is rendered as a Fluxon `<.sheet>` dialog opened via `phx-click={MusicLibraryWeb.Components.Notes.open("record-notes-sheet")}` from a standard `