540 lines · 17.8 KB · Go
RawDownload
package pluginhost import (	"context"	"encoding/json"	"os"	"path/filepath"	"reflect"	"testing"	"time" 	"github.com/router-for-me/CLIProxyAPI/v7/internal/config"	"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi") func TestAuthProviderDiscovery(t *testing.T) {	host := newHostWithRecords(		capabilityRecord{			id:       "high",			priority: 20,			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{identifier: " High-Provider "},			}},		},		capabilityRecord{			id:       "low",			priority: 10,			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{identifier: "low-provider"},			}},		},		capabilityRecord{			id: "missing-auth-provider",			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{				ModelRegistrar: staticModelRegistrar("provider", "model"),			}},		},	) 	identifiers := host.AuthProviderIdentifiers()	if len(identifiers) != 2 || identifiers[0] != "high-provider" || identifiers[1] != "low-provider" {		t.Fatalf("AuthProviderIdentifiers() = %#v, want sorted normalized providers", identifiers)	}	if !host.HasAuthProvider(" HIGH-PROVIDER ") {		t.Fatal("HasAuthProvider(high-provider) = false, want true")	}	if host.HasAuthProvider("missing-provider") {		t.Fatal("HasAuthProvider(missing-provider) = true, want false")	}} func TestParseAuthDefaultsProviderFromRequest(t *testing.T) {	host := newHostWithRecords(capabilityRecord{		id: "auth-plugin",		plugin: pluginapi.Plugin{			Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{					identifier: "plugin-provider",					parseAuth: func(ctx context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {						return pluginapi.AuthParseResponse{							Handled: true,							Auth: pluginapi.AuthData{								ID: "auth-1",							},						}, nil					},				},			},		},	}) 	auth, handled, errParse := host.ParseAuth(context.Background(), pluginapi.AuthParseRequest{Provider: "plugin-provider"})	if errParse != nil {		t.Fatalf("ParseAuth() error = %v", errParse)	}	if !handled || auth == nil {		t.Fatalf("ParseAuth() handled=%t auth=%#v, want parsed auth", handled, auth)	}	if auth.Provider != "plugin-provider" || auth.Metadata["type"] != "plugin-provider" {		t.Fatalf("ParseAuth() auth = %#v, want plugin-provider defaults", auth)	}} func TestParseAuthDefaultsProviderFromAuthProviderIdentifier(t *testing.T) {	seenProvider := ""	host := newHostWithRecords(capabilityRecord{		id: "auth-plugin",		plugin: pluginapi.Plugin{			Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{					identifier: "Plugin-Provider",					parseAuth: func(ctx context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {						seenProvider = req.Provider						return pluginapi.AuthParseResponse{							Handled: true,							Auth: pluginapi.AuthData{								ID: "auth-1",							},						}, nil					},				},			},		},	}) 	auth, handled, errParse := host.ParseAuth(context.Background(), pluginapi.AuthParseRequest{})	if errParse != nil {		t.Fatalf("ParseAuth() error = %v", errParse)	}	if !handled || auth == nil {		t.Fatalf("ParseAuth() handled=%t auth=%#v, want parsed auth", handled, auth)	}	if seenProvider != "plugin-provider" {		t.Fatalf("plugin parse request provider = %q, want plugin-provider", seenProvider)	}	if auth.Provider != "plugin-provider" || auth.Metadata["type"] != "plugin-provider" {		t.Fatalf("ParseAuth() auth = %#v, want identifier provider fallback", auth)	}} func TestParseAuthsExpandsMultiplePluginAuths(t *testing.T) {	host := newHostWithRecords(capabilityRecord{		id: "geminicli",		plugin: pluginapi.Plugin{			Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{					identifier: "gemini-cli",					parseAuth: func(ctx context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {						return pluginapi.AuthParseResponse{							Handled: true,							Auths: []pluginapi.AuthData{								{									Provider:    "gemini-cli",									ID:          "user.json",									FileName:    "user.json",									StorageJSON: []byte(`{"type":"gemini-cli"}`),								},								{									Provider:    "gemini-cli",									ID:          "user-project-a.json",									FileName:    "user-project-a.json",									StorageJSON: []byte(`{"type":"gemini-cli","project_id":"project-a"}`),									Metadata:    map[string]any{"project_id": "project-a"},								},							},						}, nil					},				},			},		},	})	host.runtimeConfig = &config.Config{AuthDir: t.TempDir()} 	auths, handled, errParse := host.ParseAuths(context.Background(), pluginapi.AuthParseRequest{Provider: "gemini-cli"})	if errParse != nil {		t.Fatalf("ParseAuths() error = %v", errParse)	}	if !handled || len(auths) != 2 {		t.Fatalf("ParseAuths() handled=%t len=%d, want two auths", handled, len(auths))	}	if auths[1].Provider != "gemini-cli" || auths[1].Metadata["project_id"] != "project-a" {		t.Fatalf("second auth = %#v, want project-a virtual auth", auths[1])	}} func TestStartLoginPassesProviderBaseURLHostAndHTTPClient(t *testing.T) {	authDir := t.TempDir()	expiresAt := time.Now().Add(time.Minute).UTC()	called := false	host := newHostWithRecords(capabilityRecord{		id: "auth-plugin",		plugin: pluginapi.Plugin{			Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{					identifier: "plugin-provider",					startLogin: func(ctx context.Context, req pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {						called = true						if req.Provider != "plugin-provider" || req.BaseURL != "http://localhost:8080/login" {							t.Fatalf("StartLogin request = %#v, want provider/baseURL", req)						}						if req.Host.AuthDir != authDir || req.Host.ProxyURL != "http://proxy.local" || !req.Host.ForceModelPrefix {							t.Fatalf("StartLogin host = %#v, want configured summary", req.Host)						}						if req.HTTPClient == nil {							t.Fatal("StartLogin HTTPClient = nil, want host HTTP bridge")						}						return pluginapi.AuthLoginStartResponse{							Provider:  req.Provider,							URL:       "http://provider/login",							State:     "state-1",							ExpiresAt: expiresAt,						}, nil					},				},			},		},	})	host.runtimeConfig = &config.Config{		SDKConfig: config.SDKConfig{			ProxyURL:         "http://proxy.local",			ForceModelPrefix: true,		},		AuthDir: authDir,	} 	resp, handled, errStart := host.StartLogin(context.Background(), " Plugin-Provider ", "http://localhost:8080/login")	if errStart != nil {		t.Fatalf("StartLogin() error = %v", errStart)	}	if !handled || !called {		t.Fatalf("StartLogin() handled=%t called=%t, want handled call", handled, called)	}	if resp.Provider != "plugin-provider" || resp.URL != "http://provider/login" || resp.State != "state-1" || !resp.ExpiresAt.Equal(expiresAt) {		t.Fatalf("StartLogin() response = %#v, want plugin response", resp)	}} func TestStartLoginPassesMetadataAndClonesMap(t *testing.T) {	called := false	var receivedMetadata map[string]any	host := newHostWithRecords(capabilityRecord{		id: "auth-plugin",		plugin: pluginapi.Plugin{			Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{					identifier: "plugin-provider",					startLogin: func(ctx context.Context, req pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {						called = true						receivedMetadata = req.Metadata						return pluginapi.AuthLoginStartResponse{							Provider: req.Provider,							State:    "state-1",						}, nil					},				},			},		},	}) 	meta := map[string]any{"region": "us-east-1", "nested": "val"}	resp, handled, errStart := host.StartLogin(context.Background(), "plugin-provider", "http://localhost:8080/login", meta)	if errStart != nil {		t.Fatalf("StartLogin() error = %v", errStart)	}	if !handled || !called {		t.Fatalf("StartLogin() handled=%t called=%t, want handled call", handled, called)	}	if resp.State != "state-1" {		t.Fatalf("StartLogin() response = %#v, want state-1", resp)	}	if receivedMetadata == nil || receivedMetadata["region"] != "us-east-1" {		t.Fatalf("receivedMetadata = %#v, want region=us-east-1", receivedMetadata)	} 	// Verify metadata cloning: mutating original meta map must not mutate received map	meta["region"] = "mutated"	if receivedMetadata["region"] != "us-east-1" {		t.Fatalf("receivedMetadata was mutated when caller map changed: %#v", receivedMetadata)	} 	// Verify calling StartLogin without metadata sets req.Metadata to nil	called = false	receivedMetadata = nil	_, _, errNoMeta := host.StartLogin(context.Background(), "plugin-provider", "http://localhost:8080/login")	if errNoMeta != nil {		t.Fatalf("StartLogin() without metadata error = %v", errNoMeta)	}	if !called {		t.Fatal("StartLogin() without metadata was not called")	}	if receivedMetadata != nil {		t.Fatalf("receivedMetadata = %#v, want nil for empty metadata", receivedMetadata)	}} func TestPollLoginPassesProviderStateHostAndHTTPClient(t *testing.T) {	authDir := t.TempDir()	called := false	host := newHostWithRecords(capabilityRecord{		id: "auth-plugin",		plugin: pluginapi.Plugin{			Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{					identifier: "plugin-provider",					pollLogin: func(ctx context.Context, req pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {						called = true						if req.Provider != "plugin-provider" || req.State != "state-1" {							t.Fatalf("PollLogin request = %#v, want provider/state", req)						}						if req.Host.AuthDir != authDir || req.Host.ProxyURL != "http://proxy.local" || !req.Host.ForceModelPrefix {							t.Fatalf("PollLogin host = %#v, want configured summary", req.Host)						}						if req.HTTPClient == nil {							t.Fatal("PollLogin HTTPClient = nil, want host HTTP bridge")						}						return pluginapi.AuthLoginPollResponse{							Status:  pluginapi.AuthLoginStatusSuccess,							Message: "done",							Auth: pluginapi.AuthData{								Provider: "plugin-provider",								ID:       "auth-1",							},						}, nil					},				},			},		},	})	host.runtimeConfig = &config.Config{		SDKConfig: config.SDKConfig{			ProxyURL:         "http://proxy.local",			ForceModelPrefix: true,		},		AuthDir: authDir,	} 	resp, handled, errPoll := host.PollLogin(context.Background(), " Plugin-Provider ", " state-1 ")	if errPoll != nil {		t.Fatalf("PollLogin() error = %v", errPoll)	}	if !handled || !called {		t.Fatalf("PollLogin() handled=%t called=%t, want handled call", handled, called)	}	if resp.Status != pluginapi.AuthLoginStatusSuccess || resp.Message != "done" || resp.Auth.ID != "auth-1" {		t.Fatalf("PollLogin() response = %#v, want plugin response", resp)	}} func TestRefreshAuthPreservesAuthIndex(t *testing.T) {	host := newHostWithRecords(capabilityRecord{		id: "auth-plugin",		plugin: pluginapi.Plugin{			Capabilities: pluginapi.Capabilities{				AuthProvider: fakeAuthProvider{					identifier: "plugin-provider",					refreshAuth: func(ctx context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {						if req.AuthID != "auth-1" || req.AuthProvider != "plugin-provider" {							t.Fatalf("RefreshAuth request = %#v, want auth id/provider", req)						}						return pluginapi.AuthRefreshResponse{							Auth: pluginapi.AuthData{								Metadata: map[string]any{"access_token": "new-token"},							},						}, nil					},				},			},		},	}) 	auth := host.AuthDataToCoreAuth(pluginapi.AuthData{		Provider: "plugin-provider",		ID:       "auth-1",		Metadata: map[string]any{"access_token": "old-token"},	}, "", "")	if auth == nil {		t.Fatal("AuthDataToCoreAuth() = nil, want auth")	}	auth.Index = "home-index-1" 	refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), auth)	if errRefresh != nil {		t.Fatalf("RefreshAuth() error = %v", errRefresh)	}	if !handled || refreshed == nil {		t.Fatalf("RefreshAuth() handled=%t auth=%#v, want refreshed auth", handled, refreshed)	}	if refreshed.Index != "home-index-1" {		t.Fatalf("RefreshAuth() index = %q, want home-index-1", refreshed.Index)	}	if got := refreshed.Metadata["access_token"]; got != "new-token" {		t.Fatalf("RefreshAuth() access_token = %q, want new-token", got)	}} func TestHostAuthDataToCoreAuthRejectsMissingProviderAndUsesAuthDir(t *testing.T) {	authDir := t.TempDir()	host := New()	host.runtimeConfig = &config.Config{AuthDir: authDir}	path := filepath.Join(authDir, "nested", "auth.json") 	if auth := host.AuthDataToCoreAuth(pluginapi.AuthData{ID: "auth-1"}, path, "auth.json"); auth != nil {		t.Fatalf("AuthDataToCoreAuth() = %#v, want nil for missing provider", auth)	}	auth := host.AuthDataToCoreAuth(pluginapi.AuthData{Provider: "Plugin-Provider"}, path, "")	if auth == nil {		t.Fatal("AuthDataToCoreAuth() = nil, want auth")	}	if auth.Provider != "plugin-provider" || auth.ID != "nested/auth.json" {		t.Fatalf("AuthDataToCoreAuth() auth = %#v, want normalized provider and relative ID", auth)	}	if auth.Metadata["type"] != "plugin-provider" || auth.Attributes["path"] != path || auth.Attributes["source"] != path {		t.Fatalf("AuthDataToCoreAuth() metadata=%#v attributes=%#v, want path/source/type", auth.Metadata, auth.Attributes)	}} func TestPluginTokenStorageMergesRawMetadataAndProviderType(t *testing.T) {	storage := &pluginTokenStorage{		provider: "plugin-provider",		rawJSON:  []byte(`{"old":"value","type":"old-provider"}`),	}	storage.SetMetadata(map[string]any{		"new": "value",		"old": "override",	}) 	raw := storage.RawJSON()	var decoded map[string]any	if errUnmarshal := json.Unmarshal(raw, &decoded); errUnmarshal != nil {		t.Fatalf("RawJSON() decode error = %v", errUnmarshal)	}	if decoded["old"] != "override" || decoded["new"] != "value" || decoded["type"] != "plugin-provider" {		t.Fatalf("RawJSON() decoded = %#v, want merged metadata and provider type", decoded)	} 	path := filepath.Join(t.TempDir(), "auth.json")	if errSave := storage.SaveTokenToFile(path); errSave != nil {		t.Fatalf("SaveTokenToFile() error = %v", errSave)	}	saved, errReadFile := os.ReadFile(path)	if errReadFile != nil {		t.Fatalf("ReadFile(saved token) error = %v", errReadFile)	}	decoded = nil	if errUnmarshal := json.Unmarshal(saved, &decoded); errUnmarshal != nil {		t.Fatalf("saved token decode error = %v", errUnmarshal)	}	if decoded["old"] != "override" || decoded["new"] != "value" || decoded["type"] != "plugin-provider" {		t.Fatalf("saved token decoded = %#v, want merged metadata and provider type", decoded)	}} func TestPluginTokenStorageNormalizesCredentialMetadataKeys(t *testing.T) {	tests := []struct {		name     string		rawJSON  []byte		metadata map[string]any		want     map[string]any	}{		{			name:    "legacy raw keys",			rawJSON: []byte(`{"request-retry":2,"disable-cooling":true,"provider-specific-key":"preserved"}`),			want: map[string]any{				"request_retry":         float64(2),				"disable_cooling":       true,				"provider-specific-key": "preserved",				"type":                  "plugin-provider",			},		},		{			name:    "canonical metadata wins",			rawJSON: []byte(`{"request-retry":2,"disable-cooling":true}`),			metadata: map[string]any{				"request_retry":   0,				"disable_cooling": false,			},			want: map[string]any{				"request_retry":   float64(0),				"disable_cooling": false,				"type":            "plugin-provider",			},		},	} 	for _, test := range tests {		t.Run(test.name, func(t *testing.T) {			storage := &pluginTokenStorage{				provider: "plugin-provider",				rawJSON:  test.rawJSON,			}			storage.SetMetadata(test.metadata) 			outputs := map[string][]byte{				"RawJSON": storage.RawJSON(),			}			path := filepath.Join(t.TempDir(), "auth.json")			if errSave := storage.SaveTokenToFile(path); errSave != nil {				t.Fatalf("SaveTokenToFile() error = %v", errSave)			}			saved, errReadFile := os.ReadFile(path)			if errReadFile != nil {				t.Fatalf("ReadFile(saved token) error = %v", errReadFile)			}			outputs["SaveTokenToFile"] = saved 			for outputName, payload := range outputs {				var decoded map[string]any				if errUnmarshal := json.Unmarshal(payload, &decoded); errUnmarshal != nil {					t.Fatalf("%s decode error = %v", outputName, errUnmarshal)				}				if !reflect.DeepEqual(decoded, test.want) {					t.Errorf("%s decoded = %#v, want %#v", outputName, decoded, test.want)				}				if _, exists := decoded["request-retry"]; exists {					t.Errorf("%s retained request-retry: %#v", outputName, decoded)				}				if _, exists := decoded["disable-cooling"]; exists {					t.Errorf("%s retained disable-cooling: %#v", outputName, decoded)				}			}

Lines 1–500 of 540.