fix(download): prepare fallback sessions before availability checks

This commit is contained in:
zarzet
2026-09-06 17:50:25 +07:00
parent 7920554f5c
commit 90da359e58
2 changed files with 22 additions and 7 deletions
+13 -2
View File
@@ -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)
@@ -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())
}
})
}