414 lines · 14.1 KB · Go
RawDownload
package pluginhost import (	"context"	"encoding/json"	"reflect"	"strings"	"testing" 	"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginabi"	"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi") func TestRPCCapabilitiesIncludeFrontendAuthProviderExclusive(t *testing.T) {	plugin := pluginapi.Plugin{		Capabilities: pluginapi.Capabilities{			FrontendAuthProvider:          frontendAuthProviderFunc{identifier: "exclusive-auth"},			FrontendAuthProviderExclusive: true,		},	} 	caps := rpcCapabilitiesFromPlugin(plugin)	if !caps.FrontendAuthProvider {		t.Fatal("FrontendAuthProvider = false, want true")	}	if !caps.FrontendAuthProviderExclusive {		t.Fatal("FrontendAuthProviderExclusive = false, want true")	} 	raw, errMarshal := json.Marshal(caps)	if errMarshal != nil {		t.Fatalf("Marshal() error = %v", errMarshal)	}	if !json.Valid(raw) {		t.Fatalf("marshaled capabilities are invalid JSON: %s", raw)	}	var decoded map[string]any	if errUnmarshal := json.Unmarshal(raw, &decoded); errUnmarshal != nil {		t.Fatalf("Unmarshal() error = %v", errUnmarshal)	}	if decoded["frontend_auth_provider_exclusive"] != true {		t.Fatalf("frontend_auth_provider_exclusive = %#v, want true", decoded["frontend_auth_provider_exclusive"])	}} func TestRPCCapabilitiesIncludeScheduler(t *testing.T) {	plugin := pluginapi.Plugin{		Capabilities: pluginapi.Capabilities{			Scheduler: schedulerFunc(func(context.Context, pluginapi.SchedulerPickRequest) (pluginapi.SchedulerPickResponse, error) {				return pluginapi.SchedulerPickResponse{}, nil			}),		},	} 	caps := rpcCapabilitiesFromPlugin(plugin)	if !caps.Scheduler {		t.Fatal("Scheduler = false, want true")	} 	raw, errMarshal := json.Marshal(caps)	if errMarshal != nil {		t.Fatalf("Marshal() error = %v", errMarshal)	}	if !json.Valid(raw) {		t.Fatalf("marshaled capabilities are invalid JSON: %s", raw)	}	var decoded map[string]any	if errUnmarshal := json.Unmarshal(raw, &decoded); errUnmarshal != nil {		t.Fatalf("Unmarshal() error = %v", errUnmarshal)	}	if decoded["scheduler"] != true {		t.Fatalf("scheduler = %#v, want true", decoded["scheduler"])	}} func TestRPCCapabilitiesIncludeModelRouter(t *testing.T) {	plugin := pluginapi.Plugin{		Capabilities: pluginapi.Capabilities{			ModelRouter: modelRouterFunc(func(context.Context, pluginapi.ModelRouteRequest) (pluginapi.ModelRouteResponse, error) {				return pluginapi.ModelRouteResponse{}, nil			}),		},	} 	caps := rpcCapabilitiesFromPlugin(plugin)	if !caps.ModelRouter {		t.Fatal("ModelRouter = false, want true")	} 	raw, errMarshal := json.Marshal(caps)	if errMarshal != nil {		t.Fatalf("Marshal() error = %v", errMarshal)	}	if !json.Valid(raw) {		t.Fatalf("marshaled capabilities are invalid JSON: %s", raw)	}	var decoded map[string]any	if errUnmarshal := json.Unmarshal(raw, &decoded); errUnmarshal != nil {		t.Fatalf("Unmarshal() error = %v", errUnmarshal)	}	if decoded["model_router"] != true {		t.Fatalf("model_router = %#v, want true", decoded["model_router"])	}} func TestRPCCapabilitiesIncludeQuotaProvider(t *testing.T) {	plugin := pluginapi.Plugin{		Capabilities: pluginapi.Capabilities{			QuotaProvider: &testQuotaProvider{identifier: "quota-test"},		},	} 	caps := rpcCapabilitiesFromPlugin(plugin)	if !caps.QuotaProvider {		t.Fatal("QuotaProvider = false, want true")	} 	raw, errMarshal := json.Marshal(caps)	if errMarshal != nil {		t.Fatalf("Marshal() error = %v", errMarshal)	}	if !json.Valid(raw) {		t.Fatalf("marshaled capabilities are invalid JSON: %s", raw)	}	var decoded map[string]any	if errUnmarshal := json.Unmarshal(raw, &decoded); errUnmarshal != nil {		t.Fatalf("Unmarshal() error = %v", errUnmarshal)	}	if decoded["quota_provider"] != true {		t.Fatalf("quota_provider = %#v, want true", decoded["quota_provider"])	}} func TestRegisterRPCPluginSendsHostSchemaVersion(t *testing.T) {	lookup := newTestSymbolLookup(&testPlugin{		registerResult: validTestPlugin("schema"),	}) 	registered, errRegister := registerRPCPlugin(context.Background(), nil, "schema", lookup, pluginabi.MethodPluginRegister, []byte("mode: test"))	if errRegister != nil {		t.Fatalf("registerRPCPlugin() error = %v", errRegister)	}	if lookup.lastLifecycle.SchemaVersion != pluginabi.SchemaVersion {		t.Fatalf("lifecycle schema_version = %d, want %d", lookup.lastLifecycle.SchemaVersion, pluginabi.SchemaVersion)	}	if registered.SchemaVersion != pluginabi.SchemaVersion {		t.Fatalf("registered SchemaVersion = %d, want %d", registered.SchemaVersion, pluginabi.SchemaVersion)	}	if string(lookup.lastLifecycle.ConfigYAML) != "mode: test" {		t.Fatalf("lifecycle config = %q, want input config", lookup.lastLifecycle.ConfigYAML)	}} func TestRegisterRPCPluginRejectsFutureSchemaVersion(t *testing.T) {	lookup := newTestSymbolLookup(&testPlugin{		registerResult: validTestPlugin("future-schema"),	})	lookup.schemaVersion = pluginabi.SchemaVersion + 1 	_, errRegister := registerRPCPlugin(context.Background(), nil, "future-schema", lookup, pluginabi.MethodPluginRegister, nil)	if errRegister == nil || !strings.Contains(errRegister.Error(), "schema version") {		t.Fatalf("registerRPCPlugin() error = %v, want unsupported schema version", errRegister)	}} func TestRegisterRPCPluginAcceptsModelRouterOnSchema1(t *testing.T) {	plugin := validTestPlugin("router-schema1")	plugin.Capabilities.ModelRouter = modelRouterFunc(func(context.Context, pluginapi.ModelRouteRequest) (pluginapi.ModelRouteResponse, error) {		return pluginapi.ModelRouteResponse{}, nil	})	lookup := newTestSymbolLookup(&testPlugin{registerResult: plugin})	lookup.schemaVersion = 1 	registered, errRegister := registerRPCPlugin(context.Background(), nil, "router-schema1", lookup, pluginabi.MethodPluginRegister, nil)	if errRegister != nil {		t.Fatalf("registerRPCPlugin() error = %v, want model_router on schema 1", errRegister)	}	if registered.Capabilities.ModelRouter == nil {		t.Fatal("ModelRouter = nil, want adapter")	}	if registered.SchemaVersion != 1 {		t.Fatalf("registered SchemaVersion = %d, want 1", registered.SchemaVersion)	}} func TestRPCModelRouteUsesAdapter(t *testing.T) {	var routeCalls int	var gotReq pluginapi.ModelRouteRequest	lookup := newTestSymbolLookup(&testPlugin{		registerResult: pluginapi.Plugin{			Metadata: pluginapi.Metadata{				Name:             "router",				Version:          "1.0.0",				Author:           "test",				GitHubRepository: "https://github.com/router-for-me/CLIProxyAPI",			},			Capabilities: pluginapi.Capabilities{				ModelRouter: modelRouterFunc(func(ctx context.Context, req pluginapi.ModelRouteRequest) (pluginapi.ModelRouteResponse, error) {					routeCalls++					gotReq = req					return pluginapi.ModelRouteResponse{						Handled:    true,						TargetKind: pluginapi.ModelRouteTargetExecutor,						Target:     "claude-websearch-plugin",						Reason:     "typed websearch",					}, nil				}),			},		},	}) 	plugin, errRegister := registerRPCPlugin(context.Background(), nil, "router", lookup, pluginabi.MethodPluginRegister, nil)	if errRegister != nil {		t.Fatalf("registerRPCPlugin() error = %v", errRegister)	}	if plugin.Capabilities.ModelRouter == nil {		t.Fatal("ModelRouter = nil, want adapter")	} 	req := pluginapi.ModelRouteRequest{		SourceFormat:   "anthropic",		RequestedModel: "claude-sonnet",		Stream:         true,		Headers:        map[string][]string{"X-Test": {"one", "two"}},		Query:          map[string][]string{"beta": {"true"}},		Body:           []byte(`{"tools":[{"type":"web_search_20250305","name":"web_search"}]}`),		Metadata: map[string]any{			"keep": "value",		},	}	resp, errRoute := plugin.Capabilities.ModelRouter.RouteModel(context.Background(), req)	if errRoute != nil {		t.Fatalf("ModelRouter.RouteModel() error = %v", errRoute)	}	if !resp.Handled || resp.Target != "claude-websearch-plugin" || resp.Reason != "typed websearch" {		t.Fatalf("ModelRouter.RouteModel() response = %#v", resp)	}	if routeCalls != 1 {		t.Fatalf("route calls = %d, want 1", routeCalls)	}	if gotReq.SourceFormat != req.SourceFormat || gotReq.RequestedModel != req.RequestedModel ||		gotReq.Stream != req.Stream || string(gotReq.Body) != string(req.Body) {		t.Fatalf("route request main fields = %#v, want %#v", gotReq, req)	}	if !reflect.DeepEqual(gotReq.Headers, req.Headers) {		t.Fatalf("route request headers = %#v, want %#v", gotReq.Headers, req.Headers)	}	if !reflect.DeepEqual(gotReq.Query, req.Query) {		t.Fatalf("route request query = %#v, want %#v", gotReq.Query, req.Query)	}	if gotReq.Metadata["keep"] != "value" {		t.Fatalf("route request metadata = %#v", gotReq.Metadata)	}} func TestRPCSchedulerPickUsesAdapter(t *testing.T) {	var pickCalls int	var gotReq pluginapi.SchedulerPickRequest	lookup := newTestSymbolLookup(&testPlugin{		registerResult: pluginapi.Plugin{			Metadata: pluginapi.Metadata{				Name:             "scheduler",				Version:          "1.0.0",				Author:           "test",				GitHubRepository: "https://github.com/router-for-me/CLIProxyAPI",			},			Capabilities: pluginapi.Capabilities{				Scheduler: schedulerFunc(func(ctx context.Context, req pluginapi.SchedulerPickRequest) (pluginapi.SchedulerPickResponse, error) {					pickCalls++					gotReq = req					return pluginapi.SchedulerPickResponse{						AuthID:  "auth-2",						Handled: true,					}, nil				}),			},		},	}) 	plugin, errRegister := registerRPCPlugin(context.Background(), nil, "scheduler", lookup, pluginabi.MethodPluginRegister, nil)	if errRegister != nil {		t.Fatalf("registerRPCPlugin() error = %v", errRegister)	}	if plugin.Capabilities.Scheduler == nil {		t.Fatal("Scheduler = nil, want adapter")	} 	req := pluginapi.SchedulerPickRequest{		Provider:  "openai",		Providers: []string{"openai", "codex"},		Model:     "gpt-5.4",		Stream:    true,		Options: pluginapi.SchedulerOptions{			Headers: map[string][]string{"X-Test": {"one", "two"}},		},		Candidates: []pluginapi.SchedulerAuthCandidate{			{				ID:         "auth-1",				Provider:   "openai",				Priority:   10,				Status:     "ready",				Attributes: map[string]string{"region": "us"},			},			{				ID:         "auth-2",				Provider:   "codex",				Priority:   20,				Status:     "ready",				Attributes: map[string]string{"region": "eu"},			},		},	}	resp, errPick := plugin.Capabilities.Scheduler.Pick(context.Background(), req)	if errPick != nil {		t.Fatalf("Scheduler.Pick() error = %v", errPick)	}	if resp.AuthID != "auth-2" || !resp.Handled {		t.Fatalf("Scheduler.Pick() response = %#v, want auth-2 handled", resp)	}	if pickCalls != 1 {		t.Fatalf("scheduler pick calls = %d, want 1", pickCalls)	}	if gotReq.Provider != req.Provider || !reflect.DeepEqual(gotReq.Providers, req.Providers) ||		gotReq.Model != req.Model || gotReq.Stream != req.Stream {		t.Fatalf("scheduler request main fields = %#v, want %#v", gotReq, req)	}	if !reflect.DeepEqual(gotReq.Options.Headers, req.Options.Headers) {		t.Fatalf("scheduler request headers = %#v, want %#v", gotReq.Options.Headers, req.Options.Headers)	}	if len(gotReq.Candidates) != len(req.Candidates) {		t.Fatalf("scheduler candidates len = %d, want %d", len(gotReq.Candidates), len(req.Candidates))	}	for index := range req.Candidates {		gotCandidate := gotReq.Candidates[index]		wantCandidate := req.Candidates[index]		if gotCandidate.ID != wantCandidate.ID ||			gotCandidate.Provider != wantCandidate.Provider ||			gotCandidate.Priority != wantCandidate.Priority ||			gotCandidate.Status != wantCandidate.Status ||			!reflect.DeepEqual(gotCandidate.Attributes, wantCandidate.Attributes) {			t.Fatalf("scheduler candidate[%d] = %#v, want %#v", index, gotCandidate, wantCandidate)		}	}} func TestSanitizePluginRequestScheduler(t *testing.T) {	req := pluginapi.SchedulerPickRequest{		Provider:  "openai",		Providers: []string{"openai", "codex"},		Model:     "gpt-5.4",		Stream:    true,		Options: pluginapi.SchedulerOptions{			Headers: map[string][]string{"X-Test": {"one", "two"}},			Metadata: map[string]any{				"keep": "value",				"drop": make(chan struct{}),			},		},		Candidates: []pluginapi.SchedulerAuthCandidate{			{				ID:         "auth-1",				Provider:   "openai",				Priority:   10,				Status:     "ready",				Attributes: map[string]string{"region": "us"},				Metadata: map[string]any{					"keep": "candidate",					"drop": make(chan struct{}),				},			},		},	} 	raw, errMarshal := json.Marshal(sanitizePluginRequest(req))	if errMarshal != nil {		t.Fatalf("Marshal(sanitized scheduler request) error = %v", errMarshal)	}	var decoded pluginapi.SchedulerPickRequest	if errUnmarshal := json.Unmarshal(raw, &decoded); errUnmarshal != nil {		t.Fatalf("Unmarshal(sanitized scheduler request) error = %v", errUnmarshal)	} 	if decoded.Provider != req.Provider || !reflect.DeepEqual(decoded.Providers, req.Providers) ||		decoded.Model != req.Model || decoded.Stream != req.Stream {		t.Fatalf("scheduler request main fields = %#v, want %#v", decoded, req)	}	if !reflect.DeepEqual(decoded.Options.Headers, req.Options.Headers) {		t.Fatalf("scheduler request headers = %#v, want %#v", decoded.Options.Headers, req.Options.Headers)	}	if decoded.Options.Metadata["keep"] != "value" {		t.Fatalf("scheduler options metadata keep = %#v, want value", decoded.Options.Metadata["keep"])	}	if _, ok := decoded.Options.Metadata["drop"]; ok {		t.Fatalf("scheduler options metadata drop survived sanitize: %#v", decoded.Options.Metadata)	}	if len(decoded.Candidates) != 1 {		t.Fatalf("scheduler candidates len = %d, want 1", len(decoded.Candidates))	}	gotCandidate := decoded.Candidates[0]	wantCandidate := req.Candidates[0]	if gotCandidate.ID != wantCandidate.ID ||		gotCandidate.Provider != wantCandidate.Provider ||		gotCandidate.Priority != wantCandidate.Priority ||		gotCandidate.Status != wantCandidate.Status ||		!reflect.DeepEqual(gotCandidate.Attributes, wantCandidate.Attributes) {		t.Fatalf("scheduler candidate = %#v, want %#v", gotCandidate, wantCandidate)	}	if gotCandidate.Metadata["keep"] != "candidate" {		t.Fatalf("scheduler candidate metadata keep = %#v, want candidate", gotCandidate.Metadata["keep"])	}	if _, ok := gotCandidate.Metadata["drop"]; ok {		t.Fatalf("scheduler candidate metadata drop survived sanitize: %#v", gotCandidate.Metadata)	}}