package api import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/alicebob/miniredis/v2" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" ) func TestFeatureHandler_UpdateFeatures_LogOverrides(t *testing.T) { gin.SetMode(gin.TestMode) mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) h := NewFeatureHandler(rdb) r := gin.New() r.PUT("/admin/features", h.UpdateFeatures) body := []byte(`{"log_retention_days":7,"log_max_records":123}`) req := httptest.NewRequest(http.MethodPut, "/admin/features", bytes.NewReader(body)) rr := httptest.NewRecorder() r.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", rr.Code, rr.Body.String()) } type resp struct { Updated map[string]string `json:"updated"` } var got resp if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { t.Fatalf("decode response: %v", err) } if got.Updated["log_retention_days"] != "7" { t.Fatalf("expected log_retention_days=7, got %q", got.Updated["log_retention_days"]) } if got.Updated["log_max_records"] != "123" { t.Fatalf("expected log_max_records=123, got %q", got.Updated["log_max_records"]) } if v, _ := mr.Get("meta:log:retention_days"); v != "7" { t.Fatalf("expected redis retention_days=7, got %q", v) } if v, _ := mr.Get("meta:log:max_records"); v != "123" { t.Fatalf("expected redis max_records=123, got %q", v) } if v := mr.HGet("meta:features", "log_retention_days"); v != "" { t.Fatalf("expected no log_retention_days in meta:features, got %q", v) } if v := mr.HGet("meta:features", "log_max_records"); v != "" { t.Fatalf("expected no log_max_records in meta:features, got %q", v) } } func TestFeatureHandler_UpdateFeatures_LogOverridesClear(t *testing.T) { gin.SetMode(gin.TestMode) mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) if err := rdb.Set(context.Background(), "meta:log:retention_days", "9", 0).Err(); err != nil { t.Fatalf("seed retention: %v", err) } if err := rdb.Set(context.Background(), "meta:log:max_records", "999", 0).Err(); err != nil { t.Fatalf("seed max_records: %v", err) } h := NewFeatureHandler(rdb) r := gin.New() r.PUT("/admin/features", h.UpdateFeatures) body := []byte(`{"log_retention_days":0,"log_max_records":0}`) req := httptest.NewRequest(http.MethodPut, "/admin/features", bytes.NewReader(body)) rr := httptest.NewRecorder() r.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", rr.Code, rr.Body.String()) } if mr.Exists("meta:log:retention_days") { t.Fatalf("expected retention_days to be cleared") } if mr.Exists("meta:log:max_records") { t.Fatalf("expected max_records to be cleared") } } func TestFeatureHandler_ListFeatures_IncludesLogOverrides(t *testing.T) { gin.SetMode(gin.TestMode) mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) if err := rdb.HSet(context.Background(), "meta:features", map[string]any{ "registration_code_enabled": "true", }).Err(); err != nil { t.Fatalf("seed features: %v", err) } if err := rdb.Set(context.Background(), "meta:log:retention_days", "14", 0).Err(); err != nil { t.Fatalf("seed retention: %v", err) } if err := rdb.Set(context.Background(), "meta:log:max_records", "2048", 0).Err(); err != nil { t.Fatalf("seed max_records: %v", err) } h := NewFeatureHandler(rdb) r := gin.New() r.GET("/admin/features", h.ListFeatures) req := httptest.NewRequest(http.MethodGet, "/admin/features", nil) rr := httptest.NewRecorder() r.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", rr.Code, rr.Body.String()) } var got struct { Features map[string]string `json:"features"` } if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { t.Fatalf("decode response: %v", err) } if got.Features["registration_code_enabled"] != "true" { t.Fatalf("expected registration_code_enabled=true, got %q", got.Features["registration_code_enabled"]) } if got.Features["log_retention_days"] != "14" { t.Fatalf("expected log_retention_days=14, got %q", got.Features["log_retention_days"]) } if got.Features["log_max_records"] != "2048" { t.Fatalf("expected log_max_records=2048, got %q", got.Features["log_max_records"]) } }