Files
silo-server/internal/config/admin_settings_test.go

316 lines
11 KiB
Go

//nolint:goconst // Settings contract tests intentionally repeat literal keys in input and expected maps.
package config
import (
"reflect"
"strings"
"testing"
)
func TestEffectiveAdminSettingsUsesRuntimeDefaults(t *testing.T) {
effective := EffectiveAdminSettings(map[string]string{
"database.max_connections": "",
"server.log_level": "debug",
"custom.setting": "kept",
})
if got := effective["database.max_connections"]; got != "20" {
t.Fatalf("database.max_connections = %q, want 20", got)
}
if got := effective["s3.public_path_style"]; got != "true" {
t.Fatalf("s3.public_path_style = %q, want true", got)
}
if got := effective["playback.transcode_enabled"]; got != "true" {
t.Fatalf("playback.transcode_enabled = %q, want true", got)
}
if got := effective["theme.catalog_url"]; got != DefaultThemeCatalogURL {
t.Fatalf("theme.catalog_url = %q, want %q", got, DefaultThemeCatalogURL)
}
if got := effective["server.log_level"]; got != "debug" {
t.Fatalf("server.log_level = %q, want debug", got)
}
if got := effective["custom.setting"]; got != "kept" {
t.Fatalf("custom.setting = %q, want kept", got)
}
}
func TestEffectiveAdminSettingsUsesLegacyS3FallbacksBeforeDefaults(t *testing.T) {
effective := EffectiveAdminSettings(map[string]string{
"s3.operational_path_style": "false",
"s3.operational_token_ttl": "3600",
})
if got := effective["s3.public_path_style"]; got != "false" {
t.Fatalf("s3.public_path_style = %q, want legacy false", got)
}
if got := effective["s3.private_path_style"]; got != "false" {
t.Fatalf("s3.private_path_style = %q, want legacy false", got)
}
if got := effective["s3.public_token_ttl"]; got != "3600" {
t.Fatalf("s3.public_token_ttl = %q, want legacy 3600", got)
}
}
func TestEffectiveAdminSettingsCanonicalS3ValuesOverrideLegacyFallbacks(t *testing.T) {
effective := EffectiveAdminSettings(map[string]string{
"s3.public_path_style": "true",
"s3.private_path_style": "false",
"s3.operational_path_style": "true",
"s3.public_token_ttl": "7200",
"s3.operational_token_ttl": "3600",
})
if got := effective["s3.public_path_style"]; got != "true" {
t.Fatalf("s3.public_path_style = %q, want canonical true", got)
}
if got := effective["s3.private_path_style"]; got != "false" {
t.Fatalf("s3.private_path_style = %q, want canonical false", got)
}
if got := effective["s3.public_token_ttl"]; got != "7200" {
t.Fatalf("s3.public_token_ttl = %q, want canonical 7200", got)
}
}
func TestEffectiveAdminSettingsEmptyCanonicalS3ValuesUseLegacyFallbacks(t *testing.T) {
effective := EffectiveAdminSettings(map[string]string{
"s3.public_path_style": "",
"s3.private_path_style": "",
"s3.operational_path_style": "false",
"s3.public_token_ttl": "",
"s3.operational_token_ttl": "3600",
})
if got := effective["s3.public_path_style"]; got != "false" {
t.Fatalf("s3.public_path_style = %q, want legacy false", got)
}
if got := effective["s3.private_path_style"]; got != "false" {
t.Fatalf("s3.private_path_style = %q, want legacy false", got)
}
if got := effective["s3.public_token_ttl"]; got != "3600" {
t.Fatalf("s3.public_token_ttl = %q, want legacy 3600", got)
}
}
func TestEffectiveAdminSettingsProjectsRuntimeLegacyFallbacks(t *testing.T) {
stored := map[string]string{
"s3.operational_endpoint": "https://s3.example.invalid",
"s3.operational_public_endpoint": "https://cdn.example.invalid",
"s3.operational_region": "us-test-1",
"s3.operational_path_style": "false",
"s3.operational_bucket": "legacy-bucket",
"s3.operational_key_prefix": "legacy-prefix",
"s3.operational_access_key": "legacy-access",
"s3.operational_secret_key": "legacy-secret",
"s3.operational_url_auth": "presigned",
"s3.operational_token_secret": "legacy-token",
"s3.operational_token_param": "signature",
"s3.operational_token_ttl": "3600",
"subtitle_ai.base_url": "https://legacy-ai.example.invalid",
"subtitle_ai.api_key": "legacy-ai-key",
"subtitle_ai.chat_model": "legacy-chat-model",
"subtitle_ai.max_concurrent_jobs": "7",
"ai.max_concurrent_jobs": "0",
}
effective := EffectiveAdminSettings(stored)
expected := map[string]string{
"s3.public_endpoint": stored["s3.operational_endpoint"],
"s3.public_read_endpoint": stored["s3.operational_public_endpoint"],
"s3.public_region": stored["s3.operational_region"],
"s3.public_path_style": stored["s3.operational_path_style"],
"s3.public_bucket": stored["s3.operational_bucket"],
"s3.public_key_prefix": stored["s3.operational_key_prefix"],
"s3.public_access_key": stored["s3.operational_access_key"],
"s3.public_secret_key": stored["s3.operational_secret_key"],
"s3.public_url_auth": stored["s3.operational_url_auth"],
"s3.public_token_secret": stored["s3.operational_token_secret"],
"s3.public_token_param": stored["s3.operational_token_param"],
"s3.public_token_ttl": stored["s3.operational_token_ttl"],
"s3.private_endpoint": stored["s3.operational_endpoint"],
"s3.private_region": stored["s3.operational_region"],
"s3.private_path_style": stored["s3.operational_path_style"],
"s3.private_bucket": stored["s3.operational_bucket"],
"s3.private_key_prefix": stored["s3.operational_key_prefix"],
"s3.private_access_key": stored["s3.operational_access_key"],
"s3.private_secret_key": stored["s3.operational_secret_key"],
"ai.base_url": stored["subtitle_ai.base_url"],
"ai.api_key": stored["subtitle_ai.api_key"],
"ai.chat_model": stored["subtitle_ai.chat_model"],
"ai.max_concurrent_jobs": stored["subtitle_ai.max_concurrent_jobs"],
}
for key, want := range expected {
if got := effective[key]; got != want {
t.Errorf("%s = %q, want legacy fallback %q", key, got, want)
}
}
}
func TestEffectiveAdminSettingsUsesRuntimeDefaultForNonpositiveCanonicalAIConcurrency(t *testing.T) {
for _, canonical := range []string{"0", "-1"} {
t.Run(canonical, func(t *testing.T) {
effective := EffectiveAdminSettings(map[string]string{
"ai.max_concurrent_jobs": canonical,
})
if got := effective["ai.max_concurrent_jobs"]; got != "2" {
t.Fatalf("ai.max_concurrent_jobs = %q, want runtime default 2", got)
}
})
}
}
func TestAdminSettingDefaultsAlignWithConfigRuntimeDefaults(t *testing.T) {
baseline, err := LoadFromDB(nil)
if err != nil {
t.Fatal(err)
}
normalizeEffectiveRuntimeDefaults(baseline)
for key, value := range adminSettingDefaults {
t.Run(key, func(t *testing.T) {
withExplicitDefault, err := LoadFromDB(map[string]string{key: value})
if err != nil {
t.Fatal(err)
}
normalizeEffectiveRuntimeDefaults(withExplicitDefault)
if !reflect.DeepEqual(withExplicitDefault, baseline) {
t.Fatalf("admin default %q does not match the runtime default", value)
}
})
}
}
func TestChapterThumbnailSoftwareToneMapDefaultsDisabled(t *testing.T) {
effective := EffectiveAdminSettings(nil)
if got := effective[chapterThumbnailSoftwareToneMapKey]; got != "false" {
t.Fatalf("software tone-map default = %q, want false", got)
}
}
func normalizeEffectiveRuntimeDefaults(cfg *Config) {
if cfg.S3.Public.URLAuth == "" {
cfg.S3.Public.URLAuth = "presigned"
}
if cfg.S3.Public.TokenParam == "" {
cfg.S3.Public.TokenParam = "verify"
}
if cfg.S3.Public.TokenTTL <= 0 {
cfg.S3.Public.TokenTTL = 10800
}
// The client IP loader treats an empty value as its built-in private-range
// default. Normalize formatting as well so equivalent CIDR lists compare.
cfg.ClientIP.TrustedProxies = strings.ReplaceAll(
firstConfiguredString(
map[string]string{"trusted": cfg.ClientIP.TrustedProxies},
adminSettingDefaults["clientip.trusted_proxies"],
"trusted",
),
" ",
"",
)
}
func TestNormalizeAdminSettingRejectsInvalidValues(t *testing.T) {
tests := []struct {
key string
value string
}{
{key: "database.max_connections", value: "0"},
{key: "metadata.cache_images", value: "maybe"},
{key: chapterThumbnailSoftwareToneMapKey, value: "maybe"},
{key: "auth.access_token_expiry", value: "forever"},
{key: "recommendations.embeddings_cron", value: "not a cron"},
{key: "notifications.server_channels.batch_seconds", value: "119"},
{key: "catalog.search.meilisearch.semantic_ratio", value: "1.2"},
{key: "email.smtp_port", value: "70000"},
{key: "theme.catalog_url", value: "http://raw.githubusercontent.com/Silo-Server/silo-themes/main/catalog.json"},
{key: "theme.catalog_url", value: "https://example.com/catalog.json"},
{key: "redis.url", value: "not-a-url"},
{key: "playback.segment_retention_seconds", value: "119"},
}
for _, tc := range tests {
t.Run(tc.key, func(t *testing.T) {
if _, err := NormalizeAdminSetting(tc.key, tc.value); err == nil {
t.Fatalf("NormalizeAdminSetting(%q, %q) returned nil error", tc.key, tc.value)
}
})
}
}
func TestNormalizeAdminSettingAcceptsSegmentRetentionBounds(t *testing.T) {
for _, value := range []string{"0", "120", "86400"} {
t.Run(value, func(t *testing.T) {
got, err := NormalizeAdminSetting("playback.segment_retention_seconds", value)
if err != nil {
t.Fatalf("NormalizeAdminSetting: %v", err)
}
if got != value {
t.Fatalf("normalized retention = %q, want %q", got, value)
}
})
}
}
func TestNormalizeAdminSettingAcceptsApprovedThemeCatalogURL(t *testing.T) {
got, err := NormalizeAdminSetting(
"theme.catalog_url",
"https://raw.githubusercontent.com/Silo-Server/silo-themes/main/catalog.json/",
)
if err != nil {
t.Fatal(err)
}
if got != "https://raw.githubusercontent.com/Silo-Server/silo-themes/main/catalog.json" {
t.Fatalf("normalized URL = %q", got)
}
}
func TestValidateAdminSettingsChecksProspectiveRelationships(t *testing.T) {
values := map[string]string{
"auth.access_token_expiry": "48h",
"auth.refresh_token_expiry": "24h",
"playback.watched_threshold": "90",
"playback.min_resume_threshold": "5",
}
if err := ValidateAdminSettings(values); err == nil {
t.Fatal("ValidateAdminSettings() returned nil for refresh shorter than access")
}
values["auth.refresh_token_expiry"] = "72h"
values["playback.min_resume_threshold"] = "95"
if err := ValidateAdminSettings(values); err == nil {
t.Fatal("ValidateAdminSettings() returned nil for resume threshold above watched")
}
}
func TestValidateAdminSettingsRequiresDurableRedisTransport(t *testing.T) {
values := map[string]string{"ratelimit.backend": "redis"}
if err := ValidateAdminSettings(values); err == nil {
t.Fatal("ValidateAdminSettings() accepted Redis backend without a durable transport")
}
if err := ValidateAdminSettingsWithCapabilities(values, AdminSettingsCapabilities{
RedisBootstrapAvailable: true,
}); err != nil {
t.Fatalf("bootstrap Sentinel transport was rejected: %v", err)
}
values["redis.url"] = "redis://cache.example.invalid:6379"
if err := ValidateAdminSettings(values); err != nil {
t.Fatalf("persisted Redis URL was rejected: %v", err)
}
values["redis.url"] = "not-a-url"
if err := ValidateAdminSettings(values); err == nil {
t.Fatal("ValidateAdminSettings() accepted a malformed Redis URL")
}
}
func TestNormalizeAdminSettingCanonicalizesRedisURL(t *testing.T) {
got, err := NormalizeAdminSetting("redis.url", " rediss://cache.example.invalid:6380/2 ")
if err != nil {
t.Fatal(err)
}
if got != "rediss://cache.example.invalid:6380/2" {
t.Fatalf("normalized Redis URL = %q", got)
}
}