Skip to content

Image performance optimizations & API hardening#553

Open
therobbiedavis wants to merge 72 commits into
canaryfrom
bugfix/audiobook-images
Open

Image performance optimizations & API hardening#553
therobbiedavis wants to merge 72 commits into
canaryfrom
bugfix/audiobook-images

Conversation

@therobbiedavis
Copy link
Copy Markdown
Collaborator

@therobbiedavis therobbiedavis commented Apr 28, 2026

Summary

This PR cleans up audiobook image loading, moves browser auth to HttpOnly cookie sessions, tightens configuration/API key access rules, slims the library API payload, and improves UI navigation responsiveness.

The frontend now uses direct backend image URLs instead of image tokens or blob/object URL plumbing. Audiobook, author, series, collection, wanted, and modal image paths all go through the simpler protected image helper, with lazy-loaded audiobook covers using shimmer placeholders.

The library list endpoint is now significantly leaner: the backend loads audiobooks without file includes, fetches compact format summaries and per-audiobook file counts in parallel, and returns a slim payload. This reduces memory and I/O on large libraries.

UI navigation is snappier: clicking a sidebar link immediately marks it as active (optimistic pendingNavPath) without waiting for async route guards to resolve, route transitions use a short page-fade to avoid blank flashes, and the startup-config cache window is extended to 30 s to reduce per-navigation waits.

It also updates the Add New search flow so searches only run when submitted, improves keyboard behavior, adds a public bootstrap config endpoint for pre-auth SPA startup, and fixes secret redaction/API key management rules so public remote callers cannot read unredacted secrets when auth is disabled.

This branch also includes cross-platform git hooks for local quality enforcement (pre-commit: lint-staged + layering + async void; pre-push: version sync + backend format + frontend type check + frontend tests) and runtime/CI maintenance for Docker image scanning and build notification behavior.

Changes

Added

  • Public startup bootstrap config endpoint for safe pre-auth SPA initialization
  • StartupBootstrapConfig model
  • Cookie-session auth tests and frontend auth store tests
  • API image URL and image-cache tests
  • API key management access tests
  • AppPathService for shared app/config/cache path resolution
  • RequireAdminOrApiKeyWhenAuthenticationEnabled filter
  • RequireAdministratorSessionWhenAuthenticationEnabled filter
  • /system/ready endpoint for local tooling and dev server readiness checks
  • Audiobook shimmer placeholders while images lazy load
  • IAudiobookRepository.GetAllNoFilesAsync — loads audiobooks without eagerly loading files
  • IAudiobookFileRepository.GetFormatSummariesAsync — compact per-audiobook format summaries
  • IAudiobookFileRepository.GetCountsByAudiobookIdAsync — file counts by audiobook id
  • Optimistic sidebar active state (pendingNavPath) set on click, cleared after navigation resolves
  • Page-fade transition on route changes (old view leaves instantly; new view fades in)
  • Additional AddNewView tests: no auto-search on typing, Enter submission, keyboard accessibility, advanced-field Enter submission
  • Cross-platform pre-commit hook: Windows Node PATH fix, lint-staged, layering violation checks, async void enforcement
  • Cross-platform pre-push hook: FE version sync, dotnet format verify, vue-tsc type check, vitest run (no npm run, no .bin wrappers)
  • Vue SFC parse error reporting in check-vue-template-handlers.mjs

Changed

  • Switched browser session auth from readable bearer/session tokens to HttpOnly cookies
  • Simplified protected image handling to direct backend URLs with cookie auth
  • Updated startup config loading to use public bootstrap data before full authenticated config
  • Updated SignalR reconnect behavior around cookie-based browser auth
  • Restored public-remote config secret redaction based on caller trust, independent of auth-enabled state
  • Restricted API key read/regenerate access to admin sessions when auth is enabled, or local/private-network callers when auth is disabled
  • Made Prowlarr compatibility endpoints require API-key auth when authentication is enabled
  • Updated Add New search to submit explicitly instead of auto-searching while typing
  • Made Add New simple and advanced search fields submit on Enter
  • Streamlined author/audiobook/series image usage across library, collection, wanted, detail, and modal views
  • Improved image cache download validation with content-type, extension, redirect, DNS/private IP, and size checks
  • Updated app/config/cache path usage in ffmpeg, startup config, system, and image services
  • LibraryController now parallelizes file-summary/count/download lookups and uses counts for FileCount
  • Extended startup-config cache window to 30 s to reduce per-navigation waits
  • AudiobooksView virtual scroller: responsive items-per-row calculation, measured row height sync, resets visible range on grouping change
  • WantedView relies on libraryStore.loading instead of issuing a redundant library fetch
  • AuthenticationSection emits update:apiKey directly instead of wrapping in StartupConfig
  • Frontend format:check and format scripts now also run the vue-handler template checker
  • Updated Docker runtime package patching for npm transitive CVEs and libcap2
  • Updated build-and-publish Discord notification logic to avoid duplicate notifications after reruns
  • Updated root npm run dev readiness check to use /system/ready
  • Updated Swagger auth docs for cookie sessions and API key management rules

Fixed

  • Prevented public/no-auth remote callers from receiving unredacted configuration secrets
  • Prevented API-key-authenticated callers from rotating the API key when authentication is enabled
  • Prevented direct image requests from depending on frontend-readable image/session tokens
  • Fixed image loading scalability by removing frontend blob URL/object URL lifecycle work
  • Fixed Add New search accessibility around tab flow and Enter submission
  • Fixed stale frontend auth marker handling after 401/403 responses
  • Fixed delayed sidebar active highlight during async guarded navigation (optimistic pending state)
  • Fixed blank flash between page transitions (page-fade transition)
  • Fixed Docker scanner noise around vulnerable bundled npm dependencies where possible
  • Fixed duplicate Discord release notifications on successful workflow reruns
  • Fixed git hooks failing under Windows Git clients where npm run invokes bash-only .bin wrapper scripts

Removed

  • Frontend image access token plumbing
  • Frontend blob/object URL image fetching
  • lazyLoad.ts custom image observer
  • Audiobook image loading spinner overlay, since shimmer now handles loading state
  • Add New auto-search debounce trigger
  • Add New unused load-more/results watcher leftovers
  • Bearer and X-Session-Token browser session auth paths
  • SignalR access_token session-token query plumbing
  • metadataUrlCache in-memory candidate URL cache (image URLs now carry source hint as a query param)
  • Legacy 'audiobooks''books' group query mapping (only valid group values are accepted)

Testing

  • npm run build
  • npm run test:unit -- --run
  • npm run test:unit -- --run src/__tests__/api.ensureImageCached.spec.ts src/__tests__/api.imageUrls.spec.ts src/__tests__/AddNewView.spec.ts
  • dotnet test tests/Listenarr.Api.Tests/Listenarr.Api.Tests.csproj --filter "ConfigurationControllerDownloadClientTests|SessionCookieAuthTests" --no-restore -v minimal
  • dotnet test tests/Listenarr.Api.Tests/Listenarr.Api.Tests.csproj --filter "LibraryController" --no-restore -v minimal
  • Pre-commit and pre-push hooks verified on Windows (Git for Windows) and Linux

@therobbiedavis therobbiedavis requested a review from a team April 28, 2026 12:33
@therobbiedavis therobbiedavis added the major major version bump - incompatible API changes label Apr 28, 2026
Comment thread tests/Listenarr.Api.Tests/SessionCookieAuthTests.cs Fixed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a152f5b016

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fe/src/stores/auth.ts
Comment thread tests/Listenarr.Api.Tests/SessionCookieAuthTests.cs Fixed
Comment thread listenarr.api/Services/ImageCacheService.cs Fixed
Comment thread listenarr.api/Services/SecurityRequestUtils.cs Fixed
Comment thread listenarr.api/Services/AppPathService.cs Fixed
@therobbiedavis therobbiedavis requested a review from T4g1 April 28, 2026 17:40
Comment thread listenarr.api/Services/AppPathService.cs Fixed
Copy link
Copy Markdown
Contributor

@T4g1 T4g1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like i said, i did not process the frontend part as this is not my area of expertise, but i might look into it later this week if i get the time

Comment thread listenarr.api/Controllers/AccountController.cs Outdated
Comment thread listenarr.api/Controllers/ConfigurationController.cs Outdated
Comment thread listenarr.api/Controllers/ConfigurationController.cs Outdated
Comment thread listenarr.api/Controllers/ConfigurationController.cs Outdated
Comment thread listenarr.api/Controllers/ConfigurationController.cs Outdated
Comment thread listenarr.application/Common/ImageCacheService.cs Outdated
Comment thread listenarr.api/Services/ImageCacheService.cs Outdated
Comment thread listenarr.api/Services/SecurityRequestUtils.cs Outdated
Comment thread listenarr.api/Services/SecurityRequestUtils.cs Outdated
Comment thread listenarr.domain/Models/StartupBootstrapConfig.cs Outdated
Copilot AI review requested due to automatic review settings May 1, 2026 12:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@therobbiedavis therobbiedavis force-pushed the bugfix/audiobook-images branch from 9592070 to 4c26deb Compare May 1, 2026 17:31
@therobbiedavis therobbiedavis changed the title Image performace optimizations & API hardening Image performance optimizations & API hardening May 2, 2026
@therobbiedavis therobbiedavis requested review from a team and T4g1 and removed request for T4g1 May 2, 2026 03:44
@therobbiedavis therobbiedavis force-pushed the bugfix/audiobook-images branch from 8614967 to 23e16cc Compare May 10, 2026 11:55
@therobbiedavis therobbiedavis requested a review from T4g1 May 13, 2026 17:33
kevinheneveld pushed a commit to kevinheneveld/Listenarr that referenced this pull request May 14, 2026
The committed script assumed (a) docker on Clyde, (b) kevin@media.local SSH,
and (c) the docker compose plugin — none of which match this environment.
Three real divergences captured by Clyde's deploy procedure write-up:

- Build host. Clyde has no docker binary; image is built on media. Script
  now rsyncs the working tree to /root/listenarr-build/listenarr-src/ on
  media (override via BUILD_DIR_REMOTE) and runs `docker build` there.
  The old docker save | ssh ... docker load transfer step is gone.
- SSH target. Defaults to the `media` ssh alias from ~/.ssh/config
  (root@192.168.1.35), override via MEDIA_SSH. Health-check curls use
  MEDIA_IP (default 192.168.1.35) since `media` isn't a public DNS name.
- Compose binary. Restart step now uses the standalone `docker-compose`
  v2.x binary that media actually has, not the `docker compose` plugin.
  The script's printed rollback hint uses the same.

Also: the backup check now matches the timestamped pattern (config.bak.*)
that's actually used, not the bare config.bak it looked for before.
smoke-test.sh's "check container logs" hint now says `ssh media` instead
of `ssh kevin@media.local`.

Added a closing note about browser-loading SPA changes — HTTP smoke is
necessary but not sufficient for JS module-init failures; this is the
trap that bit us when we accidentally deployed kevin/live with PR Listenarrs#553
merged in.
@therobbiedavis therobbiedavis force-pushed the bugfix/audiobook-images branch from 23e16cc to 7faf429 Compare May 15, 2026 18:40
Introduce short-lived image-only access tokens and client/server plumbing to allow direct, signed backend image URLs without requiring cookie/bearer auth. Changes include:

- New backend ImageAccessTokenService (IDataProtector-based tokens) and tests (ImageAccessTokenServiceTests).
- AccountController: return imageToken on login and add GET /account/image-token endpoint for issuing per-user tokens.
- SessionAuthenticationMiddleware: accept image token via ?t= on /api/images/* and authenticate that request principal when valid.
- Image serving: set Cache-Control header for cached images and use resolved content-root paths (IAppPathService injection used where applicable).
- LibraryController, FfmpegController, DiscordController: add optional IAppPathService usage and resolve paths via content root instead of Directory.GetCurrentDirectory().
- ImageCacheService: use content root path for static asset lookups.
- SystemController: add /system/ready readiness endpoint (AllowAnonymous).

Frontend changes (fe/src):
- ApiService: manage image access token lifecycle (fetch, cache, expiry), attach token as ?t= to backend image URLs, avoid blob-fetch path for signed backend images, add blob URL LRU cache and limited concurrency for image blob fetching, expose clear/get helpers used in tests.
- main.ts: prefetch image access token at startup alongside antiforgery token.
- Add frontend Vitest tests for image token flows (api.imageAccessToken.spec.ts).

Tests:
- Update SessionCookieAuthTests to cover image-token endpoint, image access with token, non-image endpoint protection, and readiness probe.

Overall this enables safer direct image URL usage (query-string signed tokens), reduces unnecessary blob-ification for same-origin signed images, and improves client image caching/concurrency behavior.
CI: Add a step that checks prior run attempts via the GitHub CLI and sets an already_notified output; update the Discord notification condition to run only on the first attempt or when no prior attempt succeeded to avoid duplicate messages.

Dockerfiles: install libcap2 (addresses CVE-2026-4878) and extend the npm-based hotpatching to include brace-expansion v5 in addition to v2, while keeping the picomatch patch. Tarballs are cleaned up after extraction. Changes applied to both the repository root Dockerfile and listenarr.api/Dockerfile.runtime to mitigate CVE-2026-33671 and CVE-2026-33750 and pull the patched OS package.
Introduce IAppPathService / AppPathService to centralize app paths (content, config, logs, ffmpeg, tools, wwwroot) and resolve paths relative to the host content root. Update FfmpegInstallerService and SystemService to use the new service instead of AppContext/CurrentDirectory, and register IAppPathService (and ImageAccessTokenService) in DI. Adjust Listenarr.Api.csproj to stop copying config\appsettings files to output/publish so local dev config remains in listenarr.api/config. Update package.json dev script to wait for the /api/v1/system/ready endpoint instead of /api/v1/system/health.
Migrate frontend and API client to rely on HttpOnly session cookies and a bootstrap config endpoint instead of embedding bearer/image tokens. ApiService: add getBootstrapConfig, simplify getImageUrl/fetchImageObjectUrl to return backend URLs directly, remove image access token caching/attachment logic, and streamline auth/antiforgery handling (session marker used for cross-tab sync). UI/composables: remove protected image blob-fetch flow and startup-config-dependent auth checks; update SystemLogs SignalR options and include credentials for log fetches. Tests and components: update mocks to include getBootstrapConfig, adjust AuthenticationSection to emit apiKey, add auth store tests, and update session-token/storage tests to assert sanitized cross-tab markers. Also include server-side support for bootstrap config and authorization middleware/filter changes to enforce admin or API key when authentication is enabled.
Remove custom image blob/object-url handling and lazy-loading utilities, and simplify image URL resolution to use backend image endpoints directly. useProtectedImages now delegates to apiService.getImageUrl and no longer tracks per-image cache keys, object URLs, or retry state. ApiService removes blob fetch, concurrency and metadataUrlCache logic; ensureImageCached now attempts the provided /images/{id}?url=... endpoint first and falls back to the base /images/{id} endpoint. UI changes: components no longer pass cache keys to getProtectedImageSrc and rely on the simplified API; lazyLoad util deleted and related lazy-loading hooks removed. Search UX: debounce/autosearch removed from useSearch, unified/advanced search got improved keyboard accessibility and explicit submit handling; AddNewView updated to use the composable APIs and handle results synchronously. Tests updated to match the new behavior (including renamed test file) and new unit tests added for search submit/accessibility and ensureImageCached behavior.
Enforce stricter access for API key operations: add a RequireAdministratorSessionWhenAuthenticationEnabled filter and a RequireApiKeyManagementAccess helper in ConfigurationController to require an administrator session when authentication is enabled, while allowing local/private-network callers when auth is disabled. Update SecurityRequestUtils.ShouldRedactSecretsForCaller to trust local/private requests for non-redaction. Update Swagger docs to reflect the new access rules. Add tests covering remote vs private access when authentication is disabled, rejection of API-key auth for these endpoints when auth is enabled, admin-session regeneration flow, and an antiforgery helper used by tests.
Comment thread listenarr.domain/Common/FileUtils.cs
Comment thread tests/Features/Api/Controllers/LibraryController_MoveTests.cs
Remove FileUtils.IsPathMissing and replace its usages with string.IsNullOrEmpty to simplify path emptiness checks. Update LibraryController to validate request.SourcePath/DestinationPath using string.IsNullOrEmpty, adjust IsPathInvalidForOs to avoid the null-forgiving operator, and simplify NormalizeStoredPath with null-coalescing assignment. Also remove the unit test that targeted IsPathMissing.
Refactor the MoveAudiobook test to use the BaseTests fixture and test DI instead of manually creating an in-memory DbContext and numerous mocks. Tests now use FileService for temp paths, save application settings with ApplicationSettingsBuilder, add an audiobook via _audiobookRepository, and obtain the controller from the test provider. Simplifies setup, preserves destination-path-whitespace assertions, and removes redundant mock move-queue verification.
Update CONTRIBUTING.md to clarify DbContext and test-host registration (recommend AddListenarrInfrastructure, ListenarrWebApplicationFactory and Program.Testing.cs/ApplyTestHostPatches), centralize HttpClient registration in listenarr.infrastructure, suggest IDownloadClientAdapterFactory for adapter resolution, and strengthen testing guidance (refer to tests/README.md and use builders). Also replace a manual Audiobook instantiation in LibraryController_MoveTests with AudiobookBuilder to improve test consistency and readability.
Move ImageCacheService from the Services feature to a new Cache feature (file and namespace rename). Update CONTRIBUTING.md to document the Cache folder and clarify where infrastructure implementations should live. Adjust InfrastructureServiceRegistrationExtensions to import the new Cache namespace. Relocate ImageCacheService tests from tests/Features/Api/Services to tests/Features/Infrastructure/Cache and update test imports accordingly (old test deleted, new test added). These changes reorganize code by feature/technology and update references to match the new structure.
Decouple DTO from domain model and centralize API version normalization. StartupConfigDto.FromStartupConfig now accepts primitive auth flag and api version instead of a StartupConfig instance. Controller GetBootstrapConfig is made synchronous and uses the startup config service's IsAuthenticationRequired/GetEffectiveApiVersion. StartupConfigService.GetEffectiveApiVersion now delegates to NormalizeApiVersion, and StartupConfig.IsAuthenticationEnabled was simplified (GetEffectiveApiVersion and related static helper removed). Tests and mocks updated to use ApiVersionNormalizer from Listenarr.Domain.Common.
Add AudiobookBuilder.WithSeriesNumber and refactor LibraryController_BasePathTests to use the builder. Tests now declare root/path and pattern constants, retrieve the private ComputeAudiobookBaseDirectoryFromPattern via reflection, and use a CreateController helper to simplify controller construction. Mocking of IFileNamingService is simplified and expectations condensed; assertions use Path.Join with the shared RootPath. Overall this cleans up test setup and removes duplicated controller/mock instantiation.
Annotate infrastructure test classes and methods with xUnit [Trait] metadata for improved test organization and filtering. Updated files:
- tests/Features/Infrastructure/Platform/ApplicationVersionServiceTests.cs
- tests/Features/Infrastructure/Platform/SystemServiceVersionTests.cs
- tests/Features/Infrastructure/Services/ApplicationPathServiceTests.cs

Each class now includes Area/Name/Category traits and several tests include Method and Scenario traits. These changes are metadata-only and do not alter test logic.
Switch tests to use builders and shared test utilities for clarity and reuse. Added AudiobookBuilder.WithFilePath and replaced manual model construction with AudiobookBuilder/AudiobookFileBuilder usages. Tests now use FileService for temp directories, include Traits, follow Arrange/Act/Assert comments, and remove the fragile TryDeleteDirectory cleanup. CreateController was made an instance method and updated to use LibraryControllerMockFactory.CreateApplicationPathService(FileService.GetTempPath()).
Enhance test builders and refactor LibraryController tests for consistency and reuse. AudiobookBuilder now initializes Authors/Genres/AuthorAsins and adds fluent With... helpers (ImageUrl, Monitored, Description, Subtitle, FileSize, OpenLibraryId, WithGenre, WithAuthorAsin). AudiobookFileBuilder adds WithSize and WithFormat. Tests were updated to use these builders, added Trait attributes, replaced Arrange/Act/Assert comments with Given/When/Then, and switched to shared test utilities (LibraryControllerMockFactory/FileService) for application path and temp directories. Some fragile cleanup and ad-hoc object construction were removed in favor of builder usage and stable temp-path helpers.
Replace ad-hoc test setup with shared BaseTests, builders and mock factories across many tests. Added fluent methods to DownloadBuilder (Title, Artist, Series, AudiobookId) and updated tests to use DownloadBuilder, AudiobookBuilder, AudiobookFileBuilder and ApplicationSettingsBuilder. Consolidated controller construction into CreateController helpers, added Trait attributes to tests, removed direct in-memory DbContext/service provider setups, and streamlined assertions and arrange/act/then comments for clarity.
Replace verbose inline Download construction and manual IAudiobookFileRepository setup with reusable helpers. The test now uses DownloadBuilder to add a download and LibraryControllerMockFactory.CreateAudiobookFileRepository(allFiles) to create the mocked file repo, improving readability and reducing duplication while preserving existing behavior (e.g. DownloadStatus.Downloading).
Replace FileService.GetTempDirectory("ffmpeg") with Path.Combine(FileService.GetTempPath(), "ffmpeg") and remove the explicit Directory.Delete cleanup and its try/catch. The test now asserts the ffmpeg directory does not exist initially and verifies ffprobe/ffmpeg paths after installation without deleting the temp directory.
Comment thread tests/Features/Api/Services/FfmpegServiceTests.cs Dismissed
Introduce NormalizeOrDefault in ApiVersionNormalizer to return a default version when normalization yields null, and update ApiVersionUtils to call this new helper. Removed the redundant NormalizeApiVersionString wrapper in the application layer and replaced its usages with ApiVersionNormalizer.NormalizeOrDefault to centralize defaulting behavior. Changes touch listenarr.domain/Common/ApiVersionNormalizer.cs and listenarr.application/Common/ApiVersionUtils.cs.
Add an optional ILogger parameter to ResolveApiVersion and import Microsoft.Extensions.Logging. Replace System.Diagnostics.Debug.WriteLine calls with logger?.LogWarning to emit structured warnings on route/path parse failures. The logger is optional (defaults to null) so the method remains backwards-compatible while improving diagnostics.
Register ImagesController in the test service collection and refactor ImagesController_ContentRootResolutionTests to use DI-provided controller and shared mocks. The test now creates a temporary content root, injects mocked IImageCacheService and IApplicationPathService (returning the temp root) during InitializeAsync, and obtains the controller from the test service provider. This simplifies setup by removing ad-hoc mock/instance creation in the test and ensures the controller resolves its effective content root from IApplicationPathService.
Rename file to Models/Configurations/FfmpegConfig.cs and update the class namespace from Listenarr.Domain.Models to Listenarr.Domain.Models.Configurations to match the new folder structure. No other changes to the class implementation.
@therobbiedavis therobbiedavis requested a review from T4g1 May 23, 2026 03:35
Comment thread tests/Mocks/Api/LibraryControllerMockFactory.cs Outdated
Switch ImageCacheService to accept a HttpClient directly and register it as a typed HttpClient. Removed the ImageCacheHttpClientNames constant and IHttpClientFactory usage, and updated the DI registration to services.AddHttpClient<ImageCacheService>() with the same handler configuration. Tests were adjusted to pass a real HttpClient instance instead of mocking IHttpClientFactory and to look up HttpClientFactoryOptions by the typed client name. This simplifies DI and enables typed client configuration for the image cache service.
Return early before calling UpdateAsync when the download does not exist, preventing silent recreation of deleted or never-persisted downloads. Drop the previousStatus intermediate variable since the direct null check on previous is sufficient.
Rename AudiobookFileFormatSummary to AudiobookFormatSummary and update all usages. Updated file name and type references across the application and infrastructure layers (LibraryListService, IAudiobookFileRepository, EfAudiobookFileRepository, AudiobookStatusEvaluator) and adjusted tests/mocks to use the new type to keep naming consistent.
Use Path.IsPathRooted exclusively when validating relative path segments and remove the redundant IsWindowsDriveRootedPath helper. This simplifies the combine logic and eliminates an extra, unnecessary root-detection implementation while preserving the existing behavior of throwing for rooted segments.
Update test service setup to use DI-provided application/path and library services. ServiceCollectionBuilder.Build now accepts an optional contentRootPath and forwards it to AddListenarrInfrastructure. BaseTests exposes IApplicationPathService and initializes the service collection with FileService.GetTempPath(). Multiple controller tests now resolve IApplicationPathService and ILibraryListService from the test provider instead of using LibraryControllerMockFactory; tests were adapted to use real repositories/services where appropriate (notably LibraryListSlimPayload). Removed the now-unused tests/Mocks/Api/LibraryControllerMockFactory.cs.
Call services.AddLogging() in the tests' ServiceCollectionBuilder so logging services (ILogger, etc.) are available during tests. This prevents null logger issues and supports components that depend on logging when building the test DI container.
Copy link
Copy Markdown
Contributor

@T4g1 T4g1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two remarks left for the tests, but they are non blocking

}

public ServiceCollection Build()
public ServiceCollection Build(string? contentRootPath = null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be another method WithContentRootPath to define the content root path to use when calling the build method instead if we want to respect the builder pattern

Comment thread tests/Common/BaseTests.cs Outdated
public void Init()
{
_services ??= new ServiceCollectionBuilder().Build();
_services ??= new ServiceCollectionBuilder().Build(FileService.GetTempPath());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add an optional parameter to the method that takes the content root path and calls ServiceCollectionBuilder() with a WithContentRootPath if a path is given, that way we can keep an empty Init() method and you can properly handle the content root path for the tests here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we dont even need to change the method Init() and can just give it WithContentRootPath(FileService.GetTempPath()) every time

To be able to chain them, WithContentRootPath should return the ServiceCollectionBuilder with return this (see how it's done in the others ...Builder.cs)

typeof(LibraryController).GetMethod("ComputeAudiobookBaseDirectoryFromPattern",
BindingFlags.NonPublic | BindingFlags.Instance)!;

private LibraryController CreateController(Mock<IFileNamingService> fileNamingService) =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is optional but here, i believe you could replace this entire method with a _provider.GetRequiredService() (provided you add it in the ServiceCollectionBuilder)

This applies for every CreateController method you defined. The main advantage is that you no longer need to update the tests setup if you change the constructor of LibraryController, add a service, rename one, and so on

Centralize DI registration by moving IAudiobookMetadataService from listenarr.api/Program.cs into the infrastructure extension. Removed the AddScoped call from Program.cs and added services.AddScoped<IAudiobookMetadataService, AudiobookMetadataService>() in listenarr.infrastructure/Extensions/AppServiceRegistrationExtensions.cs to keep service registrations consolidated.
Introduce a private _contentRootPath and a fluent WithContentRootPath(string?) setter on ServiceCollectionBuilder, and change Build() to use the stored value instead of accepting a parameter. Update BaseTests to use the new fluent API (WithContentRootPath(...).Build()). This refactors how the content root path is provided to the test service builder for clearer, chainable construction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major major version bump - incompatible API changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants