package main import ( "context" "testing" acp "github.com/coder/acp-go-sdk" ) func TestSetSessionConfigOptionReturnsAuthoritativeState(t *testing.T) { sessionID := acp.SessionId("session-config-test") agent := &mockAgent{ sessionConfig: map[acp.SessionId][]acp.SessionConfigOption{ sessionID: mockSessionConfigOptions(), }, } response, err := agent.SetSessionConfigOption(context.Background(), acp.SetSessionConfigOptionRequest{ ValueId: &acp.SetSessionConfigOptionValueId{ SessionId: sessionID, ConfigId: "effort", Value: reasoningEffortLow, }, }) if err != nil { t.Fatalf("SetSessionConfigOption() error = %v", err) } values := make(map[string]string, len(response.ConfigOptions)) for _, option := range response.ConfigOptions { if option.Select != nil { values[string(option.Select.Id)] = string(option.Select.CurrentValue) } } if values["model"] != modelFast || values["effort"] != reasoningEffortLow { t.Fatalf("config values = %#v, want model=mock-fast and effort=low", values) } } func TestSetSessionConfigOptionRejectsUnknownValue(t *testing.T) { sessionID := acp.SessionId("session-config-test") agent := &mockAgent{ sessionConfig: map[acp.SessionId][]acp.SessionConfigOption{ sessionID: mockSessionConfigOptions(), }, } _, err := agent.SetSessionConfigOption(context.Background(), acp.SetSessionConfigOptionRequest{ ValueId: &acp.SetSessionConfigOptionValueId{ SessionId: sessionID, ConfigId: "effort", Value: "unadvertised", }, }) if err == nil { t.Fatal("SetSessionConfigOption() error = nil, want invalid value error") } values := make(map[string]string) for _, option := range agent.sessionConfig[sessionID] { if option.Select != nil { values[string(option.Select.Id)] = string(option.Select.CurrentValue) } } if values["effort"] != reasoningEffortMed { t.Fatalf("effort = %q, want unchanged medium", values["effort"]) } } func TestSetSessionConfigOptionModelChangesAvailableOptions(t *testing.T) { agent := &mockAgent{ sessions: make(map[acp.SessionId]bool), sessionConfig: make(map[acp.SessionId][]acp.SessionConfigOption), commandsEmitted: make(map[acp.SessionId]bool), } session, err := agent.NewSession(context.Background(), acp.NewSessionRequest{}) if err != nil { t.Fatalf("NewSession: %v", err) } response, err := agent.SetSessionConfigOption(context.Background(), acp.SetSessionConfigOptionRequest{ ValueId: &acp.SetSessionConfigOptionValueId{ SessionId: session.SessionId, ConfigId: "model", Value: modelSmart, }, }) if err != nil { t.Fatalf("set model: %v", err) } values := make(map[string]string) for _, option := range response.ConfigOptions { if option.Select != nil { values[string(option.Select.Id)] = string(option.Select.CurrentValue) } } if values["model"] != modelSmart { t.Fatalf("model = %q, want mock-smart", values["model"]) } if values["effort"] != reasoningEffortHigh { t.Fatalf("effort = %q, want high for mock-smart", values["effort"]) } var hasMax, hasMedium bool for _, option := range response.ConfigOptions { if option.Select == nil || option.Select.Id != "effort" || option.Select.Options.Ungrouped == nil { continue } for _, choice := range *option.Select.Options.Ungrouped { hasMax = hasMax || choice.Value == "max" hasMedium = hasMedium || choice.Value == reasoningEffortMed } } if !hasMax || hasMedium { t.Fatalf("response = %#v, want smart-only effort choices", response.ConfigOptions) } } func TestSessionConfigIsIsolatedAcrossNewSessions(t *testing.T) { agent := &mockAgent{ sessions: make(map[acp.SessionId]bool), sessionConfig: make(map[acp.SessionId][]acp.SessionConfigOption), commandsEmitted: make(map[acp.SessionId]bool), } first, err := agent.NewSession(context.Background(), acp.NewSessionRequest{}) if err != nil { t.Fatalf("first NewSession: %v", err) } second, err := agent.NewSession(context.Background(), acp.NewSessionRequest{}) if err != nil { t.Fatalf("second NewSession: %v", err) } if first.SessionId == second.SessionId { t.Fatalf("session IDs must be unique: %q", first.SessionId) } _, err = agent.SetSessionConfigOption(context.Background(), acp.SetSessionConfigOptionRequest{ ValueId: &acp.SetSessionConfigOptionValueId{ SessionId: first.SessionId, ConfigId: "effort", Value: reasoningEffortLow, }, }) if err != nil { t.Fatalf("set first effort: %v", err) } response, err := agent.SetSessionConfigOption(context.Background(), acp.SetSessionConfigOptionRequest{ ValueId: &acp.SetSessionConfigOptionValueId{ SessionId: second.SessionId, ConfigId: "model", Value: "mock-fast", }, }) if err != nil { t.Fatalf("read second state: %v", err) } for _, option := range response.ConfigOptions { if option.Select != nil && option.Select.Id == "effort" && option.Select.CurrentValue != reasoningEffortMed { t.Fatalf("second effort = %q, want medium", option.Select.CurrentValue) } } } func TestSetSessionConfigOptionRejectsClosedSession(t *testing.T) { agent := &mockAgent{ sessions: make(map[acp.SessionId]bool), sessionConfig: make(map[acp.SessionId][]acp.SessionConfigOption), commandsEmitted: make(map[acp.SessionId]bool), } session, err := agent.NewSession(context.Background(), acp.NewSessionRequest{}) if err != nil { t.Fatalf("NewSession: %v", err) } if _, err = agent.CloseSession(context.Background(), acp.CloseSessionRequest{SessionId: session.SessionId}); err != nil { t.Fatalf("CloseSession: %v", err) } _, err = agent.SetSessionConfigOption(context.Background(), acp.SetSessionConfigOptionRequest{ ValueId: &acp.SetSessionConfigOptionValueId{ SessionId: session.SessionId, ConfigId: "effort", Value: reasoningEffortLow, }, }) if err == nil { t.Fatal("SetSessionConfigOption() error = nil, want unknown session error") } if _, ok := agent.sessionConfig[session.SessionId]; ok { t.Fatal("SetSessionConfigOption() recreated closed session config") } } // TestMockSessionConfigOptionsForModelAdvertisesSlowModel verifies the mock // agent advertises "mock-slow" in the model selector. E2E fixtures use the // slow tier for delay-timing assertions, and the no-silent-model-fallback // strict policy fails session start when the profile model is absent from the // advertised list — so dropping the advertisement would break both. func TestMockSessionConfigOptionsForModelAdvertisesSlowModel(t *testing.T) { for _, model := range []string{modelFast, modelSmart, modelSlow} { options := mockSessionConfigOptionsForModel(model) values := make(map[string]string, len(options)) for _, option := range options { if option.Select != nil { values[string(option.Select.Id)] = string(option.Select.CurrentValue) } } if values["model"] != model { t.Errorf("mockSessionConfigOptionsForModel(%q) current model = %q, want %q", model, values["model"], model) } } modelOption := findModelOption(t) advertised := make([]string, 0, len(*modelOption.Select.Options.Ungrouped)) for _, option := range *modelOption.Select.Options.Ungrouped { advertised = append(advertised, string(option.Value)) } if !containsString(advertised, modelSlow) { t.Fatalf("advertised models = %v, missing mock-slow (E2E + strict policy depend on it)", advertised) } for _, want := range []string{modelFast, modelSmart, modelSlow} { if !containsString(advertised, want) { t.Errorf("advertised models = %v, missing %s", advertised, want) } } } func findModelOption(t *testing.T) acp.SessionConfigOption { t.Helper() for _, option := range mockSessionConfigOptionsForModel(modelFast) { if option.Select != nil && string(option.Select.Id) == "model" { return option } } t.Fatal("no model config option advertised") return acp.SessionConfigOption{} } func containsString(values []string, want string) bool { for _, v := range values { if v == want { return true } } return false }