mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 22:14:37 +00:00
463 lines
15 KiB
Go
463 lines
15 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseReasoningConfigDefaultsToOff(t *testing.T) {
|
|
agent := &AgentData{}
|
|
|
|
got := agent.ParseReasoningConfig()
|
|
if got.OverrideMode != ReasoningOverrideInherit {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ReasoningOverrideInherit)
|
|
}
|
|
if got.Effort != "off" {
|
|
t.Fatalf("Effort = %q, want off", got.Effort)
|
|
}
|
|
if got.Fallback != ReasoningFallbackDowngrade {
|
|
t.Fatalf("Fallback = %q, want %q", got.Fallback, ReasoningFallbackDowngrade)
|
|
}
|
|
if got.Source != ReasoningSourceUnset {
|
|
t.Fatalf("Source = %q, want %q", got.Source, ReasoningSourceUnset)
|
|
}
|
|
}
|
|
|
|
func TestParseReasoningConfigUsesLegacyThinkingLevel(t *testing.T) {
|
|
agent := &AgentData{
|
|
ThinkingLevel: "medium",
|
|
}
|
|
|
|
got := agent.ParseReasoningConfig()
|
|
if got.Effort != "medium" {
|
|
t.Fatalf("Effort = %q, want medium", got.Effort)
|
|
}
|
|
if got.OverrideMode != ReasoningOverrideCustom {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ReasoningOverrideCustom)
|
|
}
|
|
if got.Source != ReasoningSourceLegacy {
|
|
t.Fatalf("Source = %q, want %q", got.Source, ReasoningSourceLegacy)
|
|
}
|
|
}
|
|
|
|
func TestParseReasoningConfigPrefersAdvancedSettings(t *testing.T) {
|
|
agent := &AgentData{
|
|
ThinkingLevel: "high",
|
|
ReasoningConfig: json.RawMessage(`{"effort": "xhigh", "fallback": "provider_default"}`),
|
|
}
|
|
|
|
got := agent.ParseReasoningConfig()
|
|
if got.Effort != "xhigh" {
|
|
t.Fatalf("Effort = %q, want xhigh", got.Effort)
|
|
}
|
|
if got.OverrideMode != ReasoningOverrideCustom {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ReasoningOverrideCustom)
|
|
}
|
|
if got.Fallback != ReasoningFallbackProviderDefault {
|
|
t.Fatalf("Fallback = %q, want %q", got.Fallback, ReasoningFallbackProviderDefault)
|
|
}
|
|
if got.Source != ReasoningSourceAdvanced {
|
|
t.Fatalf("Source = %q, want %q", got.Source, ReasoningSourceAdvanced)
|
|
}
|
|
}
|
|
|
|
func TestParseReasoningConfigKeepsLegacyEffortWhenAdvancedOnlySetsFallback(t *testing.T) {
|
|
agent := &AgentData{
|
|
ThinkingLevel: "medium",
|
|
ReasoningConfig: json.RawMessage(`{"fallback": "off"}`),
|
|
}
|
|
|
|
got := agent.ParseReasoningConfig()
|
|
if got.Effort != "medium" {
|
|
t.Fatalf("Effort = %q, want medium", got.Effort)
|
|
}
|
|
if got.Fallback != ReasoningFallbackDisable {
|
|
t.Fatalf("Fallback = %q, want %q", got.Fallback, ReasoningFallbackDisable)
|
|
}
|
|
}
|
|
|
|
func TestParseReasoningConfigPreservesExplicitInherit(t *testing.T) {
|
|
agent := &AgentData{
|
|
ThinkingLevel: "high",
|
|
ReasoningConfig: json.RawMessage(`{"override_mode": "inherit"}`),
|
|
}
|
|
|
|
got := agent.ParseReasoningConfig()
|
|
if got.OverrideMode != ReasoningOverrideInherit {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ReasoningOverrideInherit)
|
|
}
|
|
if got.Effort != "off" {
|
|
t.Fatalf("Effort = %q, want off", got.Effort)
|
|
}
|
|
if got.Source != ReasoningSourceUnset {
|
|
t.Fatalf("Source = %q, want %q", got.Source, ReasoningSourceUnset)
|
|
}
|
|
}
|
|
|
|
func TestParseProviderReasoningConfigNormalizesDefaults(t *testing.T) {
|
|
settings := json.RawMessage(`{
|
|
"reasoning_defaults": {"effort": " xhigh ", "fallback": "provider_default"}
|
|
}`)
|
|
|
|
got := ParseProviderReasoningConfig(settings)
|
|
if got == nil {
|
|
t.Fatal("ParseProviderReasoningConfig() = nil, want config")
|
|
}
|
|
if got.Effort != "xhigh" {
|
|
t.Fatalf("Effort = %q, want xhigh", got.Effort)
|
|
}
|
|
if got.Fallback != ReasoningFallbackProviderDefault {
|
|
t.Fatalf("Fallback = %q, want %q", got.Fallback, ReasoningFallbackProviderDefault)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveReasoningConfigUsesProviderDefaults(t *testing.T) {
|
|
got := ResolveEffectiveReasoningConfig(
|
|
&ProviderReasoningConfig{Effort: "medium", Fallback: ReasoningFallbackDisable},
|
|
AgentReasoningConfig{OverrideMode: ReasoningOverrideInherit},
|
|
)
|
|
|
|
if got.OverrideMode != ReasoningOverrideInherit {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ReasoningOverrideInherit)
|
|
}
|
|
if got.Effort != "medium" {
|
|
t.Fatalf("Effort = %q, want medium", got.Effort)
|
|
}
|
|
if got.Fallback != ReasoningFallbackDisable {
|
|
t.Fatalf("Fallback = %q, want %q", got.Fallback, ReasoningFallbackDisable)
|
|
}
|
|
if got.Source != ReasoningSourceProviderDefault {
|
|
t.Fatalf("Source = %q, want %q", got.Source, ReasoningSourceProviderDefault)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveReasoningConfigPreservesCustomAgentReasoning(t *testing.T) {
|
|
got := ResolveEffectiveReasoningConfig(
|
|
&ProviderReasoningConfig{Effort: "medium", Fallback: ReasoningFallbackDisable},
|
|
AgentReasoningConfig{
|
|
OverrideMode: ReasoningOverrideCustom,
|
|
Effort: "xhigh",
|
|
Fallback: ReasoningFallbackProviderDefault,
|
|
Source: ReasoningSourceAdvanced,
|
|
},
|
|
)
|
|
|
|
if got.OverrideMode != ReasoningOverrideCustom {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ReasoningOverrideCustom)
|
|
}
|
|
if got.Effort != "xhigh" {
|
|
t.Fatalf("Effort = %q, want xhigh", got.Effort)
|
|
}
|
|
if got.Source != ReasoningSourceAdvanced {
|
|
t.Fatalf("Source = %q, want %q", got.Source, ReasoningSourceAdvanced)
|
|
}
|
|
}
|
|
|
|
func TestParseChatGPTOAuthRoutingNormalizesNames(t *testing.T) {
|
|
agent := &AgentData{
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"strategy": "round_robin",
|
|
"extra_provider_names": [" openai-codex-backup ", "", "openai-codex-backup", "openai-codex-team"]
|
|
}`),
|
|
}
|
|
|
|
got := agent.ParseChatGPTOAuthRouting()
|
|
if got == nil {
|
|
t.Fatal("ParseChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if got.Strategy != ChatGPTOAuthStrategyRoundRobin {
|
|
t.Fatalf("Strategy = %q, want %q", got.Strategy, ChatGPTOAuthStrategyRoundRobin)
|
|
}
|
|
if got.OverrideMode != ChatGPTOAuthOverrideCustom {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ChatGPTOAuthOverrideCustom)
|
|
}
|
|
|
|
wantExtras := []string{"openai-codex-backup", "openai-codex-team"}
|
|
if !reflect.DeepEqual(got.ExtraProviderNames, wantExtras) {
|
|
t.Fatalf("ExtraProviderNames = %#v, want %#v", got.ExtraProviderNames, wantExtras)
|
|
}
|
|
}
|
|
|
|
func TestPublicChatGPTOAuthRoutingMigratesLegacyStrategiesToPriorityOrder(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
strategy string
|
|
}{
|
|
{name: "unknown", strategy: "something_else"},
|
|
{name: "manual", strategy: "manual"},
|
|
{name: "primary_first", strategy: "primary_first"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
agent := &AgentData{
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"strategy": "` + tc.strategy + `",
|
|
"extra_provider_names": ["openai-codex-backup"]
|
|
}`),
|
|
}
|
|
|
|
got := agent.ParseChatGPTOAuthRouting()
|
|
if got == nil {
|
|
t.Fatal("ParseChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
public := PublicChatGPTOAuthRouting(got)
|
|
if public == nil {
|
|
t.Fatal("PublicChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if public.Strategy != ChatGPTOAuthStrategyPriority {
|
|
t.Fatalf("Strategy = %q, want %q", public.Strategy, ChatGPTOAuthStrategyPriority)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPublicChatGPTOAuthRoutingCanonicalizesSingleAccountOverrideToPriorityOrder(t *testing.T) {
|
|
agent := &AgentData{
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"strategy": "manual",
|
|
"extra_provider_names": []
|
|
}`),
|
|
}
|
|
|
|
got := agent.ParseChatGPTOAuthRouting()
|
|
if got == nil {
|
|
t.Fatal("ParseChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if got.OverrideMode != ChatGPTOAuthOverrideCustom {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ChatGPTOAuthOverrideCustom)
|
|
}
|
|
public := PublicChatGPTOAuthRouting(got)
|
|
if public == nil {
|
|
t.Fatal("PublicChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if public.Strategy != ChatGPTOAuthStrategyPriority {
|
|
t.Fatalf("Strategy = %q, want %q", public.Strategy, ChatGPTOAuthStrategyPriority)
|
|
}
|
|
if got.ExtraProviderNames == nil {
|
|
t.Fatal("ExtraProviderNames = nil, want explicit empty slice preserved")
|
|
}
|
|
}
|
|
|
|
func TestParseChatGPTOAuthRoutingPreservesExplicitInheritMode(t *testing.T) {
|
|
agent := &AgentData{
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"override_mode": "inherit"
|
|
}`),
|
|
}
|
|
|
|
got := agent.ParseChatGPTOAuthRouting()
|
|
if got == nil {
|
|
t.Fatal("ParseChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if got.OverrideMode != ChatGPTOAuthOverrideInherit {
|
|
t.Fatalf("OverrideMode = %q, want %q", got.OverrideMode, ChatGPTOAuthOverrideInherit)
|
|
}
|
|
public := PublicChatGPTOAuthRouting(got)
|
|
if public == nil {
|
|
t.Fatal("PublicChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if public.Strategy != ChatGPTOAuthStrategyPriority {
|
|
t.Fatalf("Strategy = %q, want %q", public.Strategy, ChatGPTOAuthStrategyPriority)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveChatGPTOAuthRoutingUsesProviderDefaultsWhenAgentUnset(t *testing.T) {
|
|
defaults := &ChatGPTOAuthRoutingConfig{
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{"codex-work"},
|
|
}
|
|
|
|
got := ResolveEffectiveChatGPTOAuthRouting(defaults, nil)
|
|
if got == nil {
|
|
t.Fatal("ResolveEffectiveChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if got.Strategy != ChatGPTOAuthStrategyRoundRobin {
|
|
t.Fatalf("Strategy = %q, want %q", got.Strategy, ChatGPTOAuthStrategyRoundRobin)
|
|
}
|
|
if !reflect.DeepEqual(got.ExtraProviderNames, []string{"codex-work"}) {
|
|
t.Fatalf("ExtraProviderNames = %#v, want %#v", got.ExtraProviderNames, []string{"codex-work"})
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveChatGPTOAuthRoutingAllowsInheritWithoutSavedProviderPool(t *testing.T) {
|
|
override := &ChatGPTOAuthRoutingConfig{
|
|
OverrideMode: ChatGPTOAuthOverrideInherit,
|
|
}
|
|
|
|
got := ResolveEffectiveChatGPTOAuthRouting(nil, override)
|
|
if got != nil {
|
|
t.Fatalf("ResolveEffectiveChatGPTOAuthRouting() = %#v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveChatGPTOAuthRoutingInheritForwardsProviderDefaults(t *testing.T) {
|
|
defaults := &ChatGPTOAuthRoutingConfig{
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{"codex-work"},
|
|
}
|
|
override := &ChatGPTOAuthRoutingConfig{
|
|
OverrideMode: ChatGPTOAuthOverrideInherit,
|
|
}
|
|
|
|
got := ResolveEffectiveChatGPTOAuthRouting(defaults, override)
|
|
if got == nil {
|
|
t.Fatal("ResolveEffectiveChatGPTOAuthRouting() = nil, want config forwarding provider defaults")
|
|
}
|
|
if got.Strategy != ChatGPTOAuthStrategyRoundRobin {
|
|
t.Fatalf("Strategy = %q, want %q", got.Strategy, ChatGPTOAuthStrategyRoundRobin)
|
|
}
|
|
if !reflect.DeepEqual(got.ExtraProviderNames, []string{"codex-work"}) {
|
|
t.Fatalf("ExtraProviderNames = %#v, want provider defaults", got.ExtraProviderNames)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveChatGPTOAuthRoutingAllowsCustomSingleAccountToDisableDefaults(t *testing.T) {
|
|
defaults := &ChatGPTOAuthRoutingConfig{
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{"codex-work"},
|
|
}
|
|
override := &ChatGPTOAuthRoutingConfig{
|
|
OverrideMode: ChatGPTOAuthOverrideCustom,
|
|
Strategy: ChatGPTOAuthStrategyPriority,
|
|
ExtraProviderNames: []string{},
|
|
}
|
|
|
|
got := ResolveEffectiveChatGPTOAuthRouting(defaults, override)
|
|
if got == nil {
|
|
t.Fatal("ResolveEffectiveChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if got.Strategy != ChatGPTOAuthStrategyPriority {
|
|
t.Fatalf("Strategy = %q, want %q", got.Strategy, ChatGPTOAuthStrategyPriority)
|
|
}
|
|
if len(got.ExtraProviderNames) != 0 {
|
|
t.Fatalf("ExtraProviderNames = %#v, want empty", got.ExtraProviderNames)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveChatGPTOAuthRoutingRoundRobinEmptyExtrasKeepsDefaults(t *testing.T) {
|
|
defaults := &ChatGPTOAuthRoutingConfig{
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{"codex-work"},
|
|
}
|
|
override := &ChatGPTOAuthRoutingConfig{
|
|
OverrideMode: ChatGPTOAuthOverrideCustom,
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{},
|
|
}
|
|
|
|
got := ResolveEffectiveChatGPTOAuthRouting(defaults, override)
|
|
if got == nil {
|
|
t.Fatal("ResolveEffectiveChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if !reflect.DeepEqual(got.ExtraProviderNames, defaults.ExtraProviderNames) {
|
|
t.Fatalf("ExtraProviderNames = %#v, want %#v", got.ExtraProviderNames, defaults.ExtraProviderNames)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveChatGPTOAuthRoutingKeepsProviderOwnedMembersForStrategyOverride(t *testing.T) {
|
|
defaults := &ChatGPTOAuthRoutingConfig{
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{"codex-work", "codex-team"},
|
|
}
|
|
override := &ChatGPTOAuthRoutingConfig{
|
|
OverrideMode: ChatGPTOAuthOverrideCustom,
|
|
Strategy: ChatGPTOAuthStrategyPriority,
|
|
}
|
|
|
|
got := ResolveEffectiveChatGPTOAuthRouting(defaults, override)
|
|
if got == nil {
|
|
t.Fatal("ResolveEffectiveChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if got.Strategy != ChatGPTOAuthStrategyPriority {
|
|
t.Fatalf("Strategy = %q, want %q", got.Strategy, ChatGPTOAuthStrategyPriority)
|
|
}
|
|
if !reflect.DeepEqual(got.ExtraProviderNames, defaults.ExtraProviderNames) {
|
|
t.Fatalf("ExtraProviderNames = %#v, want %#v", got.ExtraProviderNames, defaults.ExtraProviderNames)
|
|
}
|
|
}
|
|
|
|
func TestResolveEffectiveChatGPTOAuthRoutingIgnoresCustomMembersWhenProviderOwnsPool(t *testing.T) {
|
|
defaults := &ChatGPTOAuthRoutingConfig{
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{"codex-work", "codex-team"},
|
|
}
|
|
override := &ChatGPTOAuthRoutingConfig{
|
|
OverrideMode: ChatGPTOAuthOverrideCustom,
|
|
Strategy: ChatGPTOAuthStrategyRoundRobin,
|
|
ExtraProviderNames: []string{"rogue-provider"},
|
|
}
|
|
|
|
got := ResolveEffectiveChatGPTOAuthRouting(defaults, override)
|
|
if got == nil {
|
|
t.Fatal("ResolveEffectiveChatGPTOAuthRouting() = nil, want config")
|
|
}
|
|
if !reflect.DeepEqual(got.ExtraProviderNames, defaults.ExtraProviderNames) {
|
|
t.Fatalf("ExtraProviderNames = %#v, want provider defaults %#v", got.ExtraProviderNames, defaults.ExtraProviderNames)
|
|
}
|
|
}
|
|
|
|
// ─── ParseAllowImageGeneration ────────────────────────────────────────────
|
|
|
|
func TestParseAllowImageGeneration_DefaultTrue_NoOtherConfig(t *testing.T) {
|
|
ag := &AgentData{}
|
|
if !ag.ParseAllowImageGeneration() {
|
|
t.Error("empty other_config must default to true (image gen enabled)")
|
|
}
|
|
}
|
|
|
|
func TestParseAllowImageGeneration_DefaultTrue_EmptyObject(t *testing.T) {
|
|
ag := &AgentData{OtherConfig: json.RawMessage(`{}`)}
|
|
if !ag.ParseAllowImageGeneration() {
|
|
t.Error("empty JSONB object must default to true")
|
|
}
|
|
}
|
|
|
|
func TestParseAllowImageGeneration_ExplicitTrue(t *testing.T) {
|
|
ag := &AgentData{OtherConfig: json.RawMessage(`{"allow_image_generation":true}`)}
|
|
if !ag.ParseAllowImageGeneration() {
|
|
t.Error("explicit true must return true")
|
|
}
|
|
}
|
|
|
|
func TestParseAllowImageGeneration_ExplicitFalse(t *testing.T) {
|
|
ag := &AgentData{OtherConfig: json.RawMessage(`{"allow_image_generation":false}`)}
|
|
if ag.ParseAllowImageGeneration() {
|
|
t.Error("explicit false must return false")
|
|
}
|
|
}
|
|
|
|
func TestParseAllowImageGeneration_MalformedJSON_DefaultsTrue(t *testing.T) {
|
|
ag := &AgentData{OtherConfig: json.RawMessage(`{not-json`)}
|
|
if !ag.ParseAllowImageGeneration() {
|
|
t.Error("malformed other_config must default to true")
|
|
}
|
|
}
|
|
|
|
func TestParseAllowImageGeneration_UnrelatedKeys_DefaultsTrue(t *testing.T) {
|
|
ag := &AgentData{OtherConfig: json.RawMessage(`{"self_evolve":true,"skill_evolve":false}`)}
|
|
if !ag.ParseAllowImageGeneration() {
|
|
t.Error("other_config without allow_image_generation key must default to true")
|
|
}
|
|
}
|
|
|
|
func TestParseToolsConfigWaitPolicy(t *testing.T) {
|
|
t.Parallel()
|
|
agent := AgentData{
|
|
ToolsConfig: json.RawMessage(`{"profile":"coding","wait":{"min_ms":500,"max_ms":60000},"toolCallPrefix":"proxy_"}`),
|
|
}
|
|
|
|
got := agent.ParseToolsConfig()
|
|
if got == nil {
|
|
t.Fatal("ParseToolsConfig() = nil")
|
|
}
|
|
if got.Wait == nil {
|
|
t.Fatal("Wait policy was not parsed")
|
|
}
|
|
if got.Wait.MinMs != 500 || got.Wait.MaxMs != 60000 {
|
|
t.Fatalf("Wait = %#v, want min=500 max=60000", got.Wait)
|
|
}
|
|
if got.ToolCallPrefix != "proxy_" {
|
|
t.Fatalf("ToolCallPrefix = %q", got.ToolCallPrefix)
|
|
}
|
|
}
|