Skip to content

fix(library): seed a default quality profile and reject invalid profile ids; profile-less adds were silently never searched#760

Open
t3chnaztea wants to merge 1 commit into
Listenarrs:canaryfrom
t3chnaztea:fix/seed-default-quality-profile
Open

fix(library): seed a default quality profile and reject invalid profile ids; profile-less adds were silently never searched#760
t3chnaztea wants to merge 1 commit into
Listenarrs:canaryfrom
t3chnaztea:fix/seed-default-quality-profile

Conversation

@t3chnaztea

Copy link
Copy Markdown

The bug

Three compounding gaps meant a monitored book on a fresh install could silently never grab anything, with no warning anywhere:

  1. No default quality profile is ever seeded — QualityProfileRepository.GetDefaultAsync returns null on a fresh database (there is no seed data repo-wide).
  2. Adds proceeded anyway: both the production add path (LibraryAddService) and the workflow fallback (LibraryAddWorkflow) logged a warning and persisted the audiobook with QualityProfileId = null; a caller-supplied profile id was assigned without checking it exists.
  3. The automatic-search query filters QualityProfileId != null, so those books are never searched — and nothing ever surfaces that.

The fix

  • Seed on startup: StartupDbNormalizer (the existing startup-normalization hosted service) now seeds one permissive "Any Quality" default profile when the QualityProfiles table is empty. Idempotent (AnyAsync gate); the shape matches what the UI creates (built on the required-qualities set that EnsureProfileHasRequiredQualitiesAsync back-fills anyway). The normalizer's JSON-column pass and the seed pass are now independently guarded, so a normalization error can't swallow the seed.
  • Validate on add: in LibraryAddService (and the workflow fallback, which had the identical bug), a supplied profile id that doesn't exist rejects the add with a 400 and a clear message, before any side effects (image move/persistence); no supplied id resolves the default, and if somehow none exists the add is rejected instead of persisting a book that will never be searched.

Out of scope, flagged for maintainers: rows already persisted with QualityProfileId = null on existing installs still need manual assignment (or a follow-up normalizer) before automatic search will see them. The search query itself is deliberately untouched.

Tests (7 new)

  • QualityProfileSeedTests: empty table seeds exactly one default; running twice stays at one; non-empty table seeds nothing.
  • StartupDbNormalizerSeedTests: fresh DB via the hosted service seeds exactly one; idempotent on re-run.
  • LibraryController_QualityProfileValidationTests: bogus profile id → 400, nothing persisted; no profile id → default assigned, persisted non-null.
  • Two existing fixtures that relied on null-profile adds being accepted now seed the default profile in setup.

Full suite green: 1196 passed / 0 failed; dotnet format --verify-no-changes clean.

🤖 Generated with Claude Code

…le ids

Monitored audiobooks silently never grabbed on fresh installs. Three
compounding gaps: no default quality profile was ever seeded, so
GetDefaultAsync returned null; the add path then persisted the audiobook
with QualityProfileId = null anyway (and accepted a caller-supplied id
without checking it exists); and the automatic-search query filters
QualityProfileId != null, so those books were never searched and nothing
surfaced the problem.

- Seed a permissive "Any Quality" default profile at startup when the
  QualityProfiles table is empty, via the existing StartupDbNormalizer
  hosted service (always registered, even when other hosted services are
  disabled). Idempotent: only seeds when no profile exists. Normalization
  and seeding are now guarded independently so a normalization failure no
  longer skips the seed. Shape built by QualityProfile.CreateDefault().
- Reject the add when a supplied QualityProfileId does not exist, or when
  no profile is supplied and no default can be resolved, instead of
  persisting a null-profile row. Surfaced as a 400 (BadRequest) through
  LibraryAddWorkflow; the primary LibraryAddService path reports this via
  a new LibraryAddOperationResult.Rejected flag, validated before any
  persistence-side work.

The automatic-search query (QualityProfileId != null) is unchanged: with
seeding + rejection the null-profile state can no longer arise for new
adds. Existing rows already written with a null quality profile still
need manual profile assignment (or a follow-up normalizer) before they
will be searched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3chnaztea
t3chnaztea requested a review from a team July 13, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant