Files
memby/server/internal/api/features_test.go
T
2026-08-22 18:40:50 +12:00

112 lines
4.6 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/ponzischeme89/memby/server/internal/config"
"github.com/ponzischeme89/memby/server/internal/store"
)
func TestFeatureEvaluationSupportsDefaultsOverridesAndSafeMode(t *testing.T) {
definition, ok := knownFeature(featureSonarrPreroll)
if !ok {
t.Fatal("preroll is not registered")
}
if got := evaluateFeature(store.DefaultFeaturePolicy(), definition, 1); !got.Enabled || got.Source != "default" {
t.Fatalf("default evaluation = %+v", got)
}
off := store.FeaturePolicy{Overrides: map[string]bool{featureSonarrPreroll: false}}
if got := evaluateFeature(off, definition, 1); got.Enabled || got.Source != "override" {
t.Fatalf("off evaluation = %+v", got)
}
safe := store.FeaturePolicy{Overrides: map[string]bool{featureSonarrPreroll: true}, SafeMode: true}
if got := evaluateFeature(safe, definition, 1); got.Enabled || got.Source != "safe_mode" {
t.Fatalf("safe-mode evaluation = %+v", got)
}
}
func TestFeatureEvaluationFailsClosedForAnIncompatibleClient(t *testing.T) {
definition := featureDefinition{Key: "future", DefaultEnabled: true, MinimumProtocol: 2}
got := evaluateFeature(store.DefaultFeaturePolicy(), definition, 1)
if got.Enabled || got.Compatible || got.Source != "incompatible_client" {
t.Fatalf("incompatible evaluation = %+v", got)
}
}
func TestHEVCDirectPlayIsVisibleAndServerControllable(t *testing.T) {
definition, ok := knownFeature(featureHEVCDirectPlay)
if !ok || definition.Area != "Playback" || definition.Capability != "video_hevc_decode" {
t.Fatalf("HEVC feature definition = %+v, found=%v", definition, ok)
}
disabled := store.FeaturePolicy{Overrides: map[string]bool{featureHEVCDirectPlay: false}}
if got := evaluateFeature(disabled, definition, 1); got.Enabled {
t.Fatalf("HEVC override = %+v", got)
}
}
func TestGenreBrowserIsServerControlledAndOffByDefault(t *testing.T) {
definition, ok := knownFeature(featureGenreBrowser)
if !ok || definition.Capability != "genre_browser_v1" {
t.Fatalf("genre browser feature definition = %+v, found=%v", definition, ok)
}
if got := evaluateFeature(store.DefaultFeaturePolicy(), definition, 1); got.Enabled {
t.Fatalf("genre browser default = %+v, want disabled", got)
}
enabled := store.FeaturePolicy{Overrides: map[string]bool{featureGenreBrowser: true}}
if got := evaluateFeature(enabled, definition, 1); !got.Enabled || got.Source != "override" {
t.Fatalf("genre browser override = %+v", got)
}
}
func TestContinueWatchingIsServerControlledAndOnByDefault(t *testing.T) {
definition, ok := knownFeature(featureContinueWatching)
if !ok || definition.Area != "Home" {
t.Fatalf("Continue Watching feature definition = %+v, found=%v", definition, ok)
}
if got := evaluateFeature(store.DefaultFeaturePolicy(), definition, ProtocolVersion); !got.Enabled {
t.Fatalf("Continue Watching default = %+v, want enabled", got)
}
disabled := store.FeaturePolicy{Overrides: map[string]bool{featureContinueWatching: false}}
if got := evaluateFeature(disabled, definition, ProtocolVersion); got.Enabled || got.Source != "override" {
t.Fatalf("Continue Watching override = %+v", got)
}
}
func TestAppFontFamilyIsAValidatedGlobalConfiguration(t *testing.T) {
definition, ok := configurationDefinitionFor("presentation.fontFamily")
if !ok || definition.Type != "enum" || len(definition.Scopes) != 1 || definition.Scopes[0] != "global" {
t.Fatalf("font family definition = %+v, found=%v", definition, ok)
}
if err := validateConfigurationValue(definition, json.RawMessage(`"inter"`)); err != nil {
t.Fatalf("Inter was rejected: %v", err)
}
if err := validateConfigurationValue(definition, json.RawMessage(`"unknown"`)); err == nil {
t.Fatal("unknown font family was accepted")
}
}
func TestClientCapabilitiesAreNormalizedAndBounded(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/v1/status", nil)
req.Header.Set("X-Memby-Capabilities", " Sonarr_Preroll_V1,server_features_v1,sonarr_preroll_v1,"+
"this-capability-name-is-deliberately-longer-than-sixty-four-characters-and-is-rejected")
got := clientCapabilities(req)
if len(got) != 2 || got[0] != "server_features_v1" || got[1] != "sonarr_preroll_v1" {
t.Fatalf("capabilities = %#v", got)
}
}
func TestFeatureAdminRejectsUnknownFlagsBeforeWriting(t *testing.T) {
server := testServer(config.Config{})
req := httptest.NewRequest(http.MethodPost, "/admin/api/features",
strings.NewReader(`{"action":"save","expectedRevision":0,"overrides":{"made_up":true}}`))
rec := httptest.NewRecorder()
server.handleAdminFeaturePolicy(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want 400", rec.Code)
}
}