Improve test coverage

Files Created (8 files, 54 new tests)

  Fixtures

  1. test/support/fixtures/music_library/record_sets.ex — record_set/1
and record_set_with_records/2 helpers
  2. test/support/fixtures/music_library/online_store_templates.ex —
online_store_template/1 helper

  Context Tests

  3. test/music_library/record_sets_test.exs — 20 tests covering search,
count, CRUD, add/remove/move records
  4. test/music_library/online_store_templates_test.exs — 8 tests
covering list, CRUD, URL generation, validation
  5. test/music_library/search_test.exs — 5 tests covering universal
search and search counts
  6. test/music_library/notes_test.exs — 4 tests covering get, create,
update

  LiveView Tests

  7. test/music_library_web/live/record_set_live/index_test.exs — 11
tests covering listing, empty state, search, ordering,
create/edit/delete sets, remove records, reorder records
  8.
test/music_library_web/live/online_store_template_live/index_test.exs —
6 tests covering listing, create/edit/delete, toggle enabled

  Results

  - 297 tests, 0 failures (up from 243 — 54 new tests added)
  - All existing tests continue to pass

There's a remaining gettext warning that needs to be investigated
This commit is contained in:
Claudio Ortolina
2026-02-07 10:39:22 +00:00
parent abb1ab332a
commit 84159a1c0d
8 changed files with 763 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
defmodule MusicLibrary.NotesTest do
use MusicLibrary.DataCase
alias MusicLibrary.Notes
alias MusicLibrary.Notes.Note
@musicbrainz_id Ecto.UUID.generate()
describe "get_note/2" do
test "returns nil when no note exists" do
assert Notes.get_note(:record, Ecto.UUID.generate()) == nil
end
test "returns note for record" do
{:ok, note} =
Notes.create_note(%Note{}, %{
entity: :record,
musicbrainz_id: @musicbrainz_id,
content: "Great album"
})
found = Notes.get_note(:record, @musicbrainz_id)
assert found.id == note.id
assert found.content == "Great album"
end
end
describe "create_note/2" do
test "creates a note" do
assert {:ok, note} =
Notes.create_note(%Note{}, %{
entity: :artist,
musicbrainz_id: @musicbrainz_id,
content: "Fantastic artist"
})
assert note.entity == :artist
assert note.content == "Fantastic artist"
assert note.musicbrainz_id == @musicbrainz_id
end
end
describe "update_note/2" do
test "updates note content" do
{:ok, note} =
Notes.create_note(%Note{}, %{
entity: :record,
musicbrainz_id: @musicbrainz_id,
content: "Original"
})
assert {:ok, updated} = Notes.update_note(note, %{content: "Updated content"})
assert updated.content == "Updated content"
end
end
end
@@ -0,0 +1,95 @@
defmodule MusicLibrary.OnlineStoreTemplatesTest do
use MusicLibrary.DataCase
import MusicLibrary.Fixtures.OnlineStoreTemplates
import MusicLibrary.Fixtures.Records
alias MusicLibrary.OnlineStoreTemplates
describe "list_templates/0 and list_enabled_templates/0" do
test "lists all templates" do
t1 = online_store_template(%{name: "Amazon", enabled: true})
t2 = online_store_template(%{name: "Bandcamp", enabled: false})
all = OnlineStoreTemplates.list_templates()
ids = Enum.map(all, & &1.id)
assert t1.id in ids
assert t2.id in ids
end
test "lists only enabled templates" do
t1 = online_store_template(%{name: "Amazon", enabled: true})
t2 = online_store_template(%{name: "Bandcamp", enabled: false})
enabled = OnlineStoreTemplates.list_enabled_templates()
ids = Enum.map(enabled, & &1.id)
assert t1.id in ids
refute t2.id in ids
end
end
describe "create_template/1" do
test "creates with valid attrs" do
assert {:ok, template} =
OnlineStoreTemplates.create_template(%{
name: "Test Store",
url_template: "https://example.com/search?q={artist}"
})
assert template.name == "Test Store"
assert template.enabled == true
end
test "returns error with invalid attrs" do
assert {:error, changeset} = OnlineStoreTemplates.create_template(%{name: nil})
assert %{name: ["can't be blank"]} = errors_on(changeset)
end
test "returns error with invalid URL template" do
assert {:error, changeset} =
OnlineStoreTemplates.create_template(%{
name: "Bad",
url_template: "not-a-url"
})
assert %{url_template: ["must be a valid HTTP or HTTPS URL"]} = errors_on(changeset)
end
end
describe "update_template/2" do
test "updates with valid attrs" do
template = online_store_template()
assert {:ok, updated} =
OnlineStoreTemplates.update_template(template, %{name: "Updated Name"})
assert updated.name == "Updated Name"
end
end
describe "delete_template/1" do
test "deletes the template" do
template = online_store_template()
assert {:ok, _} = OnlineStoreTemplates.delete_template(template)
assert_raise Ecto.NoResultsError, fn -> OnlineStoreTemplates.get_template!(template.id) end
end
end
describe "generate_url/2" do
test "generates URL from template and record" do
template =
online_store_template(%{
url_template: "https://example.com/search?q={artist}+{title}+{format}"
})
rec = record_with_artist("Pink Floyd", %{title: "The Wall", format: :vinyl})
url = OnlineStoreTemplates.generate_url(template, rec)
assert url =~ "Pink+Floyd"
assert url =~ "The+Wall"
assert url =~ "vinyl"
assert String.starts_with?(url, "https://example.com/search?q=")
end
end
end
+210
View File
@@ -0,0 +1,210 @@
defmodule MusicLibrary.RecordSetsTest do
use MusicLibrary.DataCase
import MusicLibrary.Fixtures.Records
import MusicLibrary.Fixtures.RecordSets
alias MusicLibrary.RecordSets
describe "search_record_sets/2" do
test "returns sets matching by name" do
set = record_set(%{name: "Road Trip Mix"})
_other = record_set(%{name: "Unrelated"})
results = RecordSets.search_record_sets("Road Trip")
assert Enum.any?(results, &(&1.id == set.id))
refute Enum.any?(results, &(&1.name == "Unrelated"))
end
test "returns sets matching by description" do
set = record_set(%{name: "My Set", description: "Great progressive rock"})
_other = record_set(%{name: "Other", description: "Nothing here"})
results = RecordSets.search_record_sets("progressive")
assert Enum.any?(results, &(&1.id == set.id))
end
test "returns sets matching by record title" do
{set, [rec | _]} = record_set_with_records(1, %{name: "Favorites"})
results = RecordSets.search_record_sets(rec.title)
assert Enum.any?(results, &(&1.id == set.id))
end
test "returns sets matching by artist name" do
rec = record_with_artist("Marillion")
set = record_set(%{name: "Prog"})
{:ok, _} = RecordSets.add_record_to_set(set, rec.id)
results = RecordSets.search_record_sets("Marillion")
assert Enum.any?(results, &(&1.id == set.id))
end
test "returns all sets when query is empty" do
set1 = record_set(%{name: "First"})
set2 = record_set(%{name: "Second"})
results = RecordSets.search_record_sets("")
ids = Enum.map(results, & &1.id)
assert set1.id in ids
assert set2.id in ids
end
test "respects offset and limit" do
for i <- 1..5, do: record_set(%{name: "Set #{i}"})
results = RecordSets.search_record_sets("", limit: 2)
assert length(results) == 2
all = RecordSets.search_record_sets("")
offset_results = RecordSets.search_record_sets("", offset: 2, limit: 2)
refute Enum.at(all, 0).id == Enum.at(offset_results, 0).id
end
test "orders by updated_at desc by default" do
s1 = record_set(%{name: "Alpha"})
s2 = record_set(%{name: "Beta"})
# Manually set timestamps to ensure deterministic ordering
past = ~U[2024-01-01 00:00:00Z]
future = ~U[2025-01-01 00:00:00Z]
Repo.update_all(from(rs in "record_sets", where: rs.id == ^s1.id), set: [updated_at: past])
Repo.update_all(from(rs in "record_sets", where: rs.id == ^s2.id),
set: [updated_at: future]
)
results = RecordSets.search_record_sets("")
ids = Enum.map(results, & &1.id)
# s2 has newer updated_at so it should be first
assert Enum.find_index(ids, &(&1 == s2.id)) < Enum.find_index(ids, &(&1 == s1.id))
end
test "orders alphabetically when order: :alphabetical" do
record_set(%{name: "Zulu"})
record_set(%{name: "Alpha"})
results = RecordSets.search_record_sets("", order: :alphabetical)
names = Enum.map(results, & &1.name)
assert Enum.find_index(names, &(&1 == "Alpha")) < Enum.find_index(names, &(&1 == "Zulu"))
end
end
describe "count_record_sets/0 and count_record_sets/1" do
test "returns total count" do
assert RecordSets.count_record_sets() == 0
record_set(%{name: "One"})
record_set(%{name: "Two"})
assert RecordSets.count_record_sets() == 2
end
test "returns count matching query" do
record_set(%{name: "Road Trip"})
record_set(%{name: "Sunday Morning"})
assert RecordSets.count_record_sets("Road") == 1
assert RecordSets.count_record_sets("") == 2
end
end
describe "create_record_set/1" do
test "creates with valid attrs" do
assert {:ok, set} = RecordSets.create_record_set(%{name: "My Set"})
assert set.name == "My Set"
assert set.items == []
end
test "returns error changeset with invalid attrs" do
assert {:error, changeset} = RecordSets.create_record_set(%{name: nil})
assert %{name: ["can't be blank"]} = errors_on(changeset)
end
end
describe "update_record_set/2" do
test "updates with valid attrs" do
set = record_set(%{name: "Old Name"})
assert {:ok, updated} = RecordSets.update_record_set(set, %{name: "New Name"})
assert updated.name == "New Name"
end
end
describe "delete_record_set/1" do
test "deletes the record set" do
set = record_set()
assert {:ok, _} = RecordSets.delete_record_set(set)
assert_raise Ecto.NoResultsError, fn -> RecordSets.get_record_set!(set.id) end
end
end
describe "add_record_to_set/2" do
test "adds a record to the set" do
set = record_set()
rec = record()
assert {:ok, updated} = RecordSets.add_record_to_set(set, rec.id)
assert length(updated.items) == 1
assert hd(updated.items).record.id == rec.id
end
test "returns error on duplicate" do
set = record_set()
rec = record()
{:ok, _} = RecordSets.add_record_to_set(set, rec.id)
assert {:error, _changeset} = RecordSets.add_record_to_set(set, rec.id)
end
end
describe "remove_record_from_set/2" do
test "removes a record and recompacts positions" do
{set, [r1, r2, r3]} = record_set_with_records(3)
{:ok, updated} = RecordSets.remove_record_from_set(set, r2.id)
record_ids = Enum.map(updated.items, & &1.record.id)
assert r1.id in record_ids
assert r3.id in record_ids
refute r2.id in record_ids
positions = Enum.map(updated.items, & &1.position)
assert positions == [0, 1]
end
end
describe "move_record_in_set/3" do
test "moves a record up" do
{set, [r1, r2 | _]} = record_set_with_records(3)
{:ok, updated} = RecordSets.move_record_in_set(set, r2.id, :up)
ids_in_order = Enum.map(updated.items, & &1.record.id)
assert Enum.at(ids_in_order, 0) == r2.id
assert Enum.at(ids_in_order, 1) == r1.id
end
test "moves a record down" do
{set, [r1, r2 | _]} = record_set_with_records(3)
{:ok, updated} = RecordSets.move_record_in_set(set, r1.id, :down)
ids_in_order = Enum.map(updated.items, & &1.record.id)
assert Enum.at(ids_in_order, 0) == r2.id
assert Enum.at(ids_in_order, 1) == r1.id
end
test "no-ops at boundaries" do
{set, [r1, _, r3]} = record_set_with_records(3)
{:ok, moved_up} = RecordSets.move_record_in_set(set, r1.id, :up)
assert Enum.map(moved_up.items, & &1.record.id) == Enum.map(set.items, & &1.record.id)
{:ok, moved_down} = RecordSets.move_record_in_set(set, r3.id, :down)
assert Enum.map(moved_down.items, & &1.record.id) == Enum.map(set.items, & &1.record.id)
end
end
end
+77
View File
@@ -0,0 +1,77 @@
defmodule MusicLibrary.SearchTest do
use MusicLibrary.DataCase
import MusicLibrary.Fixtures.Records
alias MusicLibrary.Search
defp create_records(_) do
collected =
record_with_artist("Marillion", %{title: "Brave", purchased_at: DateTime.utc_now()})
wishlisted =
record_with_artist("Marillion", %{title: "Afraid of Sunlight", purchased_at: nil})
# Create artist info so search_artists can find artists
[artist] = collected.artists
artist_info(artist.musicbrainz_id)
%{collected: collected, wishlisted: wishlisted}
end
describe "universal_search/2" do
setup [:create_records]
test "returns collection, wishlist, and artist results", %{
collected: collected,
wishlisted: wishlisted
} do
results = Search.universal_search("Marillion")
collection_ids = Enum.map(results.collection, & &1.id)
assert collected.id in collection_ids
wishlist_ids = Enum.map(results.wishlist, & &1.id)
assert wishlisted.id in wishlist_ids
artist_names = Enum.map(results.artists, fn a -> a.artist.name end)
assert "Marillion" in artist_names
end
test "returns empty results for no matches" do
results = Search.universal_search("zzz_nonexistent_zzz")
assert results.collection == []
assert results.wishlist == []
assert results.artists == []
end
test "respects limit", %{collected: _collected} do
results = Search.universal_search("Marillion", limit: 1)
assert length(results.collection) <= 1
assert length(results.wishlist) <= 1
assert length(results.artists) <= 1
end
end
describe "search_counts/1" do
setup [:create_records]
test "returns counts per category" do
counts = Search.search_counts("Marillion")
assert counts.collection_count >= 1
assert counts.wishlist_count >= 1
assert counts.artists_count >= 1
end
test "returns zero counts for no matches" do
counts = Search.search_counts("zzz_nonexistent_zzz")
assert counts.collection_count == 0
assert counts.wishlist_count == 0
assert counts.artists_count == 0
end
end
end