package gateway import ( "strings" "testing" "gopkg.in/yaml.v3" "git.yeij.top/AskaEth/Cyrene_v2/internal/persona/loader" ) // TestPersonaUpsertRequest_ThinkingRoundTripsThroughYAML 钉住管理侧人格 // 写路径对可选 `thinking` 块的处理:手写 DTO 映射进 // loader 结构体,yaml.Marshal 输出文档化的键,重载 // 该 YAML 得到相同的值 —— 即该字段真的往返成功, // 而不是被未列入白名单的 DTO 丢弃。 func TestPersonaUpsertRequest_ThinkingRoundTripsThroughYAML(t *testing.T) { t.Parallel() show := false req := personaUpsertRequest{ ID: "xilian", Name: "昔涟", Role: "r", SystemPrompt: "s", Thinking: &personaThinkingRequest{BudgetTokens: 4096, Effort: " medium ", ShowToUser: &show, Mode: " on "}, } p := personaFromRequest(req) if err := p.ValidateThinking(); err != nil { t.Fatalf("ValidateThinking() error: %v", err) } data, err := yaml.Marshal(p) if err != nil { t.Fatalf("Marshal: %v", err) } back := &loader.Persona{} if err := yaml.Unmarshal(data, back); err != nil { t.Fatalf("Unmarshal: %v", err) } if back.Thinking.BudgetTokens != 4096 || back.Thinking.Mode != loader.ThinkingModeOn || back.Thinking.Effort != "medium" { t.Fatalf("thinking = %+v, want 4096/on/medium", back.Thinking) } if back.Thinking.ShowToUser == nil || *back.Thinking.ShowToUser { t.Fatalf("showToUser = %v, want an explicit false", back.Thinking.ShowToUser) } } // TestPersonaUpsertRequest_ThinkingInvalidRejected 断言非法的 thinking // 块会经由 loader 所用的同一套校验被拒绝,因此管理 API // 无法持久化运行时拒绝加载的模式。 func TestPersonaUpsertRequest_ThinkingInvalidRejected(t *testing.T) { t.Parallel() p := personaFromRequest(personaUpsertRequest{ ID: "x", Name: "x", Role: "r", Thinking: &personaThinkingRequest{Mode: "always"}, }) err := p.ValidateThinking() if err == nil { t.Fatal("ValidateThinking() = nil, want an error") } if !strings.Contains(err.Error(), "always") { t.Fatalf("error %q does not name the offending mode", err) } } // TestPersonaUpsertRequest_NoThinkingBlockOmitsTheKey 断言不带 thinking // 块的请求在 YAML 中不产出它,因此既有人格文件不会多出 // 一段噪声。 func TestPersonaUpsertRequest_NoThinkingBlockOmitsTheKey(t *testing.T) { t.Parallel() data, err := yaml.Marshal(personaFromRequest(personaUpsertRequest{ID: "x", Name: "x", Role: "r", SystemPrompt: "s"})) if err != nil { t.Fatalf("Marshal: %v", err) } if strings.Contains(string(data), "thinking") { t.Fatalf("marshaled persona carries a thinking block:\n%s", data) } } // TestApplyThinkingEdit_PreservesOnOmission 是非破坏性更新规则: // 省略 `thinking` 的客户端保留已存块,显式给出块则替换 // 它,全默认值的块清除它。 func TestApplyThinkingEdit_PreservesOnOmission(t *testing.T) { t.Parallel() show := false stored := &loader.Persona{Thinking: loader.ThinkingParams{BudgetTokens: 8192, Mode: loader.ThinkingModeOn, ShowToUser: &show}} kept := applyThinkingEdit(nil, stored) if kept.BudgetTokens != 8192 || kept.Mode != loader.ThinkingModeOn || kept.ShowToUser == nil { t.Fatalf("applyThinkingEdit(nil, stored) = %+v, want the stored block", kept) } replaced := applyThinkingEdit(&personaThinkingRequest{BudgetTokens: 1024, Mode: "off"}, stored) if replaced.BudgetTokens != 1024 || replaced.Mode != loader.ThinkingModeOff || replaced.ShowToUser != nil { t.Fatalf("applyThinkingEdit(explicit, stored) = %+v, want the request block", replaced) } cleared := applyThinkingEdit(&personaThinkingRequest{}, stored) if cleared.BudgetTokens != 0 || cleared.Mode != "" || cleared.ShowToUser != nil { t.Fatalf("applyThinkingEdit(empty object, stored) = %+v, want the zero block", cleared) } if created := applyThinkingEdit(nil, nil); created.BudgetTokens != 0 || created.ShowToUser != nil { t.Fatalf("applyThinkingEdit(nil, nil) = %+v, want the zero block (create path)", created) } }