diff --git a/internal/api/feature_handler_test.go b/internal/api/feature_handler_test.go index 0419c6e..0b02737 100644 --- a/internal/api/feature_handler_test.go +++ b/internal/api/feature_handler_test.go @@ -93,6 +93,98 @@ func TestFeatureHandler_UpdateFeatures_LogOverridesClear(t *testing.T) { } } +func TestFeatureHandler_UpdateFeatures_RegularKeys(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(`{"dp_context_preflight_enabled":false,"dp_state_store_backend":"redis","dp_claude_cross_upstream":"false","custom_number":12}`) + 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()) + } + + var got struct { + Updated map[string]string `json:"updated"` + } + if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { + t.Fatalf("decode response: %v", err) + } + if got.Updated["dp_context_preflight_enabled"] != "false" { + t.Fatalf("expected dp_context_preflight_enabled=false, got %q", got.Updated["dp_context_preflight_enabled"]) + } + if got.Updated["dp_state_store_backend"] != "redis" { + t.Fatalf("expected dp_state_store_backend=redis, got %q", got.Updated["dp_state_store_backend"]) + } + if got.Updated["dp_claude_cross_upstream"] != "false" { + t.Fatalf("expected dp_claude_cross_upstream=false, got %q", got.Updated["dp_claude_cross_upstream"]) + } + if got.Updated["custom_number"] != "12" { + t.Fatalf("expected custom_number=12, got %q", got.Updated["custom_number"]) + } + + if v := mr.HGet("meta:features", "dp_context_preflight_enabled"); v != "false" { + t.Fatalf("expected redis dp_context_preflight_enabled=false, got %q", v) + } + if v := mr.HGet("meta:features", "dp_state_store_backend"); v != "redis" { + t.Fatalf("expected redis dp_state_store_backend=redis, got %q", v) + } + if v := mr.HGet("meta:features", "dp_claude_cross_upstream"); v != "false" { + t.Fatalf("expected redis dp_claude_cross_upstream=false, got %q", v) + } + if v := mr.HGet("meta:features", "custom_number"); v != "12" { + t.Fatalf("expected redis custom_number=12, got %q", v) + } +} + +func TestFeatureHandler_UpdateFeatures_MixedKeys(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(`{"dp_context_preflight_enabled":true,"log_retention_days":5}`) + 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()) + } + + var got struct { + Updated map[string]string `json:"updated"` + } + if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { + t.Fatalf("decode response: %v", err) + } + if got.Updated["dp_context_preflight_enabled"] != "true" { + t.Fatalf("expected dp_context_preflight_enabled=true, got %q", got.Updated["dp_context_preflight_enabled"]) + } + if got.Updated["log_retention_days"] != "5" { + t.Fatalf("expected log_retention_days=5, got %q", got.Updated["log_retention_days"]) + } + + if v := mr.HGet("meta:features", "dp_context_preflight_enabled"); v != "true" { + t.Fatalf("expected redis dp_context_preflight_enabled=true, got %q", v) + } + if v, _ := mr.Get("meta:log:retention_days"); v != "5" { + t.Fatalf("expected redis retention_days=5, got %q", v) + } +} + func TestFeatureHandler_ListFeatures_IncludesLogOverrides(t *testing.T) { gin.SetMode(gin.TestMode)