From 854e2939025ac9bb3bff9003822b666e156a78fc Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Sun, 1 Mar 2026 17:03:36 +0000 Subject: [PATCH] Add large battery of tests, primarily workers --- test/music_library/artist_chat_test.exs | 10 +++ test/music_library/artists/batch_test.exs | 38 ++++++++++ test/music_library/batch_test.exs | 68 +++++++++++++++++ .../colors/color_frequency_extractor_test.exs | 34 +++++++++ .../colors/edge_weighted_extractor_test.exs | 32 ++++++++ test/music_library/record_chat_test.exs | 10 +++ test/music_library/records/batch_test.exs | 31 ++++++++ test/music_library/secrets_test.exs | 50 +++++++++++++ .../artist_refresh_all_discogs_data_test.exs | 21 ++++++ ...tist_refresh_all_musicbrainz_data_test.exs | 21 ++++++ ...artist_refresh_all_wikipedia_data_test.exs | 21 ++++++ .../artist_refresh_discogs_data_test.exs | 40 ++++++++++ .../artist_refresh_music_brainz_data_test.exs | 33 +++++++++ .../artist_refresh_wikipedia_data_test.exs | 53 ++++++++++++++ .../worker/extract_colors_test.exs | 38 ++++++++++ .../worker/fetch_artist_image_test.exs | 55 ++++++++++++++ .../worker/fetch_artist_info_test.exs | 73 +++++++++++++++++++ .../worker/prune_artist_info_test.exs | 35 +++++++++ .../worker/prune_asset_cache_test.exs | 28 +++++++ .../worker/prune_assets_test.exs | 45 ++++++++++++ .../record_generate_all_embeddings_test.exs | 19 +++++ ...cord_refresh_all_musicbrainz_data_test.exs | 19 +++++ .../record_refresh_music_brainz_data_test.exs | 36 +++++++++ .../worker/refresh_cover_test.exs | 31 ++++++++ .../worker/repo_optimize_test.exs | 11 +++ test/test_helper.exs | 2 +- 26 files changed, 853 insertions(+), 1 deletion(-) create mode 100644 test/music_library/artist_chat_test.exs create mode 100644 test/music_library/artists/batch_test.exs create mode 100644 test/music_library/batch_test.exs create mode 100644 test/music_library/colors/color_frequency_extractor_test.exs create mode 100644 test/music_library/colors/edge_weighted_extractor_test.exs create mode 100644 test/music_library/record_chat_test.exs create mode 100644 test/music_library/records/batch_test.exs create mode 100644 test/music_library/secrets_test.exs create mode 100644 test/music_library/worker/artist_refresh_all_discogs_data_test.exs create mode 100644 test/music_library/worker/artist_refresh_all_musicbrainz_data_test.exs create mode 100644 test/music_library/worker/artist_refresh_all_wikipedia_data_test.exs create mode 100644 test/music_library/worker/artist_refresh_discogs_data_test.exs create mode 100644 test/music_library/worker/artist_refresh_music_brainz_data_test.exs create mode 100644 test/music_library/worker/artist_refresh_wikipedia_data_test.exs create mode 100644 test/music_library/worker/extract_colors_test.exs create mode 100644 test/music_library/worker/fetch_artist_image_test.exs create mode 100644 test/music_library/worker/fetch_artist_info_test.exs create mode 100644 test/music_library/worker/prune_artist_info_test.exs create mode 100644 test/music_library/worker/prune_asset_cache_test.exs create mode 100644 test/music_library/worker/prune_assets_test.exs create mode 100644 test/music_library/worker/record_generate_all_embeddings_test.exs create mode 100644 test/music_library/worker/record_refresh_all_musicbrainz_data_test.exs create mode 100644 test/music_library/worker/record_refresh_music_brainz_data_test.exs create mode 100644 test/music_library/worker/refresh_cover_test.exs create mode 100644 test/music_library/worker/repo_optimize_test.exs diff --git a/test/music_library/artist_chat_test.exs b/test/music_library/artist_chat_test.exs new file mode 100644 index 00000000..bcc5e4bd --- /dev/null +++ b/test/music_library/artist_chat_test.exs @@ -0,0 +1,10 @@ +defmodule MusicLibrary.ArtistChatTest do + use ExUnit.Case + + alias MusicLibrary.ArtistChat + + test "implements the Chat behaviour" do + Code.ensure_loaded!(ArtistChat) + assert function_exported?(ArtistChat, :stream_response, 3) + end +end diff --git a/test/music_library/artists/batch_test.exs b/test/music_library/artists/batch_test.exs new file mode 100644 index 00000000..ad0a6d7c --- /dev/null +++ b/test/music_library/artists/batch_test.exs @@ -0,0 +1,38 @@ +defmodule MusicLibrary.Artists.BatchTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Artists.Batch + + setup do + record = record() + artist = hd(record.artists) + _artist_info = artist_info(artist.musicbrainz_id) + :ok + end + + describe "refresh_musicbrainz_data/0" do + test "enqueues refresh jobs for all artist infos" do + assert {:ok, []} = Batch.refresh_musicbrainz_data() + end + end + + describe "refresh_discogs_data/0" do + test "enqueues refresh jobs for all artist infos" do + assert {:ok, []} = Batch.refresh_discogs_data() + end + end + + describe "refresh_wikipedia_data/0" do + test "enqueues refresh jobs for all artist infos" do + assert {:ok, []} = Batch.refresh_wikipedia_data() + end + end + + describe "refresh_lastfm_data/0" do + test "enqueues refresh jobs for all artist infos" do + assert {:ok, []} = Batch.refresh_lastfm_data() + end + end +end diff --git a/test/music_library/batch_test.exs b/test/music_library/batch_test.exs new file mode 100644 index 00000000..480e2426 --- /dev/null +++ b/test/music_library/batch_test.exs @@ -0,0 +1,68 @@ +defmodule MusicLibrary.BatchTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Batch + alias MusicLibrary.Records.Record + + describe "run_on_all/3" do + test "processes all records and returns empty list on success" do + _r1 = record() + _r2 = record() + + assert {:ok, []} = + Batch.run_on_all(Record, "record", fn _record -> + :ok + end) + end + + @tag :capture_log + test "collects failed record IDs" do + r1 = record() + r2 = record() + + assert {:ok, failed_ids} = + Batch.run_on_all(Record, "record", fn _record -> + {:error, :test_failure} + end) + + assert length(failed_ids) == 2 + assert r1.id in failed_ids + assert r2.id in failed_ids + end + + @tag :capture_log + test "handles mixed success and failure" do + r1 = record() + _r2 = record() + + assert {:ok, failed_ids} = + Batch.run_on_all(Record, "record", fn record -> + if record.id == r1.id do + {:error, :test_failure} + else + :ok + end + end) + + assert failed_ids == [r1.id] + end + + test "works with empty table" do + assert {:ok, []} = + Batch.run_on_all(Record, "record", fn _record -> + :ok + end) + end + + test "accepts {:ok, result} as success" do + _r1 = record() + + assert {:ok, []} = + Batch.run_on_all(Record, "record", fn _record -> + {:ok, :updated} + end) + end + end +end diff --git a/test/music_library/colors/color_frequency_extractor_test.exs b/test/music_library/colors/color_frequency_extractor_test.exs new file mode 100644 index 00000000..6695608b --- /dev/null +++ b/test/music_library/colors/color_frequency_extractor_test.exs @@ -0,0 +1,34 @@ +defmodule MusicLibrary.Colors.ColorFrequencyExtractorTest do + use ExUnit.Case + + alias MusicLibrary.Colors.ColorFrequencyExtractor + + @image_data MusicLibrary.Fixtures.Records.marbles_cover_data() + + describe "extract_dominant_colors/1" do + @describetag :slow + + test "extracts colors from an image" do + assert {:ok, colors} = ColorFrequencyExtractor.extract_dominant_colors(@image_data) + + assert is_list(colors) + assert colors == ["#000000", "#C08080", "#400000", "#C0C0C0", "#404000"] + end + + test "extracts custom number of colors" do + assert {:ok, colors} = ColorFrequencyExtractor.extract_dominant_colors(@image_data, 3) + + assert colors == ["#000000", "#C08080", "#400000"] + end + end + + describe "extract_dominant_colors!/1" do + @describetag :slow + + test "extracts colors or raises" do + colors = ColorFrequencyExtractor.extract_dominant_colors!(@image_data) + + assert colors == ["#000000", "#C08080", "#400000", "#C0C0C0", "#404000"] + end + end +end diff --git a/test/music_library/colors/edge_weighted_extractor_test.exs b/test/music_library/colors/edge_weighted_extractor_test.exs new file mode 100644 index 00000000..06162b6e --- /dev/null +++ b/test/music_library/colors/edge_weighted_extractor_test.exs @@ -0,0 +1,32 @@ +defmodule MusicLibrary.Colors.EdgeWeightedExtractorTest do + use ExUnit.Case + + alias MusicLibrary.Colors.EdgeWeightedExtractor + + @image_data MusicLibrary.Fixtures.Records.marbles_cover_data() + + describe "extract_dominant_colors/1" do + @describetag :slow + test "extracts colors from an image" do + assert {:ok, colors} = + EdgeWeightedExtractor.extract_dominant_colors(@image_data) + + assert colors == ["#000000", "#A07850", "#785028", "#502828", "#C8A078"] + end + + test "extracts custom number of colors" do + assert {:ok, colors} = EdgeWeightedExtractor.extract_dominant_colors(@image_data, 3) + + assert colors == ["#000000", "#A07850", "#785028"] + end + end + + describe "extract_dominant_colors!/1" do + @describetag :slow + test "extracts colors or raises" do + colors = EdgeWeightedExtractor.extract_dominant_colors!(@image_data) + + assert colors == ["#000000", "#A07850", "#785028", "#502828", "#C8A078"] + end + end +end diff --git a/test/music_library/record_chat_test.exs b/test/music_library/record_chat_test.exs new file mode 100644 index 00000000..b75cb4cc --- /dev/null +++ b/test/music_library/record_chat_test.exs @@ -0,0 +1,10 @@ +defmodule MusicLibrary.RecordChatTest do + use ExUnit.Case + + alias MusicLibrary.RecordChat + + test "implements the Chat behaviour" do + Code.ensure_loaded!(RecordChat) + assert function_exported?(RecordChat, :stream_response, 3) + end +end diff --git a/test/music_library/records/batch_test.exs b/test/music_library/records/batch_test.exs new file mode 100644 index 00000000..ca1c367d --- /dev/null +++ b/test/music_library/records/batch_test.exs @@ -0,0 +1,31 @@ +defmodule MusicLibrary.Records.BatchTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Records.Batch + + describe "refresh_musicbrainz_data/0" do + test "enqueues refresh jobs for all records" do + _record = record() + + assert {:ok, []} = Batch.refresh_musicbrainz_data() + end + + test "succeeds with no records" do + assert {:ok, []} = Batch.refresh_musicbrainz_data() + end + end + + describe "generate_embeddings/0" do + test "enqueues embedding jobs for all records" do + _record = record() + + assert {:ok, []} = Batch.generate_embeddings() + end + + test "succeeds with no records" do + assert {:ok, []} = Batch.generate_embeddings() + end + end +end diff --git a/test/music_library/secrets_test.exs b/test/music_library/secrets_test.exs new file mode 100644 index 00000000..e761e5ac --- /dev/null +++ b/test/music_library/secrets_test.exs @@ -0,0 +1,50 @@ +defmodule MusicLibrary.SecretsTest do + use MusicLibrary.DataCase + + alias MusicLibrary.Secrets + + describe "store/2" do + test "stores a secret" do + assert {:ok, secret} = Secrets.store("test_key", "test_value") + assert secret.name == "test_key" + end + + test "replaces an existing secret" do + {:ok, _} = Secrets.store("test_key", "original") + {:ok, _} = Secrets.store("test_key", "updated") + + secret = Secrets.get!("test_key") + assert secret.value == "updated" + end + end + + describe "get!/1" do + test "retrieves a stored secret" do + {:ok, _} = Secrets.store("test_key", "test_value") + + secret = Secrets.get!("test_key") + assert secret.name == "test_key" + assert secret.value == "test_value" + end + + test "raises when secret does not exist" do + assert_raise Ecto.NoResultsError, fn -> + Secrets.get!("nonexistent") + end + end + end + + describe "get/1" do + test "retrieves a stored secret" do + {:ok, _} = Secrets.store("test_key", "test_value") + + secret = Secrets.get("test_key") + assert secret.name == "test_key" + assert secret.value == "test_value" + end + + test "returns nil when secret does not exist" do + assert Secrets.get("nonexistent") == nil + end + end +end diff --git a/test/music_library/worker/artist_refresh_all_discogs_data_test.exs b/test/music_library/worker/artist_refresh_all_discogs_data_test.exs new file mode 100644 index 00000000..d1d4be7f --- /dev/null +++ b/test/music_library/worker/artist_refresh_all_discogs_data_test.exs @@ -0,0 +1,21 @@ +defmodule MusicLibrary.Worker.ArtistRefreshAllDiscogsDataTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Worker.ArtistRefreshAllDiscogsData + + describe "perform/1" do + test "enqueues refresh jobs for all artist infos" do + record = record() + artist = hd(record.artists) + _artist_info = artist_info(artist.musicbrainz_id) + + assert {:ok, []} = perform_job(ArtistRefreshAllDiscogsData, %{}) + end + + test "succeeds with no artist infos" do + assert {:ok, []} = perform_job(ArtistRefreshAllDiscogsData, %{}) + end + end +end diff --git a/test/music_library/worker/artist_refresh_all_musicbrainz_data_test.exs b/test/music_library/worker/artist_refresh_all_musicbrainz_data_test.exs new file mode 100644 index 00000000..ffbfe2ce --- /dev/null +++ b/test/music_library/worker/artist_refresh_all_musicbrainz_data_test.exs @@ -0,0 +1,21 @@ +defmodule MusicLibrary.Worker.ArtistRefreshAllMusicBrainzDataTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Worker.ArtistRefreshAllMusicBrainzData + + describe "perform/1" do + test "enqueues refresh jobs for all artist infos" do + record = record() + artist = hd(record.artists) + _artist_info = artist_info(artist.musicbrainz_id) + + assert {:ok, []} = perform_job(ArtistRefreshAllMusicBrainzData, %{}) + end + + test "succeeds with no artist infos" do + assert {:ok, []} = perform_job(ArtistRefreshAllMusicBrainzData, %{}) + end + end +end diff --git a/test/music_library/worker/artist_refresh_all_wikipedia_data_test.exs b/test/music_library/worker/artist_refresh_all_wikipedia_data_test.exs new file mode 100644 index 00000000..29bd32c9 --- /dev/null +++ b/test/music_library/worker/artist_refresh_all_wikipedia_data_test.exs @@ -0,0 +1,21 @@ +defmodule MusicLibrary.Worker.ArtistRefreshAllWikipediaDataTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Worker.ArtistRefreshAllWikipediaData + + describe "perform/1" do + test "enqueues refresh jobs for all artist infos" do + record = record() + artist = hd(record.artists) + _artist_info = artist_info(artist.musicbrainz_id) + + assert {:ok, []} = perform_job(ArtistRefreshAllWikipediaData, %{}) + end + + test "succeeds with no artist infos" do + assert {:ok, []} = perform_job(ArtistRefreshAllWikipediaData, %{}) + end + end +end diff --git a/test/music_library/worker/artist_refresh_discogs_data_test.exs b/test/music_library/worker/artist_refresh_discogs_data_test.exs new file mode 100644 index 00000000..cf23fd56 --- /dev/null +++ b/test/music_library/worker/artist_refresh_discogs_data_test.exs @@ -0,0 +1,40 @@ +defmodule MusicLibrary.Worker.ArtistRefreshDiscogsDataTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias Discogs.Fixtures.Artist, as: ArtistFixture + alias MusicLibrary.Artists + alias MusicLibrary.Worker.ArtistRefreshDiscogsData + + setup do + record = record() + artist = hd(record.artists) + artist_info = artist_info(artist.musicbrainz_id) + %{artist_info: artist_info} + end + + describe "perform/1" do + test "refreshes Discogs data", %{artist_info: artist_info} do + Req.Test.stub(Discogs.API, fn conn -> + Req.Test.json(conn, ArtistFixture.get_artist()) + end) + + assert {:ok, _} = perform_job(ArtistRefreshDiscogsData, %{"id" => artist_info.id}) + + updated = Artists.get_artist_info!(artist_info.id) + assert updated.discogs_data != nil + end + + test "returns ok when no discogs data is available" do + artist_id = Ecto.UUID.generate() + + Repo.insert!(%Artists.ArtistInfo{ + id: artist_id, + musicbrainz_data: %{"name" => "No Discogs Artist"} + }) + + assert {:ok, _} = perform_job(ArtistRefreshDiscogsData, %{"id" => artist_id}) + end + end +end diff --git a/test/music_library/worker/artist_refresh_music_brainz_data_test.exs b/test/music_library/worker/artist_refresh_music_brainz_data_test.exs new file mode 100644 index 00000000..b7bd8357 --- /dev/null +++ b/test/music_library/worker/artist_refresh_music_brainz_data_test.exs @@ -0,0 +1,33 @@ +defmodule MusicLibrary.Worker.ArtistRefreshMusicBrainzDataTest do + use MusicLibrary.DataCase + + alias MusicBrainz.Fixtures.Artist, as: ArtistFixture + alias MusicLibrary.Artists + alias MusicLibrary.Artists.ArtistInfo + alias MusicLibrary.Worker.ArtistRefreshMusicBrainzData + + setup do + artist_id = Ecto.UUID.generate() + + Repo.insert!(%ArtistInfo{ + id: artist_id, + musicbrainz_data: %{"name" => "Steven Wilson"} + }) + + %{artist_id: artist_id} + end + + describe "perform/1" do + test "refreshes MusicBrainz data for an artist", %{artist_id: artist_id} do + Req.Test.stub(MusicBrainz.API, fn conn -> + Req.Test.json(conn, ArtistFixture.get_artist()) + end) + + assert {:ok, %ArtistInfo{}} = + perform_job(ArtistRefreshMusicBrainzData, %{"id" => artist_id}) + + updated = Artists.get_artist_info!(artist_id) + assert updated.musicbrainz_data["name"] == "Steven Wilson" + end + end +end diff --git a/test/music_library/worker/artist_refresh_wikipedia_data_test.exs b/test/music_library/worker/artist_refresh_wikipedia_data_test.exs new file mode 100644 index 00000000..3f5150b8 --- /dev/null +++ b/test/music_library/worker/artist_refresh_wikipedia_data_test.exs @@ -0,0 +1,53 @@ +defmodule MusicLibrary.Worker.ArtistRefreshWikipediaDataTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Artists + alias MusicLibrary.Worker.ArtistRefreshWikipediaData + + setup do + record = record() + artist = hd(record.artists) + artist_info = artist_info(artist.musicbrainz_id) + %{artist_info: artist_info} + end + + describe "perform/1" do + test "refreshes Wikipedia data", %{artist_info: artist_info} do + Req.Test.stub(Wikipedia.API, fn conn -> + conn = Plug.Conn.fetch_query_params(conn) + + case conn.params do + %{"action" => "wbgetentities"} -> + Req.Test.json(conn, Wikipedia.Fixtures.wikidata_response()) + + %{"action" => "query"} -> + Req.Test.json(conn, Wikipedia.Fixtures.article_extract()) + + _ -> + Req.Test.json(conn, Wikipedia.Fixtures.article_summary()) + end + end) + + assert {:ok, _} = perform_job(ArtistRefreshWikipediaData, %{"id" => artist_info.id}) + + updated = Artists.get_artist_info!(artist_info.id) + assert updated.wikipedia_data != nil + assert updated.wikipedia_data != %{} + end + + test "discards job when no wikidata_id exists in musicbrainz_data" do + artist_id = Ecto.UUID.generate() + + Repo.insert!(%Artists.ArtistInfo{ + id: artist_id, + musicbrainz_data: %{"name" => "No Wikipedia Artist"} + }) + + # No wikidata relation in musicbrainz_data → fetch_wikipedia_data returns {:ok, artist_info} + # Worker wraps non-error returns with `with`, so it passes through as :ok + assert {:ok, _} = perform_job(ArtistRefreshWikipediaData, %{"id" => artist_id}) + end + end +end diff --git a/test/music_library/worker/extract_colors_test.exs b/test/music_library/worker/extract_colors_test.exs new file mode 100644 index 00000000..8208d308 --- /dev/null +++ b/test/music_library/worker/extract_colors_test.exs @@ -0,0 +1,38 @@ +defmodule MusicLibrary.Worker.ExtractColorsTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Records + alias MusicLibrary.Worker.ExtractColors + + describe "perform/1" do + @describetag :slow + test "extracts colors using fast method" do + record = record() + + assert :ok = perform_job(ExtractColors, %{"id" => record.id, "method" => "fast"}) + + updated = Records.get_record!(record.id) + assert is_list(updated.dominant_colors) + assert updated.dominant_colors != [] + assert Enum.all?(updated.dominant_colors, &String.starts_with?(&1, "#")) + end + + test "extracts colors using slow method" do + record = record() + + assert :ok = perform_job(ExtractColors, %{"id" => record.id, "method" => "slow"}) + + updated = Records.get_record!(record.id) + assert is_list(updated.dominant_colors) + assert updated.dominant_colors != [] + end + + test "raises when record does not exist" do + assert_raise Ecto.NoResultsError, fn -> + perform_job(ExtractColors, %{"id" => Ecto.UUID.generate(), "method" => "fast"}) + end + end + end +end diff --git a/test/music_library/worker/fetch_artist_image_test.exs b/test/music_library/worker/fetch_artist_image_test.exs new file mode 100644 index 00000000..eebc99b3 --- /dev/null +++ b/test/music_library/worker/fetch_artist_image_test.exs @@ -0,0 +1,55 @@ +defmodule MusicLibrary.Worker.FetchArtistImageTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias Discogs.Fixtures.Artist + alias MusicLibrary.Artists + alias MusicLibrary.Worker.FetchArtistImage + + setup do + record = record() + artist = hd(record.artists) + artist_info = artist_info(artist.musicbrainz_id) + %{artist_info: artist_info} + end + + describe "perform/1" do + test "fetches and stores artist image", %{artist_info: artist_info} do + Req.Test.stub(Discogs.API, fn conn -> + Plug.Conn.send_resp(conn, 200, Artist.image_data()) + end) + + assert :ok = perform_job(FetchArtistImage, %{"id" => artist_info.id}) + + updated = Artists.get_artist_info!(artist_info.id) + assert updated.image_data_hash != nil + end + + test "cancels when no discogs data exists" do + artist_id = Ecto.UUID.generate() + + Repo.insert!(%Artists.ArtistInfo{ + id: artist_id, + musicbrainz_data: %{"name" => "No Image Artist"}, + discogs_data: nil + }) + + assert {:cancel, :no_discogs_data} = + perform_job(FetchArtistImage, %{"id" => artist_id}) + end + + test "cancels when discogs data has no images" do + artist_id = Ecto.UUID.generate() + + Repo.insert!(%Artists.ArtistInfo{ + id: artist_id, + musicbrainz_data: %{"name" => "No Image Artist"}, + discogs_data: %{"id" => 12_345, "images" => []} + }) + + assert {:cancel, :image_not_found} = + perform_job(FetchArtistImage, %{"id" => artist_id}) + end + end +end diff --git a/test/music_library/worker/fetch_artist_info_test.exs b/test/music_library/worker/fetch_artist_info_test.exs new file mode 100644 index 00000000..5439e498 --- /dev/null +++ b/test/music_library/worker/fetch_artist_info_test.exs @@ -0,0 +1,73 @@ +defmodule MusicLibrary.Worker.FetchArtistInfoTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Artists + alias MusicLibrary.Worker.FetchArtistInfo + + # The MusicBrainz fixture for Steven Wilson uses this fixed ID + @steven_wilson_mbid "3a51b862-0144-40f6-aa17-6aaeefea29d9" + + describe "perform/1" do + test "fetches and stores artist info from all sources" do + # Create a record with the artist musicbrainz_id matching the fixture + _record = + record(%{ + artists: [ + %{ + name: "Steven Wilson", + musicbrainz_id: @steven_wilson_mbid, + sort_name: "Wilson, Steven", + joinphrase: "" + } + ] + }) + + Req.Test.stub(MusicBrainz.API, fn conn -> + Req.Test.json(conn, MusicBrainz.Fixtures.Artist.get_artist()) + end) + + Req.Test.stub(Discogs.API, fn conn -> + case conn.request_path do + "/artists/" <> _ -> + Req.Test.json(conn, Discogs.Fixtures.Artist.get_artist()) + + _ -> + Plug.Conn.send_resp(conn, 200, Discogs.Fixtures.Artist.image_data()) + end + end) + + Req.Test.stub(Wikipedia.API, fn conn -> + conn = Plug.Conn.fetch_query_params(conn) + + case conn.params do + %{"action" => "wbgetentities"} -> + Req.Test.json(conn, Wikipedia.Fixtures.wikidata_response()) + + %{"action" => "query"} -> + Req.Test.json(conn, Wikipedia.Fixtures.article_extract()) + + _ -> + Req.Test.json(conn, Wikipedia.Fixtures.article_summary()) + end + end) + + Req.Test.stub(LastFm.API, fn conn -> + Req.Test.json(conn, %{ + "toptags" => %{ + "tag" => [ + %{"name" => "progressive rock", "count" => 100} + ] + } + }) + end) + + assert :ok = perform_job(FetchArtistInfo, %{"id" => @steven_wilson_mbid}) + + artist_info = Artists.get_artist_info!(@steven_wilson_mbid) + assert artist_info.musicbrainz_data != nil + assert artist_info.wikipedia_data != nil + end + end +end diff --git a/test/music_library/worker/prune_artist_info_test.exs b/test/music_library/worker/prune_artist_info_test.exs new file mode 100644 index 00000000..1949d313 --- /dev/null +++ b/test/music_library/worker/prune_artist_info_test.exs @@ -0,0 +1,35 @@ +defmodule MusicLibrary.Worker.PruneArtistInfoTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Artists + alias MusicLibrary.Worker.PruneArtistInfo + + describe "perform/1" do + test "keeps artist info when artist is still referenced by a record" do + record = record() + artist = hd(record.artists) + artist_info = artist_info(artist.musicbrainz_id) + + assert :ok = perform_job(PruneArtistInfo, %{"id" => artist.musicbrainz_id}) + + assert Artists.get_artist_info!(artist_info.id) + end + + test "deletes artist info when artist is not referenced by any record" do + artist_id = Ecto.UUID.generate() + + Repo.insert!(%Artists.ArtistInfo{ + id: artist_id, + musicbrainz_data: %{"name" => "Orphaned Artist"} + }) + + assert :ok = perform_job(PruneArtistInfo, %{"id" => artist_id}) + + assert_raise Ecto.NoResultsError, fn -> + Artists.get_artist_info!(artist_id) + end + end + end +end diff --git a/test/music_library/worker/prune_asset_cache_test.exs b/test/music_library/worker/prune_asset_cache_test.exs new file mode 100644 index 00000000..87b3782d --- /dev/null +++ b/test/music_library/worker/prune_asset_cache_test.exs @@ -0,0 +1,28 @@ +defmodule MusicLibrary.Worker.PruneAssetCacheTest do + use MusicLibrary.DataCase + + alias MusicLibrary.Assets.Cache + alias MusicLibrary.Worker.PruneAssetCache + + describe "perform/1" do + @describetag :capture_log + + test "prunes old cache entries" do + # Insert a cache entry with an old timestamp + old_timestamp = DateTime.utc_now() |> DateTime.add(-8, :day) |> DateTime.to_unix() + :ets.insert(Cache, {{"old_payload", "image/jpeg"}, old_timestamp, "old_content"}) + + # Insert a recent cache entry + Cache.set("recent_payload", "image/jpeg", "recent_content") + + assert :ok = perform_job(PruneAssetCache, %{}) + + assert Cache.get("old_payload", "image/jpeg") == :not_found + assert {:found, "recent_content"} = Cache.get("recent_payload", "image/jpeg") + end + + test "succeeds with empty cache" do + assert :ok = perform_job(PruneAssetCache, %{}) + end + end +end diff --git a/test/music_library/worker/prune_assets_test.exs b/test/music_library/worker/prune_assets_test.exs new file mode 100644 index 00000000..5fc21acd --- /dev/null +++ b/test/music_library/worker/prune_assets_test.exs @@ -0,0 +1,45 @@ +defmodule MusicLibrary.Worker.PruneAssetsTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Assets + alias MusicLibrary.Worker.PruneAssets + + describe "perform/1" do + @describetag :capture_log + test "deletes unreferenced assets" do + {:ok, orphan} = Assets.store(%{content: "orphan_data", format: "image/jpeg"}) + + assert Assets.get(orphan.hash) != nil + + assert :ok = perform_job(PruneAssets, %{}) + + assert Assets.get(orphan.hash) == nil + end + + test "keeps assets referenced by records" do + record = record() + + assert :ok = perform_job(PruneAssets, %{}) + + assert Assets.get(record.cover_hash) != nil + end + + test "keeps assets referenced by artist info" do + record = record() + artist = hd(record.artists) + _artist_info = artist_info(artist.musicbrainz_id) + + assert :ok = perform_job(PruneAssets, %{}) + + # The artist info image data comes from Discogs fixture which stores an asset + # The record's cover asset should still be present + assert Assets.get(record.cover_hash) != nil + end + + test "succeeds with no unreferenced assets" do + assert :ok = perform_job(PruneAssets, %{}) + end + end +end diff --git a/test/music_library/worker/record_generate_all_embeddings_test.exs b/test/music_library/worker/record_generate_all_embeddings_test.exs new file mode 100644 index 00000000..53c83c8b --- /dev/null +++ b/test/music_library/worker/record_generate_all_embeddings_test.exs @@ -0,0 +1,19 @@ +defmodule MusicLibrary.Worker.RecordGenerateAllEmbeddingsTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Worker.RecordGenerateAllEmbeddings + + describe "perform/1" do + test "enqueues embedding generation jobs for all records" do + _record = record() + + assert {:ok, []} = perform_job(RecordGenerateAllEmbeddings, %{}) + end + + test "succeeds with no records" do + assert {:ok, []} = perform_job(RecordGenerateAllEmbeddings, %{}) + end + end +end diff --git a/test/music_library/worker/record_refresh_all_musicbrainz_data_test.exs b/test/music_library/worker/record_refresh_all_musicbrainz_data_test.exs new file mode 100644 index 00000000..2e901b7a --- /dev/null +++ b/test/music_library/worker/record_refresh_all_musicbrainz_data_test.exs @@ -0,0 +1,19 @@ +defmodule MusicLibrary.Worker.RecordRefreshAllMusicBrainzDataTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Worker.RecordRefreshAllMusicBrainzData + + describe "perform/1" do + test "enqueues refresh jobs for all records" do + _record = record() + + assert {:ok, []} = perform_job(RecordRefreshAllMusicBrainzData, %{}) + end + + test "succeeds with no records" do + assert {:ok, []} = perform_job(RecordRefreshAllMusicBrainzData, %{}) + end + end +end diff --git a/test/music_library/worker/record_refresh_music_brainz_data_test.exs b/test/music_library/worker/record_refresh_music_brainz_data_test.exs new file mode 100644 index 00000000..99f5de1b --- /dev/null +++ b/test/music_library/worker/record_refresh_music_brainz_data_test.exs @@ -0,0 +1,36 @@ +defmodule MusicLibrary.Worker.RecordRefreshMusicBrainzDataTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicBrainz.Fixtures.ReleaseGroup + alias MusicLibrary.Records + alias MusicLibrary.Worker.RecordRefreshMusicBrainzData + + describe "perform/1" do + test "refreshes MusicBrainz data for a record" do + record = record() + + Req.Test.stub(MusicBrainz.API, fn conn -> + case conn.request_path do + "/ws/2/release-group/" <> _ -> + Req.Test.json(conn, ReleaseGroup.release_group(:marbles)) + + "/ws/2/release" <> _ -> + Req.Test.json(conn, %{"releases" => [], "release-count" => 0, "release-offset" => 0}) + end + end) + + assert :ok = perform_job(RecordRefreshMusicBrainzData, %{"id" => record.id}) + + updated = Records.get_record!(record.id) + assert updated.musicbrainz_data != nil + end + + test "raises when record does not exist" do + assert_raise Ecto.NoResultsError, fn -> + perform_job(RecordRefreshMusicBrainzData, %{"id" => Ecto.UUID.generate()}) + end + end + end +end diff --git a/test/music_library/worker/refresh_cover_test.exs b/test/music_library/worker/refresh_cover_test.exs new file mode 100644 index 00000000..94bef1d9 --- /dev/null +++ b/test/music_library/worker/refresh_cover_test.exs @@ -0,0 +1,31 @@ +defmodule MusicLibrary.Worker.RefreshCoverTest do + use MusicLibrary.DataCase + + import MusicLibrary.Fixtures.Records + + alias MusicLibrary.Assets + alias MusicLibrary.Records + alias MusicLibrary.Worker.RefreshCover + + describe "perform/1" do + test "refreshes the record cover from MusicBrainz" do + record = record() + + Req.Test.stub(MusicBrainz.API, fn conn -> + Plug.Conn.send_resp(conn, 200, marbles_cover_data()) + end) + + assert :ok = perform_job(RefreshCover, %{"id" => record.id}) + + updated = Records.get_record!(record.id) + assert updated.cover_hash != nil + assert Assets.get(updated.cover_hash) != nil + end + + test "raises when record does not exist" do + assert_raise Ecto.NoResultsError, fn -> + perform_job(RefreshCover, %{"id" => Ecto.UUID.generate()}) + end + end + end +end diff --git a/test/music_library/worker/repo_optimize_test.exs b/test/music_library/worker/repo_optimize_test.exs new file mode 100644 index 00000000..f55f8f25 --- /dev/null +++ b/test/music_library/worker/repo_optimize_test.exs @@ -0,0 +1,11 @@ +defmodule MusicLibrary.Worker.RepoOptimizeTest do + use MusicLibrary.DataCase + + alias MusicLibrary.Worker.RepoOptimize + + describe "perform/1" do + test "runs optimize on the repo" do + assert {:ok, %Exqlite.Result{}} = perform_job(RepoOptimize, %{}) + end + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index ef6b774c..b631bfe6 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,3 +1,3 @@ -ExUnit.start() +ExUnit.start(exclude: [:slow]) Ecto.Adapters.SQL.Sandbox.mode(MusicLibrary.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(MusicLibrary.BackgroundRepo, :manual)