From 90da359e58d9dba315d4f3027a0982e6703c3bb4 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:50:25 +0700 Subject: [PATCH] fix(download): prepare fallback sessions before availability checks --- go_backend/extension_fallback.go | 15 +++++++++++++-- .../extension_fallback_verification_test.go | 14 +++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/go_backend/extension_fallback.go b/go_backend/extension_fallback.go index 660971ce..f9d4f30c 100644 --- a/go_backend/extension_fallback.go +++ b/go_backend/extension_fallback.go @@ -690,7 +690,18 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro provider := newExtensionProviderWrapper(ext) - availability, err := provider.CheckAvailabilityForItemID(req.ISRC, req.TrackName, req.ArtistName, req.SpotifyID, req.DeezerID, req.TidalID, req.QobuzID, req.DurationMS, req.ItemID, extensionAvailabilityTrackContext(req)) + // Fallback providers need the same session preparation as the selected + // provider. A cached availability result may never call signedFetch, + // leaving an expired session or pending challenge invisible to the app. + var availability *ExtAvailabilityResult + verificationRequired, err := preflightExtensionDownloadSession(providerID) + if err != nil { + err = fmt.Errorf("signed-session preflight failed: %w", err) + } else if verificationRequired { + err = fmt.Errorf("verification_required: extension '%s' needs signed-session verification", providerID) + } else { + availability, err = provider.CheckAvailabilityForItemID(req.ISRC, req.TrackName, req.ArtistName, req.SpotifyID, req.DeezerID, req.TidalID, req.QobuzID, req.DurationMS, req.ItemID, extensionAvailabilityTrackContext(req)) + } if shouldAbortCancelledFallback(req.ItemID, err) { return nil, ErrDownloadCancelled } @@ -713,7 +724,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro }, nil } } else { - GoLog("[DownloadWithExtensionFallback] %s: not available\n", providerID) + GoLog("[DownloadWithExtensionFallback] %s: not available (reason: %s)\n", providerID, resolveExtensionAvailabilityReason(availability, nil)) } if terminalAvailability { GoLog("[DownloadWithExtensionFallback] %s requested skip_fallback after availability check\n", providerID) diff --git a/go_backend/extension_fallback_verification_test.go b/go_backend/extension_fallback_verification_test.go index 667997d5..ce941532 100644 --- a/go_backend/extension_fallback_verification_test.go +++ b/go_backend/extension_fallback_verification_test.go @@ -12,7 +12,7 @@ import ( ) func TestFallbackKeepsPendingVerificationOwnership(t *testing.T) { - for _, mode := range []string{"fresh-response", "saved-challenge", "network-error", "download-error", "empty-download-error"} { + for _, mode := range []string{"fresh-response", "saved-challenge", "cached-unavailable", "cold-session", "network-error", "download-error", "empty-download-error"} { t.Run(mode, func(t *testing.T) { primary := newTestLoadedExtension(t, ExtensionTypeDownloadProvider) primary.ID, primary.Manifest.Name = "primary-provider", "primary-provider" @@ -41,6 +41,7 @@ func TestFallbackKeepsPendingVerificationOwnership(t *testing.T) { registerExtension({ searchTracks: queryCatalog, checkAvailability: function() { + if (fixtureMode === "cached-unavailable" || fixtureMode === "cold-session") return {available: false, reason: "No verified track match found"}; if (fixtureMode === "network-error") throw new Error("lookup network timeout"); if (fixtureMode === "download-error" || fixtureMode === "empty-download-error") return {available: true, track_id: "matched-track"}; if (fixtureMode === "saved-challenge") throw savedChallenge; @@ -89,13 +90,16 @@ func TestFallbackKeepsPendingVerificationOwnership(t *testing.T) { Body: io.NopCloser(strings.NewReader(`{"auth_url":"https://auth.example.test/verify"}`)), }, nil })} - expectsVerification := mode == "fresh-response" || mode == "saved-challenge" - if expectsVerification { + expectsVerification := mode == "fresh-response" || mode == "saved-challenge" || mode == "cached-unavailable" || mode == "cold-session" + if expectsVerification && mode != "cold-session" { _, err := newExtensionProviderWrapper(secondary).SearchTracks("Song Artist", 1) if err == nil || GetPendingAuthRequest(secondary.ID) == nil { t.Fatalf("metadata lookup did not create a pending challenge: %v", err) } } + if !expectsVerification { + saveUsableSignedSession(t, secondary.runtime, *secondary.Manifest.SignedSession, "authenticated-session") + } if err := last.ensureRuntimeReady(); err != nil { t.Fatal(err) } @@ -128,8 +132,8 @@ func TestFallbackKeepsPendingVerificationOwnership(t *testing.T) { if laterCalls.Load() != 0 || bootstrapCalls.Load() != 1 { t.Fatalf("pending challenge was skipped or recreated: later=%d bootstrap=%d", laterCalls.Load(), bootstrapCalls.Load()) } - } else if laterCalls.Load() != 1 { - t.Fatal("ordinary lookup failures must allow the next provider") + } else if laterCalls.Load() != 1 || bootstrapCalls.Load() != 0 { + t.Fatalf("authenticated failures must allow fallback without bootstrapping: later=%d bootstrap=%d", laterCalls.Load(), bootstrapCalls.Load()) } }) }