Files
music_library/test/music_library/errors_test.exs
T
Claudio Ortolina 65b2f3e1f2 Update dependencies
ex_slop 0.4.1 => 0.4.2

Introduces a new check around misusing the `length/1` function which
require widespread fixes.
2026-05-25 19:45:33 +03:00

236 lines
7.9 KiB
Elixir

defmodule MusicLibrary.ErrorsTest do
use MusicLibrary.DataCase
import MusicLibrary.ErrorsFixtures
alias MusicLibrary.Errors
# Helper to create an error with a unique fingerprint derived from a tag.
# Avoids unique constraint violations when creating multiple errors in a test.
defp unique_error(tag, attrs) do
line = "#{tag}_line"
func = "TestModule.#{tag}/0"
error_fixture(
Map.merge(
%{
source_line: line,
source_function: func,
fingerprint: error_fingerprint(:runtime_error, line, func)
},
Map.new(attrs)
)
)
end
describe "list_errors/1" do
test "returns all errors by default" do
e1 = unique_error("a", %{reason: "Error A"})
e2 = unique_error("b", %{reason: "Error B"})
result = Errors.list_errors()
assert result.total == 2
assert Enum.count_until(result.errors, 3) == 2
ids = Enum.map(result.errors, & &1.id)
assert e1.id in ids
assert e2.id in ids
end
test "filters by status" do
_e1 = unique_error("unres", %{status: :unresolved, reason: "Unresolved error"})
e2 = unique_error("res", %{status: :resolved, reason: "Resolved error"})
result = Errors.list_errors(status: :resolved)
assert result.total == 1
assert Enum.count_until(result.errors, 2) == 1
assert hd(result.errors).id == e2.id
assert hd(result.errors).status == :resolved
end
test "filters by muted" do
_e1 = unique_error("unmuted", %{muted: false, reason: "Not muted"})
e2 = unique_error("muted", %{muted: true, reason: "Muted error"})
result = Errors.list_errors(muted: true)
assert result.total == 1
assert Enum.count_until(result.errors, 2) == 1
assert hd(result.errors).id == e2.id
assert hd(result.errors).muted == true
end
test "filters by search substring on reason" do
_e1 = unique_error("first", %{reason: "First error"})
e2 = unique_error("second", %{reason: "Second error with specific text"})
result = Errors.list_errors(search: "specific text")
assert result.total == 1
assert Enum.count_until(result.errors, 2) == 1
assert hd(result.errors).id == e2.id
assert hd(result.errors).reason == "Second error with specific text"
end
test "search escapes LIKE wildcards (% and _) to prevent pattern expansion" do
e1 = unique_error("pct", %{reason: "Error with 100% match"})
_e2 = unique_error("pctx", %{reason: "Error with 100x match"})
# Searching for "100%" should only match the exact text "100%",
# not treat % as a wildcard that would also match "100x".
result = Errors.list_errors(search: "100%")
assert result.total == 1
assert Enum.count_until(result.errors, 2) == 1
assert hd(result.errors).id == e1.id
end
test "respects limit" do
_e1 = unique_error("lim_a", %{reason: "A"})
_e2 = unique_error("lim_b", %{reason: "B"})
_e3 = unique_error("lim_c", %{reason: "C"})
result = Errors.list_errors(limit: 2)
assert result.total == 3
assert Enum.count_until(result.errors, 3) == 2
end
test "respects offset" do
_e1 = unique_error("off_a", %{reason: "A"})
_e2 = unique_error("off_b", %{reason: "B"})
_e3 = unique_error("off_c", %{reason: "C"})
result = Errors.list_errors(offset: 1, limit: 10)
assert result.total == 3
assert Enum.count_until(result.errors, 3) == 2
end
test "returns empty list when no errors match filters" do
_e1 = unique_error("real", %{reason: "Real error"})
result = Errors.list_errors(search: "NONEXISTENT")
assert result.total == 0
assert result.errors == []
end
test "returns empty list when database has no errors at all" do
result = Errors.list_errors()
assert result.total == 0
assert result.errors == []
end
end
describe "get_error/1" do
test "returns error with occurrences, occurrence_count, and first_occurrence_at" do
error = unique_error("get_ok", %{reason: "Something went wrong"})
_occ1 = occurrence_fixture(error, %{breadcrumbs: ["first"]})
_occ2 = occurrence_fixture(error, %{breadcrumbs: ["second"]})
assert {:ok, result} = Errors.get_error(error.id)
assert result.id == error.id
assert result.reason == "Something went wrong"
assert result.occurrence_count == 2
refute is_nil(result.first_occurrence_at)
assert Enum.count_until(result.occurrences, 3) == 2
# Occurrences sorted desc by inserted_at (most recent first)
[occ1, occ2] = result.occurrences
assert occ1.breadcrumbs == ["second"]
assert occ2.breadcrumbs == ["first"]
end
test "returns occurrence_count = 0 and first_occurrence_at = nil for error with no occurrences" do
error = unique_error("no_occ", %{reason: "No occurrences"})
assert {:ok, result} = Errors.get_error(error.id)
assert result.occurrence_count == 0
assert is_nil(result.first_occurrence_at)
assert result.occurrences == []
end
test "returns {:error, :not_found} for non-existent id" do
assert Errors.get_error(99_999) == {:error, :not_found}
end
end
describe "mute_error/1, unmute_error/1, resolve_error/1, unresolve_error/1" do
test "mute_error/1 sets muted to true" do
error = unique_error("mute", %{muted: false})
assert {:ok, updated} = Errors.mute_error(error.id)
assert updated.muted == true
end
test "unmute_error/1 sets muted to false" do
error = unique_error("unmute", %{muted: true})
assert {:ok, updated} = Errors.unmute_error(error.id)
assert updated.muted == false
end
test "resolve_error/1 sets status to :resolved" do
error = unique_error("res", %{status: :unresolved})
assert {:ok, updated} = Errors.resolve_error(error.id)
assert updated.status == :resolved
end
test "unresolve_error/1 sets status to :unresolved" do
error = unique_error("unres", %{status: :resolved})
assert {:ok, updated} = Errors.unresolve_error(error.id)
assert updated.status == :unresolved
end
test "returns {:error, :not_found} for non-existent id" do
assert Errors.mute_error(99_999) == {:error, :not_found}
assert Errors.unmute_error(99_999) == {:error, :not_found}
assert Errors.resolve_error(99_999) == {:error, :not_found}
assert Errors.unresolve_error(99_999) == {:error, :not_found}
end
test "mute_error/1 on already-muted error succeeds (idempotent)" do
error = unique_error("mute_idem", %{muted: true})
assert {:ok, updated} = Errors.mute_error(error.id)
assert updated.muted == true
end
test "unmute_error/1 on already-unmuted error succeeds (idempotent)" do
error = unique_error("unmute_idem", %{muted: false})
assert {:ok, updated} = Errors.unmute_error(error.id)
assert updated.muted == false
end
test "resolve_error/1 on already-resolved error succeeds (idempotent)" do
error = unique_error("res_idem", %{status: :resolved})
assert {:ok, updated} = Errors.resolve_error(error.id)
assert updated.status == :resolved
end
test "unresolve_error/1 on already-unresolved error succeeds (idempotent)" do
error = unique_error("unres_idem", %{status: :unresolved})
assert {:ok, updated} = Errors.unresolve_error(error.id)
assert updated.status == :unresolved
end
end
describe "escape_like_wildcards/1" do
test "escapes % and _ characters" do
assert Errors.escape_like_wildcards("100%") == "100\\%"
assert Errors.escape_like_wildcards("a_b") == "a\\_b"
assert Errors.escape_like_wildcards("50%_off") == "50\\%\\_off"
end
test "preserves backslash escaping" do
assert Errors.escape_like_wildcards("a\\b") == "a\\\\b"
end
test "passes through normal text unchanged" do
assert Errors.escape_like_wildcards("normal text") == "normal text"
end
end
end