deps: Bump Xunit.SkippableFact from 1.5.23 to 1.5.85 - #360
dependabot[bot] wants to merge 608 commits into
Conversation
…in/RicherTunes/Lidarr.Plugin.Common/dot-github/workflows/packaging-gates.yml-009c622e0a7165ad7d624b243d1bbab5a31b5761 deps(actions)(deps): bump RicherTunes/Lidarr.Plugin.Common/.github/workflows/packaging-gates.yml from b1781427f176d1b6e9a022960bdf6e5a8e3f6369 to 009c622e0a7165ad7d624b243d1bbab5a31b5761
Add DockerSmokeTests that mount the merged plugin DLL into a real Lidarr container and verify the Tidalarr indexer registers in the API schema. Tests skip gracefully when Docker is unavailable via [SkippableFact].
test: Docker smoke test infrastructure
…lowing The indexer's SearchAlbumsAsync, SearchTracksInternalAsync, GetAlbumDetailsInternalAsync, and SearchEnhancedAsync methods caught exceptions, reported them via bridge reporters, then returned empty results. This silently hid failures from callers — Lidarr would show zero results with no indication that an error occurred. After this change, all four methods still report errors through the bridge reporters (IIndexerStatusReporter) but rethrow instead of returning empty collections. This lets Lidarr surface auth failures, rate limits, and API errors to users. The happy path is unchanged. GetAlbumDetailsInternalAsync also gains bridge reporter calls in its catch block (previously only logged) and SearchEnhancedAsync gains the same reporter integration for consistency.
fix: stop silently swallowing exceptions in indexer search methods
chore(submodule): bump Common to v1.7.0
- OAuthTokenProviderAdapter: replace bare catch blocks with NLog warnings that capture the exception, aiding auth failure diagnosis - CLAUDE.md / TFM_RATIONALE.md: update stale net6.0 references to net8.0 to match the actual target framework - TidalSearchService: replace hardcoded "US" market with configurable parameter (default "US"); TidalIndexer now passes Settings.TidalMarket
fix: wave 1 hardening — OAuth logging, net8 docs, market config
…ceCollection Expand test coverage for three utility classes: - ConfigPathDefaultsTests: +5 tests covering HOME fallback, last-resort default, constant value, and multi-app-name scenarios - PathValidationExtensionsTests: +4 tests covering whitespace-only input, paths with spaces, delegation parity with Common library, root-only paths, and typical music file paths - ServiceCollectionExtensionsTests: +6 new tests verifying TidalModule DI registration builds without errors, registers expected service descriptors (core, bridge defaults, shared library, HTTP client factories), and handles idempotent double-registration All 13 Wave2 tests pass. Full suite: 305 passed, 0 failed.
test: wave 2 — coverage for ConfigPathDefaults, PathValidation, ServiceCollection
- Add historical notice to 22 docs/ files that are iteration plans, proposals, or status snapshots from initial development. Each notice directs readers to CLAUDE.md for current guidance. - Fix TFM_RATIONALE.md: rewrite to reflect net8.0 (was incorrectly claiming net6.0 for core and net9.0 for CLI). - Fix CLAUDE.md: update Production Deployment path from net6.0 to net8.0. - Fix deployment-smoke-test.md: replace Lidarr 2.14.2.4786 (net6) reference with current net8 plugins-branch guidance. - Fix tidalarr-qobuzarr-architecture.md: add historical notice noting net6.0 claims are outdated. - Update TECH_DEBT_BACKLOG.md: note TidalResiliencePolicy has been removed from source; fix net6.0 refs; mark TFM item as done. Dead code check: - TidalResiliencePolicy: class removed from src/; only referenced in docs/scripts/test.trx artifacts. Noted in TECH_DEBT_BACKLOG for cleanup of remaining references. - FailOnIOTokenStore: actively used in TidalModule.cs and TidalOAuthService.cs — NOT dead code.
Take main's more comprehensive net8.0 content (includes CLI info and future multi-targeting plan), add the net6.0 warning and Docker image reference from our branch.
docs: wave 3 — audit stale docs, add historical notices
Add 13 new edge-case tests for HostBridge settings mapping: - Full round-trip tests for all three settings types (TidalarrHostSettings, TidalIndexerHostSettings, TidalDownloadClientHostSettings) verifying every field is faithfully mapped including boolean fields missed by original tests - Negative enum value (-1) fallback to Lossless default - Empty string pass-through for all string fields - Zero concurrency/delay boundary values - TidalIndexerHostSettings default constructor defaults - ToCoreObject extension method dispatch for all three types plus unknown type passthrough Remove stale TidalResiliencePolicy/Polly tech-debt issue from create-tech-debt-issues.ps1 — both TidalResiliencePolicy and Polly were already deleted from source.
test: wave 4 — HostBridge mapping edge cases + script cleanup
The plugin has targeted net8.0 since the Lidarr 3.x migration, but several scripts and docs still referenced net6.0 in code samples, issue titles, and build paths. This cleans up the stragglers so nothing misleads contributors.
fix: drop remaining net6.0 references
- Add 19 tests for OAuthTokenProviderAdapter covering constructor null guard, delegation to ITidalAuth, error swallowing (returns empty/false instead of throwing), cache lifecycle, and static properties - Add null guard to OAuthTokenProviderAdapter constructor (primary constructor field initializer) - Add 5 tests for TidalarrValidationCodes verifying uniqueness, TID- prefix convention, uppercase format, and expected code values - Add NLog PackageReference to test project (required at runtime since Tidalarr.csproj excludes NLog runtime assets for ILRepack) - Doc audit: all net6.0 references in Tidalarr-owned *.md files already have historical banners or are in warning context — no changes needed
test: wave 5 — OAuthTokenProvider tests, validation codes, doc audit
Add two new SkippableFact tests that verify the plugin can create indexer and download client instances after applying valid settings. This proves DI container resolution works through the ILRepack-merged DLL, not just that the lifecycle completes. - Plugin_CreateIndexerAsync_ReturnsNonNull - Plugin_CreateDownloadClientAsync_ReturnsNonNull_OrNull Both tests skip gracefully on ReflectionTypeLoadException in case the isolated ALC cannot resolve host dependencies (Finding 1). Currently all Runtime tests fail on Tidalarr due to missing host assemblies (FluentValidation 9.0.0.0, Lidarr.Core) in the sandbox — the Common-level fix will address this.
…rtions test: add capability assertions to runtime sandbox tests
…dStatusReporter) Updates ext/Lidarr.Plugin.Common from d41d2d4 to 62e1aff, picking up: - fix: harden PluginSandbox — findings 1, 2, and 5 (#448) - feat: add DefaultDownloadStatusReporter + register in AddBridgeDefaults (#449) - test: wave 4+5 compliance and coverage improvements (#446, #447) - chore: wave 1 tech debt cleanup (#443) - docs: README v1.7.0 update (#445)
…rdening chore(submodule): bump Common to latest (sandbox hardening)
…st catch narrowing - Add _settingsLock to synchronize _settings write + RebuildServiceProvider atomically in Apply(), ApplySettingsWithDiagnostics(), and Services getter - Refactor ApplySettingsWithDiagnostics to call MapToSettings once instead of twice (was delegating to ValidateSettingsWithDiagnostics which mapped, then mapping again) - Narrow bare catch in Manifest getter to FileNotFoundException and JsonException; log warning via NLog for unexpected exceptions
fix: thread safety on Apply + eliminate double MapToSettings + manifest catch narrowing
TidalSearchService accepted a market parameter but only used it for query optimizer context — the actual API call never received it. The Tidal API's countryCode query parameter controls market filtering and was always hardcoded to the OAuth token's country. Add a countryCode-aware SearchAsync overload to ITidalCore with a default interface implementation that delegates to the existing method (no existing implementors broken). TidalApiClient overrides it to pass countryCode to the API when non-null, falling back to the token's country. TidalSearchService now forwards the market parameter through both SearchWithQualityDetectionAsync and SearchByTypeAsync call sites.
fix: plumb market parameter through to Tidal search API
…rename, download logging - S3: Replace weak >= 4 assertions with exact Assert.Equal(16, ...) in PluginSandboxRuntimeTests for both Describe and GetDefaults - S6: Cache TidalModule as class-level field instead of recreating on every RebuildServiceProvider call - I1: Rename TidalarrHostSettings_ToCore_MapsAllFields to _MapsIndexerFields and add explicit assertion that BaseUrl is NOT mapped (documenting the intentional gap) - S4: Add ILogger-based debug logging to bare catch blocks in TidalChunkDownloader (file/directory delete) and AudioFormatHandler.ProcessAudioFileAsync (file delete cleanup)
chore: review suggestions — assertion strength, module caching, test rename, download logging
The Governance workflow's "Allowlist Expiration Check" fails on any .github/sha-pin-allowlist.json entry whose expiresOn is in the past. Both entries (multi-plugin-smoke-test.yml and single-plugin-e2e.yml, each expiresOn 2026-04-01) deferred SHA-pinning the Common reusable workflow until the smoke test stabilized. That transition is already complete: both .github/workflows/multi-plugin-smoke-test.yml and .github/workflows/single-plugin-e2e.yml already pin the Common reusable workflow to a full 40-hex SHA (@c2aca69b8c94bbba2ff9a4884291f0cc501eb623), so both allowlist entries are now orphaned. Remove them so the expiration check passes. The valid (2026-08-01) sync-over-async-allowlist entries are untouched.
… LidarrNative) (#319) * fix(ci): build hostless in Governance Quarantine Visibility job The "Restore and build" step built the full plugin WITHOUT -p:SkipHostBridge, yet the job prepares only STUB host assemblies. Stub mode excludes LidarrNative/** while TidalModule still references it -> CS0234, which has failed the Governance run (Quarantined Test Visibility) since at least 2026-05-25. The job only runs State=Quarantined unit tests (no host-bridge needed), so build hostless to match the test-project step (-p:SkipHostBridge=true -p:ExcludeHostBridge=true). * fix(ci): build Governance QTV job against real Lidarr host assemblies The "Quarantined Test Visibility" job prepared STUB host assemblies, which trip Directory.Build.props auto-detect into SkipHostBridge mode (no Lidarr.dll present) and exclude LidarrNative/**. TidalModule.cs imports the Tidalarr.Integration.LidarrNative namespace unconditionally, so stub mode fails with CS0234 — the plugin cannot build hostless. My prior attempt added -p:SkipHostBridge=true -p:ExcludeHostBridge=true, which excludes the folder while the dangling reference remained: still CS0234. Mirror the established real-host-assembly approach used by ci.yml, test-and-coverage.yml, and nightly.yml (and the fix/nightly-real-host-assemblies fix): replace the stub-prep step with the Docker-extract of ghcr.io/hotio/lidarr:pr-plugins-3.1.2.4913 into ext/Lidarr/_output/net8.0, then build the FULL plugin and test project (incl. the LidarrNative host-bridge) with no SkipHostBridge/ExcludeHostBridge. Drop -p:SkipHostBridge from the test-run step so build and run stay internally consistent (run is --no-build). Validated locally: full plugin + test project build with 0 errors against the extracted assemblies; `dotnet test --no-build --filter State=Quarantined` discovers cleanly (0 matches, exit 0). ---------
…ttings, clean markers (#321) Applied the verified path-to-99 checklist: corrected stale version/metric/ framework claims, removed changelog + wiki duplication, documented remaining settings/env-vars, cleaned TODO(docval)/phantom blocks, fixed dead links. Surgical, verified vs source, no code.
Re-pins ext/Lidarr.Plugin.Common to RicherTunes/Lidarr.Plugin.Common@ed3c27b (current main HEAD) to converge the streaming trio onto one Common SHA and satisfy the Ecosystem Pin Drift guard. Supersedes the stale bump to 79d2140 (#317). Additive forward bump (manifest parsers, hostbridge size-estimator seam, MSBuild deps.json-race fix); commonVersion stays 1.18.0-dev. Both the gitlink and ext-common-sha.txt now equal ed3c27b8edfd2a125665daf7b7d754b3ecf5c341.
…aseBuilder (#320) Consumer-side dedup, behavior-preserving. Replaces the hand-rolled release-size and per-quality release-construction logic in TidalLidarrParser with the shared Common helpers (Lidarr.Plugin.Common.HostBridge, added in Common #595): - ConvertToReleaseInfosStatic now drives MultiQualityReleaseBuilder (album-level fields set once; per-tier format/extra/quality + bitrate via AddQuality). The Tidal-specific bits stay local: the 4-tier quality ladder, DetermineTitleMarkers bracket tokens, and the per-quality bitrate. - EstimateAlbumSize delegates the duration*bitrate->bytes arithmetic to AlbumSizeEstimator.EstimateBytesFromBitrate; the Tidal duration ladder and bitrate-per-quality switch are extracted to BitrateKbpsForQuality / EstimateAlbumDurationSeconds (still local, still Tidal-specific). Double arithmetic in Common also removes the latent int*int*125 overflow on long hi-res albums. Byte-for-byte equivalent to the prior formulas (pinned by Common's equivalence tests + tidal's existing parser/size tests: 85 passed / 2 skipped locally). PIN-FREE BY DESIGN: this PR touches only the source file — no ext-common-sha.txt or submodule-gitlink change — so it does not collide with the open Common-bump PRs. It REQUIRES the Common pin to reach a SHA containing #595 (>= 4f44a79); CI stays red until the coordinating agent's lockstep repin lands, then this rebases green. Verified locally against Common 4f44a79.
…s DownloadPayloadValidator (#323) TidalDownloadPayloadValidator was a near-verbatim fork of Common's canonical DownloadPayloadValidator (same fLaC/OggS/RIFF/ID3/ftyp magic + text-payload rejection + ValidateOrThrow(span, ext, mime) signature). Delete the fork and call Common's validator directly at both download call sites (the file already imports Lidarr.Plugin.Common.Utilities). Common's is a strict superset (adds m4a min-header + JSON/XML detection + file overload), so behavior is preserved for Tidal's payloads. The existing characterization tests are repointed to DownloadPayloadValidator and renamed (TidalDownloadPayloadValidationTests). All four cases (HTML/JSON text reject, FLAC accept, ftyp/m4a accept, ftyp-as-flac reject) pass unchanged against Common's validator, proving the migration preserved behavior. Verified locally: full Tidalarr.Tests suite green (1306 passed, 0 failed, 17 skipped). First of the cross-plugin audio-payload-validator consolidation (qobuz/amazon next; a Common EcosystemParityTestBase guard lands once all forks are gone).
…#324) The wiki had only Home.md (which correctly defers to the README). This adds three Tidal-specific pages that document operational detail beyond the README, without duplicating it or Common's wiki — every claim is verified against the codebase: - Authentication.md — OAuth 2.0 + PKCE sign-in (UI & CLI), token storage/refresh (single-flight), region/market (US default; US/UK/DE/FR/CA/AU/JP), and the single-use-code error (TidalOAuthService / TidalInvalidGrantException). - Quality-and-Formats.md — quality tiers → API params + codecs (LOW/HIGH/LOSSLESS/HI_RES_LOSSLESS), chunked DASH/BTS assembly, FLAC extraction via FFmpeg stream-copy (default on, skipped if FFmpeg absent), ISRC tags via Common's TagLibAudioMetadataApplier, synced lyrics (.lrc via LRCLIB fallback). - Troubleshooting.md — typed exceptions mapped to cause + fix (auth-code reuse, empty/encrypted/unsupported manifests, rate limits, stream-unavailable, FFmpeg missing) with the exact error strings from the source, plus backend-health buckets (tidal:auth/api/cdn) and the *-validate CLI diagnostics. Home.md gains a navigation table to the new pages (keeps its canonical-source deferral to the README and the performance-tuning table). README's Documentation → Wiki table is updated to list all four pages. Verification note: confirmed the README's "writes ISRC tags" claim is TRUE — it is performed by Common's TagLibAudioMetadataApplier (TSRC/Xiph/MP4), which tidalarr inherits via BaseStreamingDownloadClient; left it unchanged. Docs-only; no code touched.
Re-verified vs origin/main: - docs/README.md + TFM_RATIONALE.md: CLI is net8.0, not net9.0 (TidalCLI.csproj:11). - TFM_RATIONALE.md + hostbridge-integration.md: removed "plus Common runtime" shipping claim — Common/Abstractions are ILRepack-merged + internalized into Lidarr.Plugin.Tidalarr.dll and are FORBIDDEN as sidecars (packaging/expected-contents.txt). - TECH_DEBT_BACKLOG.md: packaging allowlist no longer lists Lidarr.Plugin.Common.dll (it's merged/forbidden); allowed set is the plugin DLL + plugin.json. - hostbridge-integration.md: clarified ToCoreObject is an extension method on IHostSettingsMapper, not an interface member (HostSettingsMapper.cs:30). Note: the DISCOVER finding about TidalarrIndexerPlugin/TidalarrDownloadPlugin was a misread — the live migration checklist correctly references the *HostSettings types. Docs-only; no net deletions.
…rack result (#297) MED (harden campaign). MapToTidalTrackInfo threw InvalidOperationException when a track DTO had no nested `album`. It runs via .Select(...) over search results (MapToTidalTrackInfo over trackDtos) and album track lists, so a SINGLE album-less track threw mid-enumeration and discarded the ENTIRE result (all albums/tracks/ artists in a search; the whole track list for an album). The album-derived fields were already null-safe (dto.album?...), so the throw was the only fatal part — removed it; an album-less track now maps with empty album info. Updated the prior GetTrackAsync_NullAlbum_Throws test (which codified the harmful throw) and added an album-track batch regression test.
…b) (#326) Bumps ext/Lidarr.Plugin.Common from ed3c27b to current Common main HEAD 975f275d9ee76be965b57214decaa4c3cb9191cd. Additive forward bump — keeps the streaming trio (qobuz/tidal/apple) on the same Common pin so the cross-repo ecosystem-pin-drift guard stays green and shared-behavior fixes reach every plugin. New Common since ed3c27b includes the shared CENC sample decryptor (#601, NIST-verified), #567 (ConfidenceNotProvided triage reason), #568 (release VERSION stamping), #550/#554/#606 (CI flake/test fixes) and related docs. Build-sanity: verify-local.ps1 -SkipTests PASS (0 errors, 2 warnings; package + packaging-closure green). commonVersion stays 1.18.0-dev (Common's current version) so VersionContractTests remain in lockstep.
…cQueryOptimizer (#328) TidalSearchService has always accepted an optional IQueryOptimizer and, when present, runs an optimize -> search -> learn loop. Nothing ever registered the interface in DI, so the parameter resolved to null and the whole feedback loop was dead code. Register Common's dependency-free HeuristicQueryOptimizer (#611) as the IQueryOptimizer in TidalModule, lighting up that consumer. Safety: HeuristicQueryOptimizer.OptimizeQueryAsync sets OptimizedQuery.Query (the PRIMARY search string TidalSearchService actually searches and learns from) to the whitespace-normalized raw query only -- never a term-dropped rewrite. All recall-adding rewrites (edition-strip, featured-artist-drop, keyword-reduce) go into OptimizedQuery.Alternatives, which TidalSearchService does not fan out into. Net effect: the user-visible primary result path is unchanged (modulo benign whitespace collapse) while the learning loop becomes live. Live recall measurement deferred (needs Tidal creds). Re-pins the Common submodule to 83aa0491 (gitlink + ext-common-sha.txt) since #611 is newer than the prior pin (975f275d); adoption inherently bumps the pin. TDD: TidalModuleDiTests gains three tests -- one pinning the dead wire is now registered (HeuristicQueryOptimizer resolves), one that TidalSearchService resolves with the optimizer satisfiable, and one asserting the primary query preserves every essential token (the safety invariant). All 1305 non-Docker unit tests pass; packaging closure clean (0 forbidden refs).
…-flow (#329) A user could never re-authenticate Tidal — every attempt failed "OAuth state mismatch". Root cause: TidalLidarrIndexer.TryExchangeAuthorizationCode compared the pasted redirect's `state` against the stored PKCE state and, on any mismatch, called PKCEStateStore.RegenerateCodes() (minting a fresh code_verifier/code_challenge/state) before aborting. In a manual copy/paste OAuth flow the stored RedirectUrl is stale, so every indexer Test (Lidarr's periodic auto-tests + manual clicks) re-entered this path, mismatched, and regenerated — invalidating whatever auth URL the user had open before they could finish pasting the redirect. An unwinnable loop (live logs showed the OAuth URL `state` changing on every Test). The method's own comment documents the intent to SKIP state validation ("CSRF protection which isn't relevant in a manual copy/paste OAuth flow"), but the code contradicted it. Remove the state-mismatch validate+regenerate block so a mismatched-state callback proceeds straight to ExchangeCodeAsync using the STORED code_verifier. The pending PKCE state is now STABLE across Tests, so the real flow converges. PKCE code_verifier<->code_challenge security is fully preserved (the exchange always uses the stored verifier); the only thing dropped is the meaningless CSRF state check. Genuinely used/expired codes are still handled by the existing TidalInvalidGrantException (invalid_grant) path, and the post-SUCCESS RegenerateCodes is kept. ParseCallbackUrl still requires `state` to be present (Tidal always returns it). TDD: TidalLidarrIndexerOAuthStateExchangeTests drives the (now internal) TryExchangeAuthorizationCode against a real temp-dir PKCEStateStore with a mocked ITidalAuth. RED before the fix (mismatch returned false, never called ExchangeCodeAsync); GREEN after (mismatch proceeds and exchanges with the stored verifier). Full suite: 1255 passed / 7 skipped / 0 failed.
… the parity suite (#330) Tidalarr now pins a Common that ships Check_DownloadClientStampsRegisteredClientId (#598) and Check_DownloadClientUsesCommonPayloadValidator (#602). Wire both as [Fact]s so they actually run in CI and enforce the contracts going forward: - download-client-id: GetItems() stamps DownloadClientInfo.Id from Definition (never 0) — TidalLidarrDownloadClient uses DownloadClientItemClientInfo.FromDownloadClient(this, ...). - payload-validator: audio validation goes through Common's DownloadPayloadValidator — the local TidalDownloadPayloadValidator fork was removed in #323. Pin-free (no submodule bump); both guards already pass against current source.
…rolled docker extraction) (#332) tidal's CodeQL workflow hand-rolled an inline docker pull/create/cp of the Lidarr plugins-branch image with a HARDCODED tag (pr-plugins-3.1.2.4913), duplicating extraction logic that drifts from the rest of the pipeline. Replace it with the shared shim (scripts/extract-lidarr-assemblies.sh, which delegates to Common's superset extractor) plus the host-FluentValidation removal — matching qobuzarr's codeql.yml exactly. The Docker image tag is now sourced centrally by the shim, so it can no longer drift in this workflow.
* chore(submodule): converge Common pin to ed3c27b (current HEAD) Re-pins ext/Lidarr.Plugin.Common to RicherTunes/Lidarr.Plugin.Common@ed3c27b (current main HEAD) to converge the streaming trio onto one Common SHA and satisfy the Ecosystem Pin Drift guard. Supersedes the stale bump to 79d2140 (#317). Additive forward bump (manifest parsers, hostbridge size-estimator seam, MSBuild deps.json-race fix); commonVersion stays 1.18.0-dev. Both the gitlink and ext-common-sha.txt now equal ed3c27b8edfd2a125665daf7b7d754b3ecf5c341. * docs: point CLAUDE.md to Common's canonical ecosystem consolidation & parity discipline Adds a short pointer (not a restatement — consolidation applies to docs too) to ext/Lidarr.Plugin.Common/AGENTS.md 'Ecosystem Consolidation & Parity Discipline': find-once -> sweep-five across the 5 copy-paste-adjacent plugins, push shared logic into Common (plugins adopt it; the out-of-tree DRM seam stays plugin-owned+public), the parity matrix is a contract, verify architecture before assuming a bug class sweeps. * fix(tidal): SnapshotSettings dropped SaveSyncedLyrics + UseLRCLIB (cross-plugin field-drop sweep) Cross-plugin sweep of the SnapshotSettings field-drop bug class (found first in amazonmusicarr, where the host snapshot dropped EnableDrm and DRM never activated in background downloads). Tidal's inline snapshotter copied 8 of the settings' 10 read-write properties — it silently dropped SaveSyncedLyrics + UseLRCLIB, so the background download (which reads the pre-await snapshot) ignored the user's lyric settings and used defaults. The existing host->core parity test didn't catch it (it checks a different mapping, not the snapshotter). Extracted the snapshotter into an internal SnapshotSettings that reflection-copies EVERY read-write property — structurally cannot drop a field. Added a unit test pinning the full contract (a reflection sweep over all properties) + the two previously-dropped lyric fields. Sweep results: apple's SnapshotSettings already copies all 10 fields (and has a test) — not vulnerable; qobuz/brainarr have no such snapshot pattern. Candidate to consolidate into a Common helper later. Build 0-error; 187 download-area tests pass + 2 new snapshot tests. * fix(download): refuse output paths outside the download root (F-09) Adversarial review F-09 (MEDIUM): TidalDownloadClient's public write boundaries (DownloadTrackEnhancedAsync, DownloadTrackWithMetadataAsync) created directories and wrote/moved/deleted files based on a caller-provided outputPath without checking it stays inside the configured DownloadPath — a hostile-controlled path (e.g. via crafted filename/segment) could write outside the download root. Fix: add IsOutputPathAllowed (canonical-form containment via Common's PathTraversalGuard.IsPathWithinRoot — resolves "..", defends sibling-prefix + case-twin) and call EnsureOutputPathAllowed as the first statement in both methods, before any mkdir/write/move/delete/tag. The system temp dir is allowed as an alternate root because DownloadTrackAsync stages there before the host imports. Mirrors amazon's existing IsPathWithinRoot guards (parity). Sibling sweep of the "download write containment" class: amazon already guarded, qobuz cleanup is the separate F-10, apple is parallel-AI-owned, brainarr has no download client. Tests: TidalDownloadClientPathContainmentTests — 5 pure-helper cases + 2 wired integration tests (escape path refused with a download-path error, no file written). RED→GREEN; full download-client suite 99/99. * perf(download): stream enhanced track assembly to file, not whole-track MemoryStream (F-08) Adversarial review F-08 (MEDIUM): DownloadTrackEnhancedAsync assembled the full track via DownloadAndAssembleAsync(manifest), which buffers the entire decrypted track in a MemoryStream (File.ReadAllBytes + in-memory decrypt), then copied that to the output FileStream — pinning a large hi-res track's full size on the managed heap and double-buffering (disk → memory → disk). Fix: switch to the already-existing file-backed path DownloadAndAssembleToFileStreamAsync, which assembles to a temp file (DeleteOnClose), decrypts in place on the file stream, and returns a seekable FileStream. The downstream header-validate + copy logic is unchanged (works on any seekable stream). Sequential (maxConcurrency 1) to preserve this path's historical ordering contract. The MemoryStream overload stays — it remains public API and is covered by the chunk downloader's own tests; only the enhanced client path moves off it. Tidal-specific class (amazon/qobuz already stream to disk; apple parallel-AI-owned). Verified: Enhanced + ChunkDownloader suites 69/69, full Tidalarr.Tests 1315/1332 (17 pre-existing skips), 0 failures. * docs(lint): make sync-over-async allowlist semantic, drop stale line numbers (F-12) Identify allowlist entries by file + pattern + method instead of drift-prone line numbers (the linter matches by file/pattern and reports the accurate current line at scan time — the JSON 'line' field was redundant + stale, e.g. TidalLidarrIndexer listed 506 vs actual 596). Part of the cross-plugin F-12 sweep. * chore(common): converge Common pin to merged #618 SHA 961dfae (F-05) Adversarial review F-05 (HIGH): re-pin tidal's ext/Lidarr.Plugin.Common submodule + ext-common-sha.txt from ed3c27b to 961dfaea60710f61f885f101f3c233baf7a650cb (origin/main after security PR #618 merged). commonVersion stays 1.18.0-dev (matches merged Common — truthful). Adoption fix for F-01's strict-by-default SSRF guard: the merged Common makes ChunkedHttpAssembler SSRF-strict by default. TidalChunkDownloader now constructs it with RemoteMediaUriPolicy { AllowHttp = true, ResolveDns = false } — the guard's value for Tidal is blocking literal private/loopback/CGNAT/metadata-host targets (the real SSRF vectors). AllowHttp preserves the pre-#618 behaviour (Tidal manifests can serve http segment URLs; the assembler had no scheme restriction before), and ResolveDns is skipped on the hot path (manifest source is authenticated/trusted). The production orchestrator uses the stream-provider path, which doesn't hit the URL guard, so no change needed there. Verified against merged Common: download/chunk/orchestrator 160/160, full Tidalarr.Tests 1315/1332 (17 pre-existing skips), 0 failures. * test(fix): use synchronous IProgress in the file-stream progress test (de-flake) DownloadAndAssembleToFileStreamAsync_WithProgress_ReportsProgress used System.Progress<T>, whose callbacks marshal asynchronously with no ordering guarantee, so progressReports[0] could observe the count-2 report before count-1 (Assert.Equal Expected:1 Actual:2 — flaked in CI's Test-with-Coverage, passed locally). Switched to the file's existing SyncProgress<T> (synchronous, inline) which the other ordered-assert tests already use, making order + count deterministic. Pre-existing flake; not introduced by the F-05/F-08 work. ---------
Bumps ext/Lidarr.Plugin.Common + ext-common-sha.txt 961dfae→a398fc4 (origin/main after Wave-P2: #619 date-parse Retry-After InvariantCulture, #612 OAuth 401-retry body replay, #608 DASH malformed-manifest memory-exhaustion guard, #614 clamp negative Retry-After). #608 lands in tidal's DASH manifest path. commonVersion stays 1.18.0-dev. Manifest/chunk/ download suites 262/262 against the new Common.
…2-02) (#342) TidalChunkDownloader hardcoded RemoteMediaUriPolicy{AllowHttp=true, ResolveDns=false} for segment fetches — the ResolveDns=false was a Wave-P0 test-driven shortcut the Round-2 review flagged as weakening SSRF. AllowHttp=true is retained (Tidal serves http segment URLs); ResolveDns returns to its strict default (true) so a segment host that resolves to an internal address (rebinding / compromised CDN record) is rejected. ValidateChunkAccessibilityAsync issued a raw _httpClient.GetAsync(chunkUrls[0]) with no SSRF guard — now routed through Common's MediaRedirectSafeSender, which validates the target (and every redirect hop) against the policy before the fetch. The downloader takes an optional segmentPolicy; tests inject TidalTestPolicies.Resolving (AllowHttp=true + a deterministic DnsResolver) so synthetic non-resolving hosts classify as public — instead of weakening the production policy. Re-pins ext/Lidarr.Plugin.Common to 3f8c4df (DnsResolver seam + MediaRedirectSafeSender + label-boundary host-suffix). 1321 unit + 36 parity green.
…a plugin subdir (R2-11) (#343) IsOutputPathAllowed accepted any path under the system temp dir as a legitimate write root (besides the configured DownloadPath). %TEMP% is world-writable and shared across processes, so that allowed a caller-supplied outputPath to land anywhere in it. Narrow the alternate root to a plugin-owned %TEMP%/tidalarr subdir (PluginTempRoot), and stage GetTempFilePath there so the legitimate staging path stays inside the allowed root. Containment stays canonical-form (PathTraversalGuard), so the sibling-prefix case "%TEMP%/tidalarr_evil" is refused, not just "%TEMP%/other". TDD: under-subdir accepted; elsewhere-in-%TEMP% and sibling-prefix rejected. 51 download tests green.
…(LOOP-001) (#344) Advances the Common pin 3f8c4df → 9691bd8, adopting the security PRs that landed after the R2-02 pin: MediaRedirectSafeSender pre-send validation (#622), SafeDirectoryCleanup reparse-point refusal (#623), TimeParsing helpers (#624). Adds CommonPinDriftTests (parity): asserts ext-common-sha.txt equals the checked-out ext/Lidarr.Plugin.Common HEAD, so the sentinel and the Common actually built can't drift apart (the apple failure mode). Compares against the checked-out submodule HEAD (not the committed gitlink) so it holds in a dirty local re-pin and still catches a forgot-to-stage submodule in a clean CI checkout. Skips when git is unavailable. The #622 pre-send validation correctly rejects the stub host "https://chunk" in the DI end-to-end download-validation test (it doesn't resolve under the Strict guard); injected TidalTestPolicies.Resolving there to match the R2-02 test sweep. 1323 unit + 38 parity green (incl. the 2 new drift gates).
…brainarr parity) (#346) Replace the 104-line inline notify-failure body with the 35-line thin shim pattern brainarr already uses: keep the plugin-specific workflow_run trigger and monitored-workflows list (preserved verbatim), delegate the notification body to RicherTunes/Lidarr.Plugin.Common notify-failure.yml@workflows/v1. Safety analysis: the reusable already carries the secrets||vars webhook fallback at job-level env, and workflows/v1 (76bb178) is byte-identical to Common main for this file. No DISCORD_WEBHOOK/SLACK_WEBHOOK secret or variable exists in this repo (or org-level), so behavior today is the step-summary fallback in both old and new versions.
--- updated-dependencies: - dependency-name: Xunit.SkippableFact dependency-version: 1.5.85 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
AssigneesThe following users could not be added as assignees: LabelsThe following labels could not be found: Please fix the above issues or remove invalid values from |
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
Updated Xunit.SkippableFact from 1.5.23 to 1.5.85.
Release notes
Sourced from Xunit.SkippableFact's releases.
1.5.85
What's Changed
New Contributors
Full Changelog: AArnott/Xunit.SkippableFact@v1.5.61...v1.5.85
1.5.61
What's Changed
New Contributors
Full Changelog: AArnott/Xunit.SkippableFact@v1.5.23...v1.5.61
Commits viewable in compare view.
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)