289 lines
7.5 KiB
Go
289 lines
7.5 KiB
Go
package claudecode
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/chenhg5/cc-connect/config"
|
|
"github.com/chenhg5/cc-connect/core"
|
|
)
|
|
|
|
// These integration tests use real provider credentials from ~/.cc-connect/config.toml.
|
|
// They verify that provider switching correctly sets up env vars and that agent
|
|
// sessions can be started with the right configuration.
|
|
//
|
|
// Run with: CC_RUN_PROVIDER_INTEGRATION=1 go test ./agent/claudecode -run TestIntegration -v
|
|
// Skip explicitly with: CC_SKIP_INTEGRATION=1
|
|
|
|
func skipIfNoConfig(t *testing.T) *config.Config {
|
|
t.Helper()
|
|
if os.Getenv("CC_SKIP_INTEGRATION") == "1" {
|
|
t.Skip("CC_SKIP_INTEGRATION=1")
|
|
}
|
|
if os.Getenv("CC_RUN_PROVIDER_INTEGRATION") != "1" {
|
|
t.Skip("set CC_RUN_PROVIDER_INTEGRATION=1 to run provider integration tests")
|
|
}
|
|
cfgPath := os.ExpandEnv("$HOME/.cc-connect/config.toml")
|
|
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
|
|
t.Skipf("config not found at %s", cfgPath)
|
|
}
|
|
cfg, err := config.Load(cfgPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to load config: %v", err)
|
|
}
|
|
cfg.ResolveProviderRefs()
|
|
return cfg
|
|
}
|
|
|
|
func configToCoreProv(p config.ProviderConfig) core.ProviderConfig {
|
|
cp := core.ProviderConfig{
|
|
Name: p.Name,
|
|
APIKey: p.APIKey,
|
|
BaseURL: p.BaseURL,
|
|
Model: p.Model,
|
|
Env: p.Env,
|
|
}
|
|
if p.Thinking != "" {
|
|
cp.Thinking = p.Thinking
|
|
}
|
|
for _, m := range p.Models {
|
|
cp.Models = append(cp.Models, core.ModelOption{Name: m.Model})
|
|
}
|
|
return cp
|
|
}
|
|
|
|
func findProjectProviders(cfg *config.Config, agentType string) (projName string, providers []core.ProviderConfig, workDir string) {
|
|
for i := range cfg.Projects {
|
|
proj := &cfg.Projects[i]
|
|
if proj.Agent.Type != agentType || len(proj.Agent.Providers) == 0 {
|
|
continue
|
|
}
|
|
for _, p := range proj.Agent.Providers {
|
|
providers = append(providers, configToCoreProv(p))
|
|
}
|
|
projName = proj.Name
|
|
if wd, ok := proj.Agent.Options["work_dir"].(string); ok {
|
|
workDir = wd
|
|
}
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func TestIntegration_ProviderSwitch_EnvVars(t *testing.T) {
|
|
cfg := skipIfNoConfig(t)
|
|
|
|
name, providers, _ := findProjectProviders(cfg, "claudecode")
|
|
if name == "" {
|
|
t.Skip("no claudecode project with providers found")
|
|
}
|
|
if len(providers) < 2 {
|
|
t.Skipf("project %q has only %d provider(s), need at least 2", name, len(providers))
|
|
}
|
|
|
|
a := &Agent{
|
|
providers: providers,
|
|
activeIdx: -1,
|
|
}
|
|
|
|
p0, p1 := providers[0], providers[1]
|
|
t.Logf("provider[0]: name=%s model=%s base_url=%s", p0.Name, p0.Model, p0.BaseURL)
|
|
t.Logf("provider[1]: name=%s model=%s base_url=%s", p1.Name, p1.Model, p1.BaseURL)
|
|
|
|
a.SetActiveProvider(p0.Name)
|
|
env0 := envSliceToMap(a.providerEnvLocked())
|
|
|
|
if p0.BaseURL != "" {
|
|
if got := env0["ANTHROPIC_BASE_URL"]; got != p0.BaseURL {
|
|
t.Errorf("provider[0] ANTHROPIC_BASE_URL = %q, want %q", got, p0.BaseURL)
|
|
}
|
|
}
|
|
if p0.Model != "" {
|
|
if got := env0["ANTHROPIC_MODEL"]; got != p0.Model {
|
|
t.Errorf("provider[0] ANTHROPIC_MODEL = %q, want %q", got, p0.Model)
|
|
}
|
|
}
|
|
|
|
a.SetActiveProvider(p1.Name)
|
|
env1 := envSliceToMap(a.providerEnvLocked())
|
|
|
|
if p1.BaseURL != "" {
|
|
if got := env1["ANTHROPIC_BASE_URL"]; got != p1.BaseURL {
|
|
t.Errorf("provider[1] ANTHROPIC_BASE_URL = %q, want %q", got, p1.BaseURL)
|
|
}
|
|
}
|
|
if p1.Model != "" {
|
|
if got := env1["ANTHROPIC_MODEL"]; got != p1.Model {
|
|
t.Errorf("provider[1] ANTHROPIC_MODEL = %q, want %q", got, p1.Model)
|
|
}
|
|
}
|
|
|
|
if p0.BaseURL != p1.BaseURL {
|
|
if env0["ANTHROPIC_BASE_URL"] == env1["ANTHROPIC_BASE_URL"] {
|
|
t.Error("providers have different base_urls but env didn't change after switch")
|
|
}
|
|
}
|
|
if p0.Model != p1.Model && p0.Model != "" && p1.Model != "" {
|
|
if env0["ANTHROPIC_MODEL"] == env1["ANTHROPIC_MODEL"] {
|
|
t.Error("providers have different models but ANTHROPIC_MODEL didn't change after switch")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIntegration_ProviderSwitch_SessionStartModel(t *testing.T) {
|
|
cfg := skipIfNoConfig(t)
|
|
|
|
name, providers, workDir := findProjectProviders(cfg, "claudecode")
|
|
if name == "" {
|
|
t.Skip("no claudecode project with providers found")
|
|
}
|
|
if len(providers) < 2 {
|
|
t.Skipf("project %q has only %d provider(s), need at least 2", name, len(providers))
|
|
}
|
|
if workDir == "" {
|
|
workDir = "/tmp"
|
|
}
|
|
|
|
cliBin, err := exec.LookPath("claude")
|
|
if err != nil {
|
|
t.Skipf("claude CLI not found: %v", err)
|
|
}
|
|
|
|
for _, prov := range providers[:2] {
|
|
t.Run(prov.Name, func(t *testing.T) {
|
|
a := &Agent{
|
|
model: "default-model",
|
|
providers: providers,
|
|
activeIdx: -1,
|
|
workDir: workDir,
|
|
cliBin: cliBin,
|
|
}
|
|
a.SetActiveProvider(prov.Name)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
sess, err := a.StartSession(ctx, "")
|
|
if err != nil {
|
|
t.Fatalf("StartSession failed: %v", err)
|
|
}
|
|
defer sess.Close()
|
|
|
|
cs := sess.(*claudeSession)
|
|
envMap := envSliceToMap(cs.cmd.Env)
|
|
|
|
if prov.Model != "" {
|
|
found := false
|
|
for _, arg := range cs.cmd.Args {
|
|
if arg == prov.Model {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("--model %q not found in args: %v", prov.Model, cs.cmd.Args)
|
|
}
|
|
}
|
|
|
|
if prov.BaseURL != "" {
|
|
gotURL := envMap["ANTHROPIC_BASE_URL"]
|
|
if gotURL == "" {
|
|
t.Error("ANTHROPIC_BASE_URL not set in session env")
|
|
} else if !strings.Contains(gotURL, "127.0.0.1") && gotURL != prov.BaseURL {
|
|
t.Errorf("ANTHROPIC_BASE_URL = %q, want %q (or proxy URL)", gotURL, prov.BaseURL)
|
|
}
|
|
}
|
|
|
|
if prov.Model != "" {
|
|
if got := envMap["ANTHROPIC_MODEL"]; got != prov.Model {
|
|
t.Errorf("ANTHROPIC_MODEL = %q, want %q", got, prov.Model)
|
|
}
|
|
}
|
|
|
|
t.Logf("session started OK: pid=%d, model=%s, env_model=%s, env_base_url=%s",
|
|
cs.cmd.Process.Pid,
|
|
prov.Model,
|
|
envMap["ANTHROPIC_MODEL"],
|
|
envMap["ANTHROPIC_BASE_URL"])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntegration_CodexProvider_EnvVars(t *testing.T) {
|
|
cfg := skipIfNoConfig(t)
|
|
|
|
name, providers, _ := findProjectProviders(cfg, "codex")
|
|
if name == "" {
|
|
t.Skip("no codex project with providers found")
|
|
}
|
|
|
|
for _, prov := range providers {
|
|
t.Run(prov.Name, func(t *testing.T) {
|
|
t.Logf("codex provider: name=%s base_url=%s model=%s", prov.Name, prov.BaseURL, prov.Model)
|
|
if prov.BaseURL == "" {
|
|
t.Error("codex provider should have a base_url")
|
|
}
|
|
if prov.Model == "" {
|
|
t.Error("codex provider should have a model")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntegration_AgentTypeChange_FiltersProviders(t *testing.T) {
|
|
cfg := skipIfNoConfig(t)
|
|
|
|
if len(cfg.Providers) == 0 {
|
|
t.Skip("no global providers in config")
|
|
}
|
|
|
|
globalByName := make(map[string]config.ProviderConfig, len(cfg.Providers))
|
|
for _, p := range cfg.Providers {
|
|
globalByName[p.Name] = p
|
|
}
|
|
|
|
for i := range cfg.Projects {
|
|
proj := &cfg.Projects[i]
|
|
if proj.Agent.Type != "claudecode" || len(proj.Agent.ProviderRefs) == 0 {
|
|
continue
|
|
}
|
|
|
|
var compatible, incompatible []string
|
|
for _, ref := range proj.Agent.ProviderRefs {
|
|
gp, ok := globalByName[ref]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if len(gp.AgentTypes) > 0 {
|
|
hasCodex := false
|
|
for _, at := range gp.AgentTypes {
|
|
if at == "codex" {
|
|
hasCodex = true
|
|
}
|
|
}
|
|
if hasCodex {
|
|
compatible = append(compatible, ref)
|
|
} else {
|
|
incompatible = append(incompatible, ref)
|
|
}
|
|
} else {
|
|
compatible = append(compatible, ref)
|
|
}
|
|
}
|
|
|
|
if len(incompatible) == 0 {
|
|
continue
|
|
}
|
|
|
|
t.Logf("project %q: switching claudecode→codex: keep=%v remove=%v",
|
|
proj.Name, compatible, incompatible)
|
|
t.Logf("provider filtering validated: %d provider(s) would be removed on agent type change", len(incompatible))
|
|
return
|
|
}
|
|
t.Skip("no project with incompatible providers found for agent type change test")
|
|
}
|