Kaynağa Gözat

fix: ensure proper handling of JSON unmarshalling for maps in config update

CaIon 1 ay önce
ebeveyn
işleme
4e93148d9e
2 değiştirilmiş dosya ile 106 ekleme ve 2 silme
  1. 10 2
      setting/config/config.go
  2. 96 0
      setting/config/config_test.go

+ 10 - 2
setting/config/config.go

@@ -252,8 +252,16 @@ func updateConfigFromMap(config interface{}, configMap map[string]string) error
 					continue
 				}
 			}
-		case reflect.Map, reflect.Slice, reflect.Struct:
-			// 复杂类型使用JSON反序列化
+		case reflect.Map:
+			// json.Unmarshal merges into existing maps (keeps old keys that are
+			// absent from the new JSON). Allocate a fresh map so removed keys
+			// are properly cleared.
+			fresh := reflect.New(field.Type())
+			if err := json.Unmarshal([]byte(strValue), fresh.Interface()); err != nil {
+				continue
+			}
+			field.Set(fresh.Elem())
+		case reflect.Slice, reflect.Struct:
 			err := json.Unmarshal([]byte(strValue), field.Addr().Interface())
 			if err != nil {
 				continue

+ 96 - 0
setting/config/config_test.go

@@ -0,0 +1,96 @@
+package config
+
+import (
+	"testing"
+)
+
+type testConfigWithMap struct {
+	Modes map[string]string `json:"modes"`
+	Exprs map[string]string `json:"exprs"`
+	Name  string            `json:"name"`
+}
+
+func TestUpdateConfigFromMap_MapReplacement(t *testing.T) {
+	cfg := &testConfigWithMap{
+		Modes: map[string]string{
+			"model-a": "tiered_expr",
+			"model-b": "tiered_expr",
+		},
+		Exprs: map[string]string{
+			"model-a": "p * 5 + c * 25",
+			"model-b": "p * 10 + c * 50",
+		},
+		Name: "billing",
+	}
+
+	// Simulate removing model-a: new value only has model-b
+	err := UpdateConfigFromMap(cfg, map[string]string{
+		"modes": `{"model-b": "tiered_expr"}`,
+		"exprs": `{"model-b": "p * 10 + c * 50"}`,
+	})
+	if err != nil {
+		t.Fatalf("UpdateConfigFromMap failed: %v", err)
+	}
+
+	if _, ok := cfg.Modes["model-a"]; ok {
+		t.Errorf("Modes still contains model-a after it was removed from the update; got %v", cfg.Modes)
+	}
+	if _, ok := cfg.Exprs["model-a"]; ok {
+		t.Errorf("Exprs still contains model-a after it was removed from the update; got %v", cfg.Exprs)
+	}
+
+	if cfg.Modes["model-b"] != "tiered_expr" {
+		t.Errorf("Modes[model-b] = %q, want %q", cfg.Modes["model-b"], "tiered_expr")
+	}
+	if cfg.Exprs["model-b"] != "p * 10 + c * 50" {
+		t.Errorf("Exprs[model-b] = %q, want %q", cfg.Exprs["model-b"], "p * 10 + c * 50")
+	}
+}
+
+func TestUpdateConfigFromMap_EmptyMapClearsAll(t *testing.T) {
+	cfg := &testConfigWithMap{
+		Modes: map[string]string{
+			"model-a": "tiered_expr",
+		},
+		Exprs: map[string]string{
+			"model-a": "p * 5 + c * 25",
+		},
+	}
+
+	err := UpdateConfigFromMap(cfg, map[string]string{
+		"modes": `{}`,
+		"exprs": `{}`,
+	})
+	if err != nil {
+		t.Fatalf("UpdateConfigFromMap failed: %v", err)
+	}
+
+	if len(cfg.Modes) != 0 {
+		t.Errorf("Modes should be empty after updating with {}, got %v", cfg.Modes)
+	}
+	if len(cfg.Exprs) != 0 {
+		t.Errorf("Exprs should be empty after updating with {}, got %v", cfg.Exprs)
+	}
+}
+
+func TestUpdateConfigFromMap_ScalarFieldsUnchanged(t *testing.T) {
+	cfg := &testConfigWithMap{
+		Modes: map[string]string{"m": "v"},
+		Name:  "old",
+	}
+
+	err := UpdateConfigFromMap(cfg, map[string]string{
+		"name": "new",
+	})
+	if err != nil {
+		t.Fatalf("UpdateConfigFromMap failed: %v", err)
+	}
+
+	if cfg.Name != "new" {
+		t.Errorf("Name = %q, want %q", cfg.Name, "new")
+	}
+	// modes was not in configMap, should remain unchanged
+	if cfg.Modes["m"] != "v" {
+		t.Errorf("Modes should be unchanged, got %v", cfg.Modes)
+	}
+}