Ctrl K
ComunidadGuías…

internal/pluginhost/auth_provider_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package pluginhost

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"reflect"
	"testing"
	"time"

	"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
	sdkauth "github.com/router-for-me/CLIProxyAPI/v8/sdk/auth"
	coreauth "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/auth"
	"github.com/router-for-me/CLIProxyAPI/v8/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 TestRefreshAuthPreservesFilePriorityWhenPluginReturnsPartialAuth(t *testing.T) {
	filePath := filepath.Join(t.TempDir(), "plugin.json")
	host := newHostWithRecords(capabilityRecord{
		id: "auth-plugin",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{
				identifier: "plugin-provider",
				refreshAuth: func(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
					return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{
						Metadata:   map[string]any{"access_token": "new-token", "priority": float64(0)},
						Attributes: map[string]string{"plugin_attr": "new-value", "priority": "0"},
					}}, nil
				},
			},
		}},
	})
	auth := &coreauth.Auth{
		ID:       "auth-1",
		Provider: "plugin-provider",
		Attributes: map[string]string{
			coreauth.AttributeSourceBackend: coreauth.AuthSourceFile,
			coreauth.AttributePath:          filePath,
			coreauth.AttributeSource:        filePath,
			coreauth.AttributeFilePriority:  "true",
			"priority":                      "1",
		},
		Metadata: map[string]any{"priority": float64(1), "access_token": "old-token"},
	}
	refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), auth)
	if errRefresh != nil || !handled || refreshed == nil {
		t.Fatalf("RefreshAuth() auth = %#v, handled = %t, error = %v", refreshed, handled, errRefresh)
	}
	if got := refreshed.Attributes["priority"]; got != "1" {
		t.Errorf("refreshed priority attribute = %q, want 1", got)
	}
	if got := refreshed.Metadata["priority"]; got != float64(1) {
		t.Errorf("refreshed priority metadata = %v, want 1", got)
	}
	if _, errSave := sdkauth.NewFileTokenStore().Save(context.Background(), refreshed); errSave != nil {
		t.Fatalf("Save() refreshed auth: %v", errSave)
	}
	payload, errRead := os.ReadFile(filePath)
	if errRead != nil {
		t.Fatalf("read saved auth file: %v", errRead)
	}
	var saved map[string]any
	if errUnmarshal := json.Unmarshal(payload, &saved); errUnmarshal != nil {
		t.Fatalf("decode saved auth file: %v", errUnmarshal)
	}
	if got := saved["priority"]; got != float64(1) {
		t.Errorf("saved priority = %v, want 1", got)
	}
}

func TestRefreshAuthAllowsPluginPriorityWithoutFilePriority(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "auth-plugin",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{
				identifier: "plugin-provider",
				refreshAuth: func(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
					return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{
						Metadata:   map[string]any{"priority": float64(2)},
						Attributes: map[string]string{"priority": "2", coreauth.AttributeFilePriority: "true"},
					}}, nil
				},
			},
		}},
	})
	auth := &coreauth.Auth{
		ID:       "auth-1",
		Provider: "plugin-provider",
		Attributes: map[string]string{
			coreauth.AttributeSourceBackend: coreauth.AuthSourceFile,
			"priority":                      "1",
		},
		Metadata: map[string]any{"priority": float64(1)},
	}
	refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), auth)
	if errRefresh != nil || !handled || refreshed == nil {
		t.Fatalf("RefreshAuth() auth = %#v, handled = %t, error = %v", refreshed, handled, errRefresh)
	}
	if refreshed.Attributes["priority"] != "2" || refreshed.Metadata["priority"] != float64(2) {
		t.Fatalf("refreshed priority = %q/%v, want 2/2", refreshed.Attributes["priority"], refreshed.Metadata["priority"])
	}
	if _, inherited := refreshed.Attributes[coreauth.AttributeFilePriority]; inherited {
		t.Fatal("plugin-supplied file priority marker survived refresh without file priority")
	}
}

func TestRefreshAuthPreservesDisabledStateWhenPluginOmitsFlag_Issue7225(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "auth-plugin",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{
				identifier: "plugin-provider",
				refreshAuth: func(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
					return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{
						Metadata: map[string]any{"access_token": "new-token"},
					}}, nil
				},
			},
		}},
	})
	auth := &coreauth.Auth{
		ID:       "auth-1",
		Provider: "plugin-provider",
		Disabled: true,
		Status:   coreauth.StatusDisabled,
		Metadata: map[string]any{"access_token": "old-token"},
	}
	refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), auth)
	if errRefresh != nil || !handled || refreshed == nil {
		t.Fatalf("RefreshAuth() auth = %#v, handled = %t, error = %v", refreshed, handled, errRefresh)
	}
	if !refreshed.Disabled || refreshed.Status != coreauth.StatusDisabled {
		t.Fatalf("RefreshAuth() disabled=%t status=%q, want disabled auth to stay disabled", refreshed.Disabled, refreshed.Status)
	}
	if got := refreshed.Metadata["access_token"]; got != "new-token" {
		t.Fatalf("RefreshAuth() access_token = %q, want new-token", got)
	}
}

func TestRefreshAuthPreservesDisabledStatusWithoutFlag_Issue7225(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "auth-plugin",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{
				identifier: "plugin-provider",
				refreshAuth: func(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
					return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{
						Metadata: map[string]any{"access_token": "new-token"},
					}}, nil
				},
			},
		}},
	})
	auth := &coreauth.Auth{
		ID:       "auth-1",
		Provider: "plugin-provider",
		Status:   coreauth.StatusDisabled,
		Metadata: map[string]any{"access_token": "old-token"},
	}
	refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), auth)
	if errRefresh != nil || !handled || refreshed == nil {
		t.Fatalf("RefreshAuth() auth = %#v, handled = %t, error = %v", refreshed, handled, errRefresh)
	}
	if !refreshed.Disabled || refreshed.Status != coreauth.StatusDisabled {
		t.Fatalf("RefreshAuth() disabled=%t status=%q, want disabled auth to stay disabled", refreshed.Disabled, refreshed.Status)
	}
}

func TestRefreshAuthDisabledStateOneWayPreservation_Issue7225(t *testing.T) {
	tests := []struct {
		name           string
		pluginDisabled bool
		wantDisabled   bool
	}{
		{name: "enabled auth stays enabled when plugin omits flag", pluginDisabled: false, wantDisabled: false},
		{name: "enabled auth becomes disabled when plugin returns disabled", pluginDisabled: true, wantDisabled: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			host := newHostWithRecords(capabilityRecord{
				id: "auth-plugin",
				plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
					AuthProvider: fakeAuthProvider{
						identifier: "plugin-provider",
						refreshAuth: func(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
							return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{
								Disabled: tt.pluginDisabled,
								Metadata: map[string]any{"access_token": "new-token"},
							}}, nil
						},
					},
				}},
			})
			auth := &coreauth.Auth{
				ID:       "auth-1",
				Provider: "plugin-provider",
				Metadata: map[string]any{"access_token": "old-token"},
			}
			refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), auth)
			if errRefresh != nil || !handled || refreshed == nil {
				t.Fatalf("RefreshAuth() auth = %#v, handled = %t, error = %v", refreshed, handled, errRefresh)
			}
			if refreshed.Disabled != tt.wantDisabled {
				t.Fatalf("RefreshAuth() disabled=%t, want %t", refreshed.Disabled, tt.wantDisabled)
			}
			wantStatus := coreauth.StatusActive
			if tt.wantDisabled {
				wantStatus = coreauth.StatusDisabled
			}
			if refreshed.Status != wantStatus {
				t.Fatalf("RefreshAuth() status=%q, want %q", refreshed.Status, wantStatus)
			}
		})
	}
}

func TestPluginTokenStorageRemovesDeletedPriority(t *testing.T) {
	storage := &pluginTokenStorage{
		provider: "plugin-provider",
		rawJSON:  []byte(`{"type":"plugin-provider","priority":1,"access_token":"old"}`),
		meta:     map[string]any{"priority": float64(1), "access_token": "old"},
	}
	storage.SetMetadata(map[string]any{"access_token": "new"})
	var saved map[string]any
	if errUnmarshal := json.Unmarshal(storage.RawJSON(), &saved); errUnmarshal != nil {
		t.Fatalf("decode storage JSON: %v", errUnmarshal)
	}
	if _, exists := saved["priority"]; exists {
		t.Fatalf("deleted priority reappeared in storage JSON: %#v", saved)
	}
}

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)
				}
			}
		})
	}
}

func TestPluginTokenStorageSkipsUnchangedFile(t *testing.T) {
	path := filepath.Join(t.TempDir(), "auth.json")
	if errWriteFile := os.WriteFile(path, []byte(`{"disabled":false,"token":"secret","type":"plugin-provider"}`), 0o600); errWriteFile != nil {
		t.Fatalf("WriteFile() error = %v", errWriteFile)
	}
	before, errStatBefore := os.Stat(path)
	if errStatBefore != nil {
		t.Fatalf("Stat(before) error = %v", errStatBefore)
	}
	storage := &pluginTokenStorage{
		provider: "plugin-provider",
		rawJSON:  []byte(`{"token":"secret"}`),
	}
	storage.SetMetadata(map[string]any{"disabled": false})

	if errSave := storage.SaveTokenToFile(path); errSave != nil {
		t.Fatalf("SaveTokenToFile() error = %v", errSave)
	}
	after, errStatAfter := os.Stat(path)
	if errStatAfter != nil {
		t.Fatalf("Stat(after) error = %v", errStatAfter)
	}
	if !os.SameFile(before, after) {
		t.Fatal("SaveTokenToFile() replaced unchanged auth file, want write skipped")
	}
}

func TestPluginTokenStorageRejectsEmptyPayload(t *testing.T) {
	storage := &pluginTokenStorage{}
	if raw := storage.RawJSON(); raw != nil {
		t.Fatalf("RawJSON() = %q, want nil for empty payload", raw)
	}
	if errSave := storage.SaveTokenToFile(filepath.Join(t.TempDir(), "auth.json")); errSave == nil {
		t.Fatal("SaveTokenToFile() error = nil, want empty payload error")
	}
}

func TestRefreshAuth_MergesAttributesAndPreservesPath_Issue6119(t *testing.T) {
	filePath := filepath.Join(t.TempDir(), "plugin.json")
	host := newHostWithRecords(capabilityRecord{
		id: "auth-plugin",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{
				identifier: "plugin-provider",
				refreshAuth: func(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
					return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{
						Metadata:   map[string]any{"access_token": "new-token"},
						Attributes: map[string]string{"priority": "1"},
					}}, nil
				},
			},
		}},
	})

	testCases := []struct {
		name          string
		sourceBackend string
	}{
		{name: "file_backend", sourceBackend: coreauth.AuthSourceFile},
		{name: "postgres_backend", sourceBackend: "postgres"},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			auth := &coreauth.Auth{
				ID:       "auth-1",
				Provider: "plugin-provider",
				FileName: "plugin.json",
				Attributes: map[string]string{
					coreauth.AttributeSourceBackend: tc.sourceBackend,
					coreauth.AttributePath:          filePath,
					coreauth.AttributeSource:        filePath,
					"custom_env":                    "production",
				},
				Metadata: map[string]any{"access_token": "old-token"},
			}

			refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), auth)
			if errRefresh != nil || !handled || refreshed == nil {
				t.Fatalf("RefreshAuth() auth = %#v, handled = %t, error = %v", refreshed, handled, errRefresh)
			}

			if got := refreshed.Attributes[coreauth.AttributePath]; got != filePath {
				t.Errorf("refreshed path attribute = %q, want %q", got, filePath)
			}
			if got := refreshed.Attributes[coreauth.AttributeSource]; got != filePath {
				t.Errorf("refreshed source attribute = %q, want %q", got, filePath)
			}
			if got := refreshed.Attributes[coreauth.AttributeSourceBackend]; got != tc.sourceBackend {
				t.Errorf("refreshed source_backend attribute = %q, want %q", got, tc.sourceBackend)
			}
			if got := refreshed.Attributes["custom_env"]; got != "production" {
				t.Errorf("refreshed custom_env attribute = %q, want production", got)
			}
			if got := refreshed.Attributes["priority"]; got != "1" {
				t.Errorf("refreshed priority attribute = %q, want 1", got)
			}
		})
	}
}

func TestAuthProviderMultiPortalRegistrationAndRouting(t *testing.T) {
	t.Run("multi_identifier_provider_with_shared_executor", func(t *testing.T) {
		var startedProviders []string
		var polledProviders []string
		var refreshedProviders []string
		host := newHostWithRecords(capabilityRecord{
			id:       "omc-zcode-oauth",
			priority: 10,
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProvider: fakeAuthProvider{
						identifier:  " ZCode-ZAI ",
						identifiers: []string{" ZCode-ZAI ", "zcode-bigmodel", "zcode-zai"},
						parseAuth: func(_ context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
							return pluginapi.AuthParseResponse{
								Handled: true,
								Auth: pluginapi.AuthData{
									Provider: "zcode",
									ID:       "zcode-auth-1",
								},
							}, nil
						},
						startLogin: func(_ context.Context, req pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {
							startedProviders = append(startedProviders, req.Provider)
							return pluginapi.AuthLoginStartResponse{
								Provider: req.Provider,
								URL:      "https://portal.example/" + req.Provider,
								State:    "state-" + req.Provider,
							}, nil
						},
						pollLogin: func(_ context.Context, req pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {
							polledProviders = append(polledProviders, req.Provider)
							return pluginapi.AuthLoginPollResponse{
								Status: pluginapi.AuthLoginStatusSuccess,
								Auth: pluginapi.AuthData{
									Provider: "zcode",
									ID:       "zcode-auth-" + req.Provider,
								},
							}, nil
						},
						refreshAuth: func(_ context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
							refreshedProviders = append(refreshedProviders, req.AuthProvider)
							return pluginapi.AuthRefreshResponse{
								Auth: pluginapi.AuthData{
									Provider: req.AuthProvider,
									ID:       req.AuthID,
									Metadata: map[string]any{"access_token": "refreshed"},
								},
							}, nil
						},
					},
					Executor:           &fakeExecutor{identifier: "zcode"},
					ExecutorModelScope: pluginapi.ExecutorModelScopeOAuth,
					ModelProvider: modelProviderFunc{
						modelsForAuth: func(_ context.Context, req pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
							return pluginapi.ModelResponse{
								Provider: req.AuthProvider,
								Models:   []pluginapi.ModelInfo{{ID: "glm-5"}},
							}, nil
						},
					},
				},
			},
		})

		infos := host.RegisteredPlugins()
		if len(infos) != 1 {
			t.Fatalf("RegisteredPlugins() len = %d, want 1", len(infos))
		}
		if !infos[0].SupportsOAuth || infos[0].OAuthProvider != "zcode-zai" {
			t.Fatalf("RegisteredPlugins()[0] = %#v, want SupportsOAuth=true OAuthProvider=zcode-zai", infos[0])
		}
		wantProviders := []string{"zcode-zai", "zcode-bigmodel"}
		if !reflect.DeepEqual(infos[0].OAuthProviders, wantProviders) {
			t.Fatalf("RegisteredPlugins()[0].OAuthProviders = %#v, want %#v", infos[0].OAuthProviders, wantProviders)
		}
		if infos[0].OAuthProviderEntries != nil {
			t.Fatalf("RegisteredPlugins()[0].OAuthProviderEntries = %#v, want nil when AuthEntries is not declared", infos[0].OAuthProviderEntries)
		}
		if got := host.AuthProviderIdentifiers(); !reflect.DeepEqual(got, wantProviders) {
			t.Fatalf("AuthProviderIdentifiers() = %#v, want %#v", got, wantProviders)
		}
		for _, portal := range wantProviders {
			if !host.HasAuthProvider(portal) {
				t.Fatalf("HasAuthProvider(%q) = false, want true", portal)
			}
			startResp, handled, errStart := host.StartLogin(context.Background(), portal, "http://127.0.0.1:8317/v8/management/oauth/callback")
			if errStart != nil || !handled || startResp.Provider != portal {
				t.Fatalf("StartLogin(%q) = (%#v, %v, %v), want handled response", portal, startResp, handled, errStart)
			}
			pollResp, handled, errPoll := host.PollLogin(context.Background(), portal, startResp.State)
			if errPoll != nil || !handled || pollResp.Status != pluginapi.AuthLoginStatusSuccess {
				t.Fatalf("PollLogin(%q) = (%#v, %v, %v), want success", portal, pollResp, handled, errPoll)
			}
		}
		if !reflect.DeepEqual(startedProviders, wantProviders) || !reflect.DeepEqual(polledProviders, wantProviders) {
			t.Fatalf("started=%#v polled=%#v, want %#v", startedProviders, polledProviders, wantProviders)
		}
		if !host.SameAuthProvider("zcode", "zcode-bigmodel") || !host.SameAuthProvider("zcode-zai", "zcode-bigmodel") {
			t.Fatal("SameAuthProvider() = false, want true for providers owned by the same plugin")
		}
		for _, credProvider := range []string{"zcode", "zcode-zai", "zcode-bigmodel"} {
			if !host.HasAuthModelProvider(credProvider) {
				t.Fatalf("HasAuthModelProvider(%q) = false, want true", credProvider)
			}
			refreshed, handled, errRefresh := host.RefreshAuth(context.Background(), &coreauth.Auth{
				ID:       "auth-" + credProvider,
				Provider: credProvider,
			})
			if errRefresh != nil || !handled || refreshed == nil {
				t.Fatalf("RefreshAuth(%q) = (%#v, %v, %v), want handled refresh", credProvider, refreshed, handled, errRefresh)
			}
		}
	})

	t.Run("plural_auth_providers_capability", func(t *testing.T) {
		var calledPortal string
		host := newHostWithRecords(capabilityRecord{
			id:       "multi-cap-plugin",
			priority: 10,
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProviders: []pluginapi.AuthProvider{
						fakeAuthProvider{
							identifier: "portal-one",
							startLogin: func(_ context.Context, req pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {
								calledPortal = "one:" + req.Provider
								return pluginapi.AuthLoginStartResponse{Provider: req.Provider, URL: "https://one.example", State: "state-1"}, nil
							},
						},
						fakeAuthProvider{
							identifier: "portal-two",
							startLogin: func(_ context.Context, req pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {
								calledPortal = "two:" + req.Provider
								return pluginapi.AuthLoginStartResponse{Provider: req.Provider, URL: "https://two.example", State: "state-2"}, nil
							},
						},
					},
				},
			},
		})

		infos := host.RegisteredPlugins()
		if len(infos) != 1 || !infos[0].SupportsOAuth || infos[0].OAuthProvider != "portal-one" {
			t.Fatalf("RegisteredPlugins() = %#v, want portal-one primary", infos)
		}
		if !reflect.DeepEqual(infos[0].OAuthProviders, []string{"portal-one", "portal-two"}) {
			t.Fatalf("OAuthProviders = %#v, want [portal-one portal-two]", infos[0].OAuthProviders)
		}
		_, handled, errStart := host.StartLogin(context.Background(), "portal-two", "http://127.0.0.1:8317/callback")
		if errStart != nil || !handled || calledPortal != "two:portal-two" {
			t.Fatalf("StartLogin(portal-two) handled=%v err=%v calledPortal=%q, want two:portal-two", handled, errStart, calledPortal)
		}
	})

	t.Run("plural_auth_providers_interleaved_shared_credential_refresh_and_parse", func(t *testing.T) {
		refreshedBy := ""
		parsedBy := ""
		host := newHostWithRecords(capabilityRecord{
			id:       "multi-handler-plugin",
			priority: 10,
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProviders: []pluginapi.AuthProvider{
						fakeAuthProvider{
							identifier: "portal-one",
							pollLogin: func(_ context.Context, req pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {
								return pluginapi.AuthLoginPollResponse{
									Status: pluginapi.AuthLoginStatusSuccess,
									Auth: pluginapi.AuthData{
										Provider: "vendor-shared",
										ID:       "cred-one",
									},
								}, nil
							},
							parseAuth: func(_ context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
								parsedBy = "one"
								return pluginapi.AuthParseResponse{
									Handled: true,
									Auth:    pluginapi.AuthData{Provider: "vendor-shared", ID: "cred-one"},
								}, nil
							},
							refreshAuth: func(_ context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
								refreshedBy = "one"
								return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{Provider: req.AuthProvider, ID: req.AuthID}}, nil
							},
						},
						fakeAuthProvider{
							identifier: "portal-two",
							pollLogin: func(_ context.Context, req pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {
								return pluginapi.AuthLoginPollResponse{
									Status: pluginapi.AuthLoginStatusSuccess,
									Auth: pluginapi.AuthData{
										Provider: "vendor-shared",
										ID:       "cred-two",
									},
								}, nil
							},
							parseAuth: func(_ context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
								parsedBy = "two"
								return pluginapi.AuthParseResponse{
									Handled: true,
									Auth:    pluginapi.AuthData{Provider: "vendor-shared", ID: "cred-two"},
								}, nil
							},
							refreshAuth: func(_ context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
								refreshedBy = "two"
								return pluginapi.AuthRefreshResponse{Auth: pluginapi.AuthData{Provider: req.AuthProvider, ID: req.AuthID}}, nil
							},
						},
					},
				},
			},
		})

		// Log in via portal-one first, then portal-two; portal-two must NOT overwrite portal-one's credential handler.
		pollOne, handled, errPoll := host.PollLogin(context.Background(), "portal-one", "state-1")
		if errPoll != nil || !handled || pollOne.Status != pluginapi.AuthLoginStatusSuccess {
			t.Fatalf("PollLogin(portal-one) = (%#v, %v, %v), want success", pollOne, handled, errPoll)
		}
		pollTwo, handled, errPoll := host.PollLogin(context.Background(), "portal-two", "state-2")
		if errPoll != nil || !handled || pollTwo.Status != pluginapi.AuthLoginStatusSuccess {
			t.Fatalf("PollLogin(portal-two) = (%#v, %v, %v), want success", pollTwo, handled, errPoll)
		}
		credOne := host.AuthDataToCoreAuth(pollOne.Auth, "", "cred-one.json")
		credTwo := host.AuthDataToCoreAuth(pollTwo.Auth, "", "cred-two.json")

		_, handled, errRefresh := host.RefreshAuth(context.Background(), credOne)
		if errRefresh != nil || !handled || refreshedBy != "one" {
			t.Fatalf("RefreshAuth(credOne) handled=%v err=%v refreshedBy=%q, want one", handled, errRefresh, refreshedBy)
		}
		_, handled, errRefresh = host.RefreshAuth(context.Background(), credTwo)
		if errRefresh != nil || !handled || refreshedBy != "two" {
			t.Fatalf("RefreshAuth(credTwo) handled=%v err=%v refreshedBy=%q, want two", handled, errRefresh, refreshedBy)
		}
		parsedOne, handled, errParse := host.ParseAuth(context.Background(), pluginapi.AuthParseRequest{
			Provider: "vendor-shared",
			RawJSON:  []byte(`{"type":"vendor-shared","oauth_provider":"portal-one"}`),
		})
		if errParse != nil || !handled || parsedBy != "one" || parsedOne == nil {
			t.Fatalf("ParseAuth(portal-one) handled=%v err=%v parsedBy=%q auth=%#v, want one", handled, errParse, parsedBy, parsedOne)
		}
		parsedTwo, handled, errParse := host.ParseAuth(context.Background(), pluginapi.AuthParseRequest{
			Provider: "vendor-shared",
			RawJSON:  []byte(`{"type":"vendor-shared","oauth_provider":"portal-two"}`),
		})
		if errParse != nil || !handled || parsedBy != "two" || parsedTwo == nil {
			t.Fatalf("ParseAuth(portal-two) handled=%v err=%v parsedBy=%q auth=%#v, want two", handled, errParse, parsedBy, parsedTwo)
		}
		refreshedBy = ""
		_, handled, errRefresh = host.RefreshAuth(context.Background(), parsedOne)
		if errRefresh != nil || !handled || refreshedBy != "one" {
			t.Fatalf("RefreshAuth(parsedOne) handled=%v err=%v refreshedBy=%q, want one", handled, errRefresh, refreshedBy)
		}
		refreshedBy = ""
		_, handled, errRefresh = host.RefreshAuth(context.Background(), parsedTwo)
		if errRefresh != nil || !handled || refreshedBy != "two" {
			t.Fatalf("RefreshAuth(parsedTwo) handled=%v err=%v refreshedBy=%q, want two", handled, errRefresh, refreshedBy)
		}
	})

	t.Run("no_prefix_hijack_or_unrelated_parse_broadcast", func(t *testing.T) {
		unrelatedParseCalled := false
		host := newHostWithRecords(capabilityRecord{
			id:       "vendor-cn-plugin",
			priority: 10,
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProvider: fakeAuthProvider{
						identifier: "vendor-cn",
						parseAuth: func(_ context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
							unrelatedParseCalled = true
							return pluginapi.AuthParseResponse{Handled: true, Auth: pluginapi.AuthData{Provider: req.Provider, ID: "hijacked"}}, nil
						},
					},
				},
			},
		})

		// "vendor-cn" must NOT implicitly claim "vendor" via prefix guessing
		if host.HasAuthProvider("vendor") {
			t.Fatal("HasAuthProvider(vendor) = true for vendor-cn plugin, want false")
		}
		if host.SameAuthProvider("vendor", "vendor-cn") {
			t.Fatal("SameAuthProvider(vendor, vendor-cn) = true without ownership declaration, want false")
		}
		// ParseAuth with an explicit unrelated provider ("claude" or "vendor") must NOT broadcast to vendor-cn
		_, handled, errParse := host.ParseAuth(context.Background(), pluginapi.AuthParseRequest{
			Provider: "claude",
			RawJSON:  []byte(`{"type":"claude","access_token":"tok"}`),
		})
		if errParse != nil || handled || unrelatedParseCalled {
			t.Fatalf("ParseAuth(claude) = (handled=%v, err=%v), unrelatedParseCalled=%v, want not called", handled, errParse, unrelatedParseCalled)
		}
	})

	t.Run("cold_start_parse_and_refresh_without_executor", func(t *testing.T) {
		// Simulate a fresh Host after restart where an auth-only plugin has portal identifiers
		// ("zai", "bigmodel") and either persisted oauth_provider metadata in RawJSON or declared CredentialProviders.
		refreshed := false
		host := newHostWithRecords(capabilityRecord{
			id:       "omc-zcode-oauth",
			priority: 10,
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProvider: fakeAuthProvider{
						identifier:  "zai",
						identifiers: []string{"zai", "bigmodel"},
						parseAuth: func(_ context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
							if req.Provider != "zcode" {
								return pluginapi.AuthParseResponse{Handled: false}, nil
							}
							return pluginapi.AuthParseResponse{
								Handled: true,
								Auth: pluginapi.AuthData{
									Provider: "zcode",
									ID:       "zcode-cold.json",
									Metadata: map[string]any{"oauth_provider": "bigmodel"},
								},
							}, nil
						},
						refreshAuth: func(_ context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
							refreshed = true
							return pluginapi.AuthRefreshResponse{
								Auth: pluginapi.AuthData{
									Provider: req.AuthProvider,
									ID:       req.AuthID,
								},
							}, nil
						},
					},
				},
			},
		})

		auth, handled, errParse := host.ParseAuth(context.Background(), pluginapi.AuthParseRequest{
			Provider: "zcode",
			FileName: "zcode-cold.json",
			RawJSON:  []byte(`{"type":"zcode","oauth_provider":"bigmodel","token":"abc"}`),
		})
		if errParse != nil || !handled || auth == nil || auth.Provider != "zcode" {
			t.Fatalf("cold-start ParseAuth(zcode) = (%#v, %v, %v), want handled zcode auth", auth, handled, errParse)
		}
		authNoRawHint, handled, errParse := host.ParseAuth(context.Background(), pluginapi.AuthParseRequest{
			Provider: "zcode",
			FileName: "zcode-cold-nohint.json",
			RawJSON:  []byte(`{"type":"zcode","token":"abc"}`),
		})
		if errParse != nil || !handled || authNoRawHint == nil {
			t.Fatalf("cold-start ParseAuth without raw hint = (%#v, %v, %v), want handled", authNoRawHint, handled, errParse)
		}
		if authNoRawHint.Metadata["oauth_provider"] != "bigmodel" || authNoRawHint.Attributes["oauth_provider"] != "bigmodel" {
			t.Fatalf("cold-start ParseAuth without raw hint attribution = (meta=%#v, attr=%q), want both bigmodel", authNoRawHint.Metadata["oauth_provider"], authNoRawHint.Attributes["oauth_provider"])
		}
		if !host.SameAuthProvider("zcode", "zai") || !host.SameAuthProvider("zcode", "bigmodel") {
			t.Fatal("SameAuthProvider after cold-start ParseAuth = false, want true")
		}
		_, handled, errRefresh := host.RefreshAuth(context.Background(), auth)
		if errRefresh != nil || !handled || !refreshed {
			t.Fatalf("RefreshAuth(zcode) after cold-start ParseAuth = (%v, %v), refreshed=%v", handled, errRefresh, refreshed)
		}
	})

	t.Run("auth_entries_projection_and_metadata_fallback", func(t *testing.T) {
		var started []string
		host := newHostWithRecords(capabilityRecord{
			id:       "omc-zcode-oauth",
			priority: 10,
			meta: pluginapi.Metadata{
				Name: "OMC ZCode OAuth",
				Logo: "https://default.example/zcode.svg",
			},
			plugin: pluginapi.Plugin{
				Metadata: pluginapi.Metadata{
					Name: "OMC ZCode OAuth",
					Logo: "https://default.example/zcode.svg",
				},
				Capabilities: pluginapi.Capabilities{
					AuthProvider: fakeAuthProvider{
						identifier: " ZCode-ZAI ",
						entries: []pluginapi.AuthProviderEntry{
							{
								Identifier: " ZCode-ZAI ",
								Name:       "Z.ai",
								Logo:       "https://chat.z.ai/logo.svg",
							},
							{
								Identifier: " ZCode-BigModel ",
								Name:       "BigModel",
								Logo:       "",
							},
							{
								Identifier: "zcode-trial",
								Name:       "",
								Logo:       "https://trial.example/logo.svg",
							},
						},
						startLogin: func(_ context.Context, req pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {
							started = append(started, req.Provider)
							return pluginapi.AuthLoginStartResponse{
								Provider: req.Provider,
								URL:      "https://portal.example/" + req.Provider,
								State:    "state-" + req.Provider,
							}, nil
						},
					},
				},
			},
		})

		infos := host.RegisteredPlugins()
		if len(infos) != 1 {
			t.Fatalf("RegisteredPlugins() len = %d, want 1", len(infos))
		}
		wantProviders := []string{"zcode-zai", "zcode-bigmodel", "zcode-trial"}
		if infos[0].OAuthProvider != "zcode-zai" || !reflect.DeepEqual(infos[0].OAuthProviders, wantProviders) {
			t.Fatalf("OAuthProvider=%q OAuthProviders=%#v, want %#v", infos[0].OAuthProvider, infos[0].OAuthProviders, wantProviders)
		}
		wantEntries := []pluginapi.AuthProviderEntry{
			{
				Identifier: "zcode-zai",
				Name:       "Z.ai",
				Logo:       "https://chat.z.ai/logo.svg",
			},
			{
				Identifier: "zcode-bigmodel",
				Name:       "BigModel",
				Logo:       "https://default.example/zcode.svg",
			},
			{
				Identifier: "zcode-trial",
				Name:       "OMC ZCode OAuth",
				Logo:       "https://trial.example/logo.svg",
			},
		}
		if !reflect.DeepEqual(infos[0].OAuthProviderEntries, wantEntries) {
			t.Fatalf("OAuthProviderEntries = %#v, want %#v", infos[0].OAuthProviderEntries, wantEntries)
		}
		for _, portal := range wantProviders {
			if !host.HasAuthProvider(portal) {
				t.Fatalf("HasAuthProvider(%q) = false, want true", portal)
			}
			resp, handled, errStart := host.StartLogin(context.Background(), portal, "http://127.0.0.1:8317/callback")
			if errStart != nil || !handled || resp.Provider != portal {
				t.Fatalf("StartLogin(%q) = (%#v, %v, %v), want handled", portal, resp, handled, errStart)
			}
		}
		if !reflect.DeepEqual(started, wantProviders) {
			t.Fatalf("started = %#v, want %#v", started, wantProviders)
		}
	})

	t.Run("plural_auth_providers_mixed_entries_and_legacy_type_without_auth_entries", func(t *testing.T) {
		host := newHostWithRecords(
			capabilityRecord{
				id:       "mixed-cap-plugin",
				priority: 20,
				meta: pluginapi.Metadata{
					Name: "Mixed Portal Plugin",
					Logo: "https://default.example/mixed.svg",
				},
				plugin: pluginapi.Plugin{
					Capabilities: pluginapi.Capabilities{
						AuthProviders: []pluginapi.AuthProvider{
							fakeAuthProvider{
								identifier: "portal-one",
								entries: []pluginapi.AuthProviderEntry{
									{Identifier: " , "},
									{Identifier: "Portal-One", Name: "", Logo: "https://one.example/logo.svg"},
									{Identifier: "portal-one", Name: "Portal One Filled", Logo: ""},
								},
							},
							legacySingleAuthProvider{identifier: "portal-two"},
						},
					},
				},
			},
			capabilityRecord{
				id:       "strict-legacy-plugin",
				priority: 10,
				meta: pluginapi.Metadata{
					Name: "Strict Legacy Plugin",
					Logo: "https://default.example/legacy.svg",
				},
				plugin: pluginapi.Plugin{
					Capabilities: pluginapi.Capabilities{
						AuthProvider: legacySingleAuthProvider{identifier: "strict-legacy"},
					},
				},
			},
		)

		infos := host.RegisteredPlugins()
		if len(infos) != 2 {
			t.Fatalf("RegisteredPlugins() len = %d, want 2", len(infos))
		}
		wantMixedEntries := []pluginapi.AuthProviderEntry{
			{
				Identifier: "portal-one",
				Name:       "Portal One Filled",
				Logo:       "https://one.example/logo.svg",
			},
			{
				Identifier: "portal-two",
				Name:       "Mixed Portal Plugin",
				Logo:       "https://default.example/mixed.svg",
			},
		}
		if !reflect.DeepEqual(infos[0].OAuthProviderEntries, wantMixedEntries) {
			t.Fatalf("mixed plugin OAuthProviderEntries = %#v, want %#v", infos[0].OAuthProviderEntries, wantMixedEntries)
		}
		if infos[1].OAuthProvider != "strict-legacy" || infos[1].OAuthProviderEntries != nil {
			t.Fatalf("strict legacy plugin = %#v, want OAuthProvider=strict-legacy and OAuthProviderEntries=nil", infos[1])
		}
	})
}

type legacySingleAuthProvider struct {
	identifier string
}

func (p legacySingleAuthProvider) Identifier() string {
	return p.identifier
}

func (legacySingleAuthProvider) ParseAuth(context.Context, pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
	return pluginapi.AuthParseResponse{}, nil
}

func (legacySingleAuthProvider) StartLogin(context.Context, pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {
	return pluginapi.AuthLoginStartResponse{}, nil
}

func (legacySingleAuthProvider) PollLogin(context.Context, pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {
	return pluginapi.AuthLoginPollResponse{}, nil
}

func (legacySingleAuthProvider) RefreshAuth(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
	return pluginapi.AuthRefreshResponse{}, nil
}