Commit Graph

230 Commits

Author SHA1 Message Date
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
Claudio Ortolina f1c9069c35 Expand enrichment query scope and streamline selected release 2026-05-22 13:40:40 +01:00
Claudio Ortolina ff3f809c2c ML-170: add enrichment fields to collection API
Add MusicLibrary.Collection.Enrichment module with batch scrobble, artist
country, and selected release enrichment from existing tables.

Wire enrichment into all four collection API endpoints (index, latest,
random, on_this_day). New JSON fields: scrobble_count, last_listened_at,
artist_country, selected_release.

Uses 3 fixed-count batch queries regardless of page size — no N+1 risk.
No schema changes or migrations required.
2026-05-22 13:21:14 +01:00
Claudio Ortolina d8a67cd475 ML-181: harden assets endpoints 2026-05-22 08:20:35 +01:00
Claudio Ortolina 5601d681e9 Make some tests async 2026-05-22 07:42:54 +01:00
Claudio Ortolina ca01e94d47 ML-194: add selective smoke coverage
Add smoke tests for bulk cron workers, CSP browser header, and
on_mount hooks (GetTimezone/StaticAssets). 9 tests across 4 files,
all passing alongside 135 existing related tests.
2026-05-21 12:01:32 +01:00
Claudio Ortolina ec6980eea0 ML-193: add secondary parser, search, and API coverage tests
Add ~80 focused tests across 10 files covering:

- Record set search by name, description, contained record title, artist name
- Navigation events for records, artists, record sets, links, and view-all actions
- MusicBrainz.ReleaseGroup parser (artist credits, included RGs, release IDs, types)
- MusicBrainz.ReleaseSearchResult edge cases (missing RG, unknown formats)
- LastFm.Session XML edge cases (non-subscriber, missing nodes)
- MusicLibrary.HttpError default status-kind mapping
- Discogs.API.ErrorResponse fallback message and retry-delay behavior
- ArtistRefreshWikipediaData {:cancel, :no_english_wikipedia} worker path
- StatsComponents grouped on-this-day records and anniversary labels
2026-05-21 10:45:29 +01:00
Claudio Ortolina ff42f24972 ML-195: Add credo check for put_toast/put_toast! usage in live components 2026-05-21 08:08:11 +01:00
Claudio Ortolina 2983313426 ML-192: add high value missing tests
- Record editing + cover search
- Barcode scan errors + cart editing
- Notes
- Scrobble rules application over set of tracks
- Image conversion
2026-05-21 06:38:02 +01:00
Claudio Ortolina 23960b5b23 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
2026-05-20 18:04:29 +01:00
Claudio Ortolina f108e6f062 ML-174: add empty action to record set dropdown menu
Add RecordSets.empty_record_set/1 that bulk-deletes all items in a set via
a single DELETE query and returns the reloaded empty set. Add Empty button
with confirmation prompt to both index and show view dropdowns.
2026-05-14 17:15:06 +01:00
Claudio Ortolina a59dd22a18 ML-168: broadcast index_changed event after background import
Import workers now broadcast :records_index_changed on
"records:index_changed" after successful import via
Records.broadcast_index_changed/0.

CollectionLive.Index and WishlistLive.Index subscribe to the
topic in mount/3 and reload their record streams on receipt,
with a live_action guard to skip reloads when the grid is
hidden behind a modal (:import, :barcode_scan).

IndexActions.handle_index_changed/1 refreshes total_entries
before reloading to keep the pagination bar accurate.
2026-05-14 17:15:06 +01:00
Claudio Ortolina cccd28a9f9 ML-165: implementation 2026-05-05 13:43:18 +01:00
Claudio Ortolina ba2566828c Fix FTS5 crashes when searching with special characters
Always wrap FTS5 query terms in phrase double-quotes instead of
checking a hardcoded list of special characters. Characters like
|, ;, +, #, @ were missing from the list, causing FTS5 syntax
errors (production errors #1 and #1398). Phrase-quoting every term
produces identical results for normal ASCII search (verified via
SQL) while safely handling any character FTS5 chokes on.

Add regression tests for bare special characters and special
characters mixed with normal words.
2026-05-05 12:05:12 +01:00
Claudio Ortolina 7c6760d7db ML-158: force single-line log output in production
Three-layer architecture:
- Logster v2 handles HTTP request and LiveView socket telemetry in logfmt format
- Custom formatter as safety net escaping any remaining embedded newlines
- Config flag (single_line_logging) gating Logster attachment to prod only

Dev environment retains multi-line format for readability.
2026-05-04 15:01:10 +01:00
Claudio Ortolina 93ac86f748 fix(errors): LIKE wildcard escaping, memory-efficient counts, context tests
- Escape LIKE wildcards (% and _) in search queries using fragment ESCAPE
  to prevent accidental pattern expansion and potential DoS vectors
- Use Repo.aggregate for occurrence_count instead of loading all
  occurrences into memory in get_error/1
- Use Repo.aggregate(:min, :inserted_at) for first_occurrence_at instead
  of fragile List.last/1 that depends on query ordering
- Add MusicLibrary.ErrorsTest with 15 tests covering list_errors/1
  (default, status/muted/search filters, wildcard escaping, pagination,
  empty results) and get_error/1 (with/without occurrences, not_found)
2026-05-04 14:11:46 +01:00
Claudio Ortolina 0b78f24fed ML-155: Compact collection summary and add stats preamble 2026-05-02 07:11:18 +01:00
Claudio Ortolina ca5b13ad8d Remove some uses of List.last 2026-05-01 17:03:42 +01:00
Claudio Ortolina 3b558044b4 refactor: extract Records sub-contexts (Search, Import, Enrichment)
Split the 450+ line Records context into focused modules:

- Records.Search — FTS5 search, SearchParser integration, genre listing
- Records.Import — MusicBrainz release/group import, release status
- Records.Enrichment — genre population, cover refresh, color extraction,
  MusicBrainz data refresh
- Records.Query — shared helpers (essential_fields, order_alphabetically macro)

Records module is now a facade with defdelegate for compile-time safety.
CRUD and PubSub stay directly in Records. Tests moved alongside each
sub-module. Zero callers changed, 886 tests pass.
2026-04-30 17:20:37 +01:00
Claudio Ortolina 3849b338f3 Improve performance of job counting in maintenance
- Use a single query for all workers
- Replace the manual Ecto query against Oban.Job with Oban.Job.query/1
2026-04-30 14:54:56 +01:00
Claudio Ortolina 9113df5879 ML-146: honour API retry headers 2026-04-25 07:39:49 +01:00
Claudio Ortolina d588e5738f Remove redundant tests 2026-04-25 06:57:03 +01:00
Claudio Ortolina a1c665b490 ML-21: Classify API errors as transient vs permanent 2026-04-24 14:02:15 +01:00
Claudio Ortolina 0d1f580379 Refactor TracklistPdf to take release alone 2026-04-23 07:48:53 +01:00
Claudio Ortolina f7f38d580e Enable unaliased modules ex_slop check 2026-04-22 20:37:09 +01:00
Claudio Ortolina c4ebe636ff Fix ML-143 review: security, worker, and test improvements
Closes ML-143 review findings:
- cast_id/1 uses Integer.parse instead of String.to_integer
- add_to_cart validates against server-side release_groups
- JS.push payload trimmed to {id, format} only
- Oban.insert_all result checked for partial failures
- handle_async success resets @importing? to false
- ImportFromMusicbrainzReleaseGroup gets unique constraint
- Record.parse_datetime/1 made public with safe match
- Workers delegate to Record.parse_datetime/1
- Test coverage: change_format, clear_cart, wishlist single import
2026-04-20 15:15:29 +01:00
Claudio Ortolina 3f5ce060b1 Implement ML-143: Cart-style multi record import
Closes ML-143.
2026-04-20 15:15:29 +01:00
Claudio Ortolina 78487b0085 Pin Wikipedia intro_html to fixture value
Map.has_key? verified the key existed but not the content. The
fixture is deterministic — assert the stored value equals
Wikipedia.Fixtures.article_extract_html/0.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 07c99992a3 Pin barcode_scan release assertions
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 162c10e70a Pin fetch_artist_image asset lookup
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina bce8560ce8 Pin fetch_artist_info stored-data assertions
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 1f457f0f11 Pin prune_assets test to %Asset{hash: ^hash} match
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 6adfbea336 Pin Records.populate_genres error to OpenAI prefix
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 0ee0dcb118 Match decoded OpenAI error body, not raw JSON
Switch the 500-response stubs to Req.Test.json so Req decodes the
body, then match the map shape. Avoids coupling the assertion to
the exact JSON serialization.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 1429f7a826 Drop tautological delete.id checks
Repo.delete always returns the struct it was given, so deleted.id
== original.id is trivially true. Thread deleted.id through the
trailing get_*! call instead, so the binding is load-bearing.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina bb260437a1 Pin populate_genres error to OpenAI prefix
Match the string prefix "OpenAI API error:" that propagates from
OpenAI.gpt on non-2xx responses.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina a841ccf8e8 Tighten generate_record_embedding assertions
Assert stored embedding text matches the representation helper.
Pin the OpenAI-500 error path to the raw JSON body string.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina bbe0d5cd9e Pin artist_refresh_discogs_data assertions
Bind the returned ArtistInfo. For the no-discogs branch, assert
discogs_data was not touched.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina f9e3e8c5f8 Pin artist_refresh_wikipedia_data assertions
Bind perform_job results and assert id equality. For the unchanged case,
also assert wikipedia_data is the same as before the job ran.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 914fb4fbee Match specific errors on scrobble_rules duplicate
Pin the duplicate-rule error to the match_value field and "has already been
taken" message, matching the unique_constraint error_key in the changeset.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 9e29de55aa Pin online_store_templates delete assertion
Assert deleted.id == template.id rather than wildcard-matching the
returned struct.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina a90fdcf82f Tighten chats assertions
Pin invalid-attrs error to specific field, assert collection UUID round-trips
through Ecto.UUID.cast unchanged, and assert deleted.id == chat.id on delete.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 66c36f4d09 Tighten record_sets delete and duplicate assertions
Assert deleted.id == set.id on delete, and pin the duplicate error to the
specific field and message from the unique_constraint on record_set_id.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 60e95804ad Tighten similarity test assertions
Bind returned embeddings and assert record_id / text_representation match.
Pin OpenAI error path to the actual raw string body returned by the stub.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina e0fe5051ec Pin asset dedup assertion to same-hash
The duplicate prevention test asserted `{:ok, _}` twice without checking
that both calls returned the same asset. Assert hash equality.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina ae036e79fb Tighten scrobble_activity assertions
Replace 9 `{:ok, _}` sites with specific response-body checks to verify
the context returns the Last.fm API payload and not just the success branch.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina beaf38fcfe Add record_release tests 2026-04-19 16:11:32 +01:00
Claudio Ortolina 45236e4b10 Convert ErrorIgnorer tests to doctests 2026-04-19 07:50:39 +01:00
Claudio Ortolina 22a47f4ca9 Add tests for previously uncovered modules 2026-04-19 07:47:20 +01:00
Claudio Ortolina 4c5a3393e2 Run query reporter tests with async false
Avoid getting spurious failures due to interference by other tests
2026-04-17 10:29:43 +01:00