Files
SpotiFLAC-Mobile/go_backend/extension_availability_verification_test.go
T

130 lines
5.2 KiB
Go

package gobackend
import (
"strings"
"testing"
"time"
"github.com/dop251/goja"
)
func TestAvailabilityPreservesCanonicalVerificationOnFailure(t *testing.T) {
for _, script := range []string{
`throw new Error("VERIFY_REQUIRED");`,
`return null;`,
`return undefined;`,
`return {available:false};`,
} {
t.Run(script, func(t *testing.T) {
ext := newTestLoadedExtension(t, ExtensionTypeDownloadProvider)
t.Cleanup(func() { teardownExtension(ext) })
if err := ext.ensureRuntimeReady(); err != nil {
t.Fatal(err)
}
if err := ext.VM.Set("requireChallenge", func(goja.FunctionCall) goja.Value {
ext.runtime.noteVerificationRequired("https://example.test/challenge")
return goja.Undefined()
}); err != nil {
t.Fatal(err)
}
if _, err := ext.VM.RunString(`extension.checkAvailability = function(){ requireChallenge(); ` + script + ` };`); err != nil {
t.Fatal(err)
}
_, err := newExtensionProviderWrapper(ext).CheckAvailabilityForItemID("", "Song", "Artist", "", "", "", "", 180000, "")
if err == nil || classifyDownloadErrorType(err.Error()) != "verification_required" || !strings.Contains(err.Error(), ext.ID) {
t.Fatalf("canonical verification not preserved: %v", err)
}
if ext.runtime.consumeVerificationRequired() != "" {
t.Fatal("verification evidence leaked into the next call")
}
})
}
}
func TestAvailabilityDoesNotPromoteUntrustedOrStaleVerification(t *testing.T) {
ext := newTestLoadedExtension(t, ExtensionTypeDownloadProvider)
t.Cleanup(func() { teardownExtension(ext) })
if err := ext.ensureRuntimeReady(); err != nil {
t.Fatal(err)
}
if _, err := ext.VM.RunString(`extension.checkAvailability = function(){ throw new Error("VERIFY_REQUIRED"); };`); err != nil {
t.Fatal(err)
}
ext.runtime.noteVerificationRequired("https://example.test/stale-challenge")
_, err := newExtensionProviderWrapper(ext).CheckAvailabilityForItemID("", "Song", "Artist", "", "", "", "", 180000, "")
if err == nil || classifyDownloadErrorType(err.Error()) == "verification_required" {
t.Fatalf("untrusted exception inherited a previous challenge: %v", err)
}
}
func TestExtensionVerificationErrorsRequireOwnedPendingChallenge(t *testing.T) {
for _, tc := range []struct {
name string
message string
pendingOwner string
challengeAge time.Duration
authURL string
wantChallenge bool
}{
{"fresh", "VERIFY_REQUIRED", "coverage-ext", 0, "https://example.test/verify", true},
{"missing", "VERIFY_REQUIRED", "", 0, "", false},
{"other-provider", "VERIFY_REQUIRED", "other-provider", 0, "https://example.test/verify", false},
{"expired", "VERIFY_REQUIRED", "coverage-ext", pendingAuthRequestTTL + time.Second, "https://example.test/verify", false},
{"future", "VERIFY_REQUIRED", "coverage-ext", -time.Minute, "https://example.test/verify", false},
{"missing-url", "VERIFY_REQUIRED", "coverage-ext", 0, "", false},
{"network-error", "network timeout", "coverage-ext", 0, "https://example.test/verify", false},
{"provider-auth", "PROVIDER_AUTH_FAILED: VERIFY_REQUIRED", "coverage-ext", 0, "https://example.test/verify", false},
{"http-status", "HTTP 401: VERIFY_REQUIRED", "coverage-ext", 0, "https://example.test/verify", false},
{"cancelled", "cancelled: VERIFY_REQUIRED", "coverage-ext", 0, "https://example.test/verify", false},
{"throwing-getter", "ordinary failure", "coverage-ext", 0, "https://example.test/verify", false},
} {
t.Run(tc.name, func(t *testing.T) {
ext := newTestLoadedExtension(t, ExtensionTypeMetadataProvider, ExtensionTypeDownloadProvider)
t.Cleanup(func() {
ClearPendingAuthRequest(ext.ID)
ClearPendingAuthRequest(tc.pendingOwner)
teardownExtension(ext)
})
if err := ext.ensureRuntimeReady(); err != nil {
t.Fatal(err)
}
if err := ext.VM.Set("fixtureMessage", tc.message); err != nil {
t.Fatal(err)
}
if _, err := ext.VM.RunString(`extension.searchTracks = extension.checkAvailability = function() { throw new Error(fixtureMessage); };`); err != nil {
t.Fatal(err)
}
if tc.name == "throwing-getter" {
if _, err := ext.VM.RunString(`extension.searchTracks = extension.checkAvailability = function() {
throw {toString: function() { return "ordinary failure"; }, get message() { throw new Error("broken getter"); }};
};`); err != nil {
t.Fatal(err)
}
}
if tc.pendingOwner != "" {
if err := registerPendingAuthRequest(&PendingAuthRequest{
ExtensionID: tc.pendingOwner,
AuthURL: tc.authURL,
CreatedAt: time.Now().Add(-tc.challengeAge),
}); err != nil {
t.Fatal(err)
}
}
provider := newExtensionProviderWrapper(ext)
_, searchErr := provider.SearchTracks("Song Artist", 1)
_, availabilityErr := provider.CheckAvailabilityForItemID("", "Song", "Artist", "", "", "", "", 180000, "")
for _, err := range []error{searchErr, availabilityErr} {
if err == nil {
t.Fatal("extension error was lost")
}
if got := classifyDownloadErrorType(err.Error()) == "verification_required"; got != tc.wantChallenge {
t.Fatalf("verification=%v, want %v: %v", got, tc.wantChallenge, err)
}
}
if tc.pendingOwner == "" && GetPendingAuthRequest(ext.ID) != nil {
t.Fatal("error classification created a challenge")
}
})
}
}