mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-11 03:13:24 +00:00
263 lines
7.7 KiB
Go
263 lines
7.7 KiB
Go
package providerresolve
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
type testTokenSource struct {
|
|
token string
|
|
}
|
|
|
|
func (s *testTokenSource) Token() (string, error) {
|
|
return s.token, nil
|
|
}
|
|
|
|
func (s *testTokenSource) RouteEligibility(context.Context) providers.RouteEligibility {
|
|
return providers.RouteEligibility{Class: providers.RouteEligibilityHealthy}
|
|
}
|
|
|
|
type stubProvider struct {
|
|
name string
|
|
model string
|
|
}
|
|
|
|
func (p *stubProvider) Chat(context.Context, providers.ChatRequest) (*providers.ChatResponse, error) {
|
|
return &providers.ChatResponse{FinishReason: "stop"}, nil
|
|
}
|
|
|
|
func (p *stubProvider) ChatStream(context.Context, providers.ChatRequest, func(providers.StreamChunk)) (*providers.ChatResponse, error) {
|
|
return &providers.ChatResponse{FinishReason: "stop"}, nil
|
|
}
|
|
|
|
func (p *stubProvider) DefaultModel() string { return p.model }
|
|
func (p *stubProvider) Name() string { return p.name }
|
|
|
|
func TestResolveConfiguredProviderKeepsNonCodexBase(t *testing.T) {
|
|
tenantID := uuid.New()
|
|
registry := providers.NewRegistry(nil)
|
|
base := &stubProvider{name: "anthropic", model: "claude-sonnet-4"}
|
|
registry.RegisterForTenant(tenantID, base)
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex-backup",
|
|
&testTokenSource{token: "backup-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
))
|
|
|
|
agent := &store.AgentData{
|
|
TenantID: tenantID,
|
|
Provider: "anthropic",
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"strategy": "round_robin",
|
|
"extra_provider_names": ["openai-codex-backup"]
|
|
}`),
|
|
}
|
|
|
|
resolved, err := ResolveConfiguredProvider(registry, agent)
|
|
if err != nil {
|
|
t.Fatalf("ResolveConfiguredProvider() error = %v", err)
|
|
}
|
|
if resolved != base {
|
|
t.Fatalf("ResolveConfiguredProvider() returned %T, want original non-Codex provider", resolved)
|
|
}
|
|
}
|
|
|
|
func TestResolveConfiguredProviderUsesRouterForCodexAgents(t *testing.T) {
|
|
tenantID := uuid.New()
|
|
registry := providers.NewRegistry(nil)
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex",
|
|
&testTokenSource{token: "primary-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
))
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex-backup",
|
|
&testTokenSource{token: "backup-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
))
|
|
|
|
agent := &store.AgentData{
|
|
TenantID: tenantID,
|
|
Provider: "openai-codex",
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"strategy": "round_robin",
|
|
"extra_provider_names": ["openai-codex-backup"]
|
|
}`),
|
|
}
|
|
|
|
resolved, err := ResolveConfiguredProvider(registry, agent)
|
|
if err != nil {
|
|
t.Fatalf("ResolveConfiguredProvider() error = %v", err)
|
|
}
|
|
router, ok := resolved.(*providers.ChatGPTOAuthRouter)
|
|
if !ok {
|
|
t.Fatalf("ResolveConfiguredProvider() returned %T, want *providers.ChatGPTOAuthRouter", resolved)
|
|
}
|
|
if !router.HasAvailableProviders() {
|
|
t.Fatal("router.HasAvailableProviders() = false, want true")
|
|
}
|
|
if router.Name() != "openai-codex" {
|
|
t.Fatalf("router.Name() = %q, want %q", router.Name(), "openai-codex")
|
|
}
|
|
}
|
|
|
|
func TestResolveConfiguredProviderUsesProviderDefaultsWhenAgentHasNoOverride(t *testing.T) {
|
|
tenantID := uuid.New()
|
|
registry := providers.NewRegistry(nil)
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex",
|
|
&testTokenSource{token: "primary-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
).WithRoutingDefaults(store.ChatGPTOAuthStrategyRoundRobin, []string{"openai-codex-backup"}))
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex-backup",
|
|
&testTokenSource{token: "backup-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
))
|
|
|
|
agent := &store.AgentData{
|
|
TenantID: tenantID,
|
|
Provider: "openai-codex",
|
|
}
|
|
|
|
resolved, err := ResolveConfiguredProvider(registry, agent)
|
|
if err != nil {
|
|
t.Fatalf("ResolveConfiguredProvider() error = %v", err)
|
|
}
|
|
router, ok := resolved.(*providers.ChatGPTOAuthRouter)
|
|
if !ok {
|
|
t.Fatalf("ResolveConfiguredProvider() returned %T, want *providers.ChatGPTOAuthRouter", resolved)
|
|
}
|
|
if !router.HasAvailableProviders() {
|
|
t.Fatal("router.HasAvailableProviders() = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestResolveConfiguredProviderKeepsExplicitSingleAccountOverride(t *testing.T) {
|
|
tenantID := uuid.New()
|
|
registry := providers.NewRegistry(nil)
|
|
baseProvider := providers.NewCodexProvider(
|
|
"openai-codex",
|
|
&testTokenSource{token: "primary-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
).WithRoutingDefaults(store.ChatGPTOAuthStrategyRoundRobin, []string{"openai-codex-backup"})
|
|
registry.RegisterForTenant(tenantID, baseProvider)
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex-backup",
|
|
&testTokenSource{token: "backup-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
))
|
|
|
|
agent := &store.AgentData{
|
|
TenantID: tenantID,
|
|
Provider: "openai-codex",
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"strategy": "manual"
|
|
}`),
|
|
}
|
|
|
|
resolved, err := ResolveConfiguredProvider(registry, agent)
|
|
if err != nil {
|
|
t.Fatalf("ResolveConfiguredProvider() error = %v", err)
|
|
}
|
|
router, ok := resolved.(*providers.ChatGPTOAuthRouter)
|
|
if !ok {
|
|
t.Fatalf("ResolveConfiguredProvider() returned %T, want *providers.ChatGPTOAuthRouter", resolved)
|
|
}
|
|
if router.Name() != "openai-codex" {
|
|
t.Fatalf("router.Name() = %q, want %q", router.Name(), "openai-codex")
|
|
}
|
|
}
|
|
|
|
type blockedTokenSource struct {
|
|
token string
|
|
}
|
|
|
|
func (s *blockedTokenSource) Token() (string, error) {
|
|
return s.token, nil
|
|
}
|
|
|
|
func (s *blockedTokenSource) RouteEligibility(context.Context) providers.RouteEligibility {
|
|
return providers.RouteEligibility{Class: providers.RouteEligibilityBlocked, Reason: "reauth"}
|
|
}
|
|
|
|
func TestResolveConfiguredProviderReturnsRouterEvenWhenPrimaryNeedsFailover(t *testing.T) {
|
|
tenantID := uuid.New()
|
|
registry := providers.NewRegistry(nil)
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex",
|
|
&blockedTokenSource{token: "primary-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
))
|
|
registry.RegisterForTenant(tenantID, providers.NewCodexProvider(
|
|
"openai-codex-backup",
|
|
&testTokenSource{token: "backup-token"},
|
|
"http://127.0.0.1",
|
|
"gpt-5.4",
|
|
))
|
|
|
|
agent := &store.AgentData{
|
|
TenantID: tenantID,
|
|
Provider: "openai-codex",
|
|
ChatGPTOAuthRouting: json.RawMessage(`{
|
|
"strategy": "round_robin",
|
|
"extra_provider_names": ["openai-codex-backup"]
|
|
}`),
|
|
}
|
|
|
|
resolved, err := ResolveConfiguredProvider(registry, agent)
|
|
if err != nil {
|
|
t.Fatalf("ResolveConfiguredProvider() error = %v", err)
|
|
}
|
|
if _, ok := resolved.(*providers.ChatGPTOAuthRouter); !ok {
|
|
t.Fatalf("ResolveConfiguredProvider() returned %T, want *providers.ChatGPTOAuthRouter", resolved)
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentProviderWrapsModelFallback(t *testing.T) {
|
|
tenantID := uuid.New()
|
|
registry := providers.NewRegistry(nil)
|
|
base := &stubProvider{name: "primary", model: "primary-model"}
|
|
backup := &stubProvider{name: "backup", model: "backup-default"}
|
|
registry.RegisterForTenant(tenantID, base)
|
|
registry.RegisterForTenant(tenantID, backup)
|
|
|
|
agent := &store.AgentData{
|
|
TenantID: tenantID,
|
|
Provider: "primary",
|
|
Model: "primary-model",
|
|
ModelFallback: json.RawMessage(`{
|
|
"enabled": true,
|
|
"strategy": "priority_order",
|
|
"candidates": [
|
|
{"provider": "backup", "model": "backup-model"}
|
|
]
|
|
}`),
|
|
}
|
|
|
|
resolved, err := ResolveAgentProvider(registry, agent)
|
|
if err != nil {
|
|
t.Fatalf("ResolveAgentProvider() error = %v", err)
|
|
}
|
|
fallback, ok := resolved.(*providers.ModelFallbackProvider)
|
|
if !ok {
|
|
t.Fatalf("ResolveAgentProvider() returned %T, want *providers.ModelFallbackProvider", resolved)
|
|
}
|
|
if fallback.PrimaryProvider() != base {
|
|
t.Fatalf("PrimaryProvider() = %T, want original base provider", fallback.PrimaryProvider())
|
|
}
|
|
}
|