package api import ( "encoding/json" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) // newTestSettingsStore builds an in-memory settings store with a temp file. func NewTestSettingsStore(t *testing.T, cfg Config) *settingsStore { t.Helper() dir := t.TempDir() ss := LoadSettingsStore(filepath.Join(dir, "palette.db"), cfg) // point persistence at a temp path (dir(dbPath) == dir) return ss } func TestAdminAuth(t *testing.T) { s := testServer(t) h := s.routes() req := httptest.NewRequest("GET", "/admin/api/settings", nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 401 { t.Fatalf("no key: expected 401, got %d", rec.Code) } req = httptest.NewRequest("GET", "/admin/api/settings", nil) req.Header.Set("X-Admin-Key", "wrong-key") rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 401 { t.Fatalf("wrong key: expected 401, got %d", rec.Code) } req = httptest.NewRequest("GET", "/admin/api/settings?key=test-admin-key", nil) rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("query key: expected 200, got %d", rec.Code) } req = httptest.NewRequest("GET", "/admin/api/settings", nil) req.Header.Set("X-Admin-Key", "test-admin-key") rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("header key: expected 200, got %d", rec.Code) } // HTML page itself is open (key entered via form) req = httptest.NewRequest("GET", "/admin", nil) rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("admin page: expected 200, got %d", rec.Code) } } func TestAdminEnvKeyPrecedence(t *testing.T) { dir := t.TempDir() dbPath := filepath.Join(dir, "palette.db") t.Setenv("PALETTE_ADMIN_KEY", "envkey1234567890abcdef") key, err := ResolveAdminKey(dbPath) if err != nil { t.Fatal(err) } if key != "envkey1234567890abcdef" { t.Fatalf("env key not used: %q", key) } if _, err := os.Stat(filepath.Join(dir, "admin-key")); !os.IsNotExist(err) { t.Fatal("env key should not create a key file") } // unset env: file takes over os.Unsetenv("PALETTE_ADMIN_KEY") key2, err := ResolveAdminKey(dbPath) if err != nil { t.Fatal(err) } if len(key2) != 32 { t.Fatalf("generated key should be 32 hex chars, got %d", len(key2)) } if fi, err := os.Stat(filepath.Join(dir, "admin-key")); err != nil || fi.Mode().Perm() != 0600 { t.Fatalf("admin-key file perms: %v err %v", fi, err) } // reuse on subsequent boots key3, _ := ResolveAdminKey(dbPath) if key3 != key2 { t.Fatal("persisted key not reused") } } func TestAdminSettingsRoundTrip(t *testing.T) { s := testServer(t) h := s.routes() post := func(body string) *httptest.ResponseRecorder { req := httptest.NewRequest("POST", "/admin/api/settings", strings.NewReader(body)) req.Header.Set("X-Admin-Key", "test-admin-key") rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec } rec := post(`{"rate_limit_burst": 9, "rate_limit_per_minute": 120, "max_content_bytes": 1024, "default_expiry": "1h", "custom_slug_reservation_days": 10, "burn_viewer_window_minutes": 7}`) if rec.Code != 200 { t.Fatalf("post settings: %d %s", rec.Code, rec.Body.String()) } got := s.settings.get() if got.RateLimitBurst != 9 || got.RateLimitPerMinute != 120 || got.MaxContentBytes != 1024 || got.DefaultExpiry != "1h" || got.CustomSlugReservationDays != 10 || got.BurnViewerWindowMinutes != 7 { t.Fatalf("settings not applied: %+v", got) } // persisted to disk b, err := os.ReadFile(s.settings.path) if err != nil { t.Fatal(err) } var persisted Settings if err := json.Unmarshal(b, &persisted); err != nil { t.Fatal(err) } if persisted.BurnViewerWindowMinutes != 7 { t.Fatalf("persisted settings wrong: %+v", persisted) } // invalid rejected if rec := post(`{"rate_limit_burst": -1}`); rec.Code != 400 { t.Fatalf("invalid settings: expected 400, got %d", rec.Code) } if rec := post(`{"rate_limit_burst": 5, "rate_limit_per_minute": 60, "max_content_bytes": 1024, "default_expiry": "bogus", "custom_slug_reservation_days": 10, "burn_viewer_window_minutes": 5}`); rec.Code != 400 { t.Fatalf("bad expiry: expected 400, got %d", rec.Code) } // settings actually consumed: default expiry applied on create req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"x"}`)) rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 201 { t.Fatalf("create: %d %s", rec.Code, rec.Body.String()) } var created struct { ExpiresAt *int64 `json:"expires_at"` } json.Unmarshal(rec.Body.Bytes(), &created) if created.ExpiresAt == nil { t.Fatal("default expiry not applied to new paste") } } func TestAdminResetKey(t *testing.T) { dir := t.TempDir() dbPath := filepath.Join(dir, "palette.db") os.Unsetenv("PALETTE_ADMIN_KEY") key1, _ := ResolveAdminKey(dbPath) // direct invocation of the reset behavior ResetAdminKeyFile(dbPath) key2, _ := ResolveAdminKey(dbPath) if key1 == key2 { t.Fatal("reset did not regenerate key") } }