Ctrl K
ComunidadGuías…

internal/pluginhost/auth_callbacks_test.go

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

Archivo original ↗Preguntar a KiBot
package pluginhost

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

	"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
	"github.com/router-for-me/CLIProxyAPI/v8/internal/registry"
	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/pluginabi"
	"github.com/router-for-me/CLIProxyAPI/v8/sdk/pluginapi"
)

type memoryAuthStorage struct {
	payload []byte
}

func (s *memoryAuthStorage) RawJSON() []byte {
	if s == nil {
		return nil
	}
	return append([]byte(nil), s.payload...)
}
func (s *memoryAuthStorage) SaveTokenToFile(authFilePath string) error {
	if s == nil || len(s.payload) == 0 {
		return fmt.Errorf("memory auth storage payload is empty")
	}
	return os.WriteFile(authFilePath, s.payload, 0o600)
}

func TestHostAuthListCallbackUsesAuthManager(t *testing.T) {
	authDir := t.TempDir()
	path := filepath.Join(authDir, "demo-a.json")
	if errWrite := os.WriteFile(path, []byte(`{"type":"demo","email":"a@example.com","api_key":"k1"}`), 0o600); errWrite != nil {
		t.Fatalf("write auth file: %v", errWrite)
	}

	auth := &coreauth.Auth{
		ID:       "demo-a.json",
		Provider: "demo",
		FileName: "demo-a.json",
		Label:    "a@example.com",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"path":   path,
			"source": path,
		},
		Metadata: map[string]any{
			"type":    "demo",
			"email":   "a@example.com",
			"api_key": "k1",
		},
		Storage: &memoryAuthStorage{payload: []byte(`{"type":"demo","email":"a@example.com","api_key":"k1"}`)},
	}
	auth.EnsureIndex()

	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	host.SetAuthManager(coreauth.NewManager(nil, nil, nil))
	if _, errRegister := host.currentAuthManager().Register(context.Background(), auth); errRegister != nil {
		t.Fatalf("register auth: %v", errRegister)
	}

	rawResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthList, nil)
	if errCall != nil {
		t.Fatalf("callFromPlugin() error = %v", errCall)
	}
	resp, errDecode := decodeRPCEnvelope[rpcHostAuthListResponse](rawResp)
	if errDecode != nil {
		t.Fatalf("decode response: %v", errDecode)
	}
	if len(resp.Files) != 1 {
		t.Fatalf("files = %#v, want one entry", resp.Files)
	}
	entry := resp.Files[0]
	if entry.AuthIndex != auth.Index || entry.Name != "demo-a.json" || entry.Email != "a@example.com" {
		t.Fatalf("entry = %#v, want auth index and file metadata", entry)
	}
}

func TestHostAuthGetCallbackReturnsPhysicalJSONByAuthIndex(t *testing.T) {
	authDir := t.TempDir()
	path := filepath.Join(authDir, "demo-b.json")
	if errWrite := os.WriteFile(path, []byte(`{"type":"demo","email":"b@example.com","api_key":"k2"}`), 0o600); errWrite != nil {
		t.Fatalf("write auth file: %v", errWrite)
	}

	auth := &coreauth.Auth{
		ID:       "demo-b.json",
		Provider: "demo",
		FileName: "demo-b.json",
		Label:    "b@example.com",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"path":   path,
			"source": path,
		},
		Metadata: map[string]any{
			"type":    "demo",
			"email":   "b@example.com",
			"api_key": "k2",
		},
		Storage: &memoryAuthStorage{payload: []byte(`{"type":"demo","email":"b@example.com","api_key":"changed"}`)},
	}
	auth.EnsureIndex()

	host := New()
	host.SetAuthManager(coreauth.NewManager(nil, nil, nil))
	if _, errRegister := host.currentAuthManager().Register(context.Background(), auth); errRegister != nil {
		t.Fatalf("register auth: %v", errRegister)
	}

	req, errMarshal := json.Marshal(pluginapi.HostAuthGetRequest{AuthIndex: auth.Index})
	if errMarshal != nil {
		t.Fatalf("marshal request: %v", errMarshal)
	}
	rawResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthGet, req)
	if errCall != nil {
		t.Fatalf("callFromPlugin() error = %v", errCall)
	}
	resp, errDecode := decodeRPCEnvelope[rpcHostAuthGetResponse](rawResp)
	if errDecode != nil {
		t.Fatalf("decode response: %v", errDecode)
	}
	if resp.AuthIndex != auth.Index || resp.Name != "demo-b.json" {
		t.Fatalf("response = %#v, want auth index and name", resp)
	}
	var decoded map[string]any
	if errUnmarshal := json.Unmarshal(resp.JSON, &decoded); errUnmarshal != nil {
		t.Fatalf("unmarshal auth json: %v", errUnmarshal)
	}
	if decoded["email"] != "b@example.com" || decoded["api_key"] != "k2" {
		t.Fatalf("decoded json = %#v, want credential payload", decoded)
	}
}

func TestHostAuthListCallbackFallsBackToDisk(t *testing.T) {
	authDir := t.TempDir()
	path := filepath.Join(authDir, "claude-a.json")
	if errWrite := os.WriteFile(path, []byte(`{"type":"claude","email":"c@example.com"}`), 0o600); errWrite != nil {
		t.Fatalf("write auth file: %v", errWrite)
	}

	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}

	rawResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthList, nil)
	if errCall != nil {
		t.Fatalf("callFromPlugin() error = %v", errCall)
	}
	resp, errDecode := decodeRPCEnvelope[rpcHostAuthListResponse](rawResp)
	if errDecode != nil {
		t.Fatalf("decode response: %v", errDecode)
	}
	if len(resp.Files) != 1 {
		t.Fatalf("files = %#v, want one disk entry", resp.Files)
	}
	entry := resp.Files[0]
	if entry.Name != "claude-a.json" || entry.Type != "claude" || entry.Email != "c@example.com" {
		t.Fatalf("entry = %#v, want disk metadata", entry)
	}
	if entry.ModTime.IsZero() {
		t.Fatalf("entry modtime is zero: %#v", entry)
	}
	_ = time.Now()
}

func TestHostAuthGetRuntimeCallbackReturnsRuntimeInfo(t *testing.T) {
	auth := &coreauth.Auth{
		ID:       "demo-runtime.json",
		Provider: "demo",
		FileName: "demo-runtime.json",
		Label:    "runtime@example.com",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"runtime_only": "true",
		},
		Metadata: map[string]any{
			"type":    "demo",
			"email":   "runtime@example.com",
			"api_key": "runtime-key",
		},
		Storage: &memoryAuthStorage{payload: []byte(`{"type":"demo","email":"runtime@example.com","api_key":"runtime-key"}`)},
	}
	auth.EnsureIndex()

	host := New()
	host.SetAuthManager(coreauth.NewManager(nil, nil, nil))
	if _, errRegister := host.currentAuthManager().Register(context.Background(), auth); errRegister != nil {
		t.Fatalf("register auth: %v", errRegister)
	}

	req, errMarshal := json.Marshal(pluginapi.HostAuthGetRequest{AuthIndex: auth.Index})
	if errMarshal != nil {
		t.Fatalf("marshal request: %v", errMarshal)
	}
	rawResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthGetRuntime, req)
	if errCall != nil {
		t.Fatalf("callFromPlugin() error = %v", errCall)
	}
	resp, errDecode := decodeRPCEnvelope[pluginapi.HostAuthGetRuntimeResponse](rawResp)
	if errDecode != nil {
		t.Fatalf("decode response: %v", errDecode)
	}
	if resp.Auth.AuthIndex != auth.Index || resp.Auth.RuntimeOnly != true || resp.Auth.Email != "runtime@example.com" {
		t.Fatalf("response = %#v, want runtime auth entry", resp.Auth)
	}
}

func TestHostAuthGetRuntimeCallbackReturnsBaseURL(t *testing.T) {
	auth := &coreauth.Auth{
		ID:       "demo-base-url.json",
		Provider: "demo",
		FileName: "demo-base-url.json",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"runtime_only": "true",
			"base_url":     "https://api.custom.example.com/v1",
		},
	}
	auth.EnsureIndex()

	authMeta := &coreauth.Auth{
		ID:       "demo-meta-base-url.json",
		Provider: "demo",
		FileName: "demo-meta-base-url.json",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"runtime_only": "true",
		},
		Metadata: map[string]any{
			"base_url": "https://meta.custom.example.com/v1",
		},
	}
	authMeta.EnsureIndex()

	host := New()
	host.SetAuthManager(coreauth.NewManager(nil, nil, nil))
	if _, errRegister := host.currentAuthManager().Register(context.Background(), auth); errRegister != nil {
		t.Fatalf("register auth: %v", errRegister)
	}
	if _, errRegister := host.currentAuthManager().Register(context.Background(), authMeta); errRegister != nil {
		t.Fatalf("register authMeta: %v", errRegister)
	}

	req, errMarshal := json.Marshal(pluginapi.HostAuthGetRequest{AuthIndex: auth.Index})
	if errMarshal != nil {
		t.Fatalf("marshal request: %v", errMarshal)
	}
	rawResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthGetRuntime, req)
	if errCall != nil {
		t.Fatalf("callFromPlugin() error = %v", errCall)
	}
	resp, errDecode := decodeRPCEnvelope[pluginapi.HostAuthGetRuntimeResponse](rawResp)
	if errDecode != nil {
		t.Fatalf("decode response: %v", errDecode)
	}
	if resp.Auth.BaseURL != "https://api.custom.example.com/v1" {
		t.Fatalf("resp.Auth.BaseURL = %q, want %q", resp.Auth.BaseURL, "https://api.custom.example.com/v1")
	}

	reqMeta, errMarshalMeta := json.Marshal(pluginapi.HostAuthGetRequest{AuthIndex: authMeta.Index})
	if errMarshalMeta != nil {
		t.Fatalf("marshal request meta: %v", errMarshalMeta)
	}
	rawRespMeta, errCallMeta := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthGetRuntime, reqMeta)
	if errCallMeta != nil {
		t.Fatalf("callFromPlugin() meta error = %v", errCallMeta)
	}
	respMeta, errDecodeMeta := decodeRPCEnvelope[pluginapi.HostAuthGetRuntimeResponse](rawRespMeta)
	if errDecodeMeta != nil {
		t.Fatalf("decode response meta: %v", errDecodeMeta)
	}
	if respMeta.Auth.BaseURL != "https://meta.custom.example.com/v1" {
		t.Fatalf("respMeta.Auth.BaseURL = %q, want %q", respMeta.Auth.BaseURL, "https://meta.custom.example.com/v1")
	}

	authBoth := &coreauth.Auth{
		ID:       "demo-both-base-url.json",
		Provider: "demo",
		FileName: "demo-both-base-url.json",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"runtime_only": "true",
			"base_url":     "https://attr.custom.example.com/v1",
		},
		Metadata: map[string]any{
			"base_url": "https://meta.custom.example.com/v1",
		},
	}
	authBoth.EnsureIndex()
	if _, errRegister := host.currentAuthManager().Register(context.Background(), authBoth); errRegister != nil {
		t.Fatalf("register authBoth: %v", errRegister)
	}

	reqBoth, errMarshalBoth := json.Marshal(pluginapi.HostAuthGetRequest{AuthIndex: authBoth.Index})
	if errMarshalBoth != nil {
		t.Fatalf("marshal request both: %v", errMarshalBoth)
	}
	rawRespBoth, errCallBoth := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthGetRuntime, reqBoth)
	if errCallBoth != nil {
		t.Fatalf("callFromPlugin() both error = %v", errCallBoth)
	}
	respBoth, errDecodeBoth := decodeRPCEnvelope[pluginapi.HostAuthGetRuntimeResponse](rawRespBoth)
	if errDecodeBoth != nil {
		t.Fatalf("decode response both: %v", errDecodeBoth)
	}
	if respBoth.Auth.BaseURL != "https://attr.custom.example.com/v1" {
		t.Fatalf("respBoth.Auth.BaseURL = %q, want %q", respBoth.Auth.BaseURL, "https://attr.custom.example.com/v1")
	}
}

func TestListAuthFilesFromDiskReadsBaseURL(t *testing.T) {
	authDir := t.TempDir()
	filePath := filepath.Join(authDir, "test-auth.json")
	fileData := []byte(`{"type":"openai","base_url":"https://disk-proxy.example.com/v1","email":"user@example.com"}`)
	if errWrite := os.WriteFile(filePath, fileData, 0o600); errWrite != nil {
		t.Fatalf("write file: %v", errWrite)
	}

	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	entries, errList := host.listAuthFilesFromDisk()
	if errList != nil {
		t.Fatalf("listAuthFilesFromDisk error: %v", errList)
	}
	if len(entries) != 1 {
		t.Fatalf("entries count = %d, want 1", len(entries))
	}
	if entries[0].BaseURL != "https://disk-proxy.example.com/v1" {
		t.Fatalf("entry BaseURL = %q, want https://disk-proxy.example.com/v1", entries[0].BaseURL)
	}
}

func TestHostAuthSaveCallbackRejectsInvalidWeightBeforePersistence(t *testing.T) {
	for _, rawWeight := range []string{`1.5`, `1000001`, `9223372036854775808`, `"invalid"`} {
		t.Run(rawWeight, func(t *testing.T) {
			authDir := t.TempDir()
			host := New()
			host.runtimeConfig = &config.Config{AuthDir: authDir}
			host.SetAuthManager(coreauth.NewManager(nil, nil, nil))

			req, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{
				Name: "invalid.json",
				JSON: json.RawMessage(`{"type":"demo","weight":` + rawWeight + `}`),
			})
			if errMarshal != nil {
				t.Fatalf("marshal request: %v", errMarshal)
			}
			if _, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, req); errCall == nil {
				t.Fatal("host.auth.save accepted an invalid weight")
			}
			if _, errStat := os.Stat(filepath.Join(authDir, "invalid.json")); !os.IsNotExist(errStat) {
				t.Fatalf("invalid auth file was persisted: %v", errStat)
			}
			if auths := host.currentAuthManager().List(); len(auths) != 0 {
				t.Fatalf("invalid auth was registered: %#v", auths)
			}
		})
	}
}

func TestHostAuthSaveCallbackWritesPhysicalFile(t *testing.T) {
	authDir := t.TempDir()
	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	host.SetAuthManager(coreauth.NewManager(nil, nil, nil))

	req, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: "saved.json",
		JSON: json.RawMessage(`{"type":"demo","email":"saved@example.com","api_key":"saved-key"}`),
	})
	if errMarshal != nil {
		t.Fatalf("marshal request: %v", errMarshal)
	}
	rawResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, req)
	if errCall != nil {
		t.Fatalf("callFromPlugin() error = %v", errCall)
	}
	resp, errDecode := decodeRPCEnvelope[pluginapi.HostAuthSaveResponse](rawResp)
	if errDecode != nil {
		t.Fatalf("decode response: %v", errDecode)
	}
	if resp.Name != "saved.json" {
		t.Fatalf("response = %#v, want saved file name", resp)
	}
	data, errRead := os.ReadFile(resp.Path)
	if errRead != nil {
		t.Fatalf("read saved file: %v", errRead)
	}
	if string(data) != `{"type":"demo","email":"saved@example.com","api_key":"saved-key"}` {
		t.Fatalf("saved file = %q, want credential json", string(data))
	}
	auths := host.currentAuthManager().List()
	if len(auths) != 1 || auths[0].FileName != "saved.json" {
		t.Fatalf("auths = %#v, want one registered auth", auths)
	}
}

func TestHostAuthSaveCallbackPreservesDisabledState(t *testing.T) {
	authDir := t.TempDir()
	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	host.SetAuthManager(coreauth.NewManager(sdkauth.NewFileTokenStore(), nil, nil))

	reqDisabled, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: "test-auth.json",
		JSON: json.RawMessage(`{"type":"demo","email":"disabled@example.com","api_key":"test-key","disabled":true}`),
	})
	if errMarshal != nil {
		t.Fatalf("marshal request: %v", errMarshal)
	}
	rawResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, reqDisabled)
	if errCall != nil {
		t.Fatalf("callFromPlugin() error = %v", errCall)
	}
	resp, errDecode := decodeRPCEnvelope[pluginapi.HostAuthSaveResponse](rawResp)
	if errDecode != nil {
		t.Fatalf("decode response: %v", errDecode)
	}

	auths := host.currentAuthManager().List()
	if len(auths) != 1 {
		t.Fatalf("auths len = %d, want 1", len(auths))
	}
	if !auths[0].Disabled {
		t.Fatalf("auth.Disabled = %v, want true", auths[0].Disabled)
	}
	if auths[0].Status != coreauth.StatusDisabled {
		t.Fatalf("auth.Status = %v, want %v", auths[0].Status, coreauth.StatusDisabled)
	}

	data, errRead := os.ReadFile(resp.Path)
	if errRead != nil {
		t.Fatalf("read saved file: %v", errRead)
	}
	var fileMeta map[string]any
	if errUnmarshal := json.Unmarshal(data, &fileMeta); errUnmarshal != nil {
		t.Fatalf("unmarshal saved file: %v", errUnmarshal)
	}
	if disabled, ok := fileMeta["disabled"].(bool); !ok || !disabled {
		t.Fatalf("saved file metadata disabled = %v, want true", fileMeta["disabled"])
	}

	reqEnabled, errMarshalEnabled := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: "test-auth.json",
		JSON: json.RawMessage(`{"type":"demo","email":"disabled@example.com","api_key":"test-key","disabled":false}`),
	})
	if errMarshalEnabled != nil {
		t.Fatalf("marshal request enabled: %v", errMarshalEnabled)
	}
	rawRespEnabled, errCallEnabled := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, reqEnabled)
	if errCallEnabled != nil {
		t.Fatalf("callFromPlugin() error = %v", errCallEnabled)
	}
	if _, errDecodeEnabled := decodeRPCEnvelope[pluginapi.HostAuthSaveResponse](rawRespEnabled); errDecodeEnabled != nil {
		t.Fatalf("decode response enabled: %v", errDecodeEnabled)
	}
	auths = host.currentAuthManager().List()
	if len(auths) != 1 {
		t.Fatalf("auths len = %d, want 1", len(auths))
	}
	if auths[0].Disabled {
		t.Fatalf("auth.Disabled after re-enable = %v, want false", auths[0].Disabled)
	}
	if auths[0].Status != coreauth.StatusActive {
		t.Fatalf("auth.Status after re-enable = %v, want %v", auths[0].Status, coreauth.StatusActive)
	}

	dataEnabled, errReadEnabled := os.ReadFile(resp.Path)
	if errReadEnabled != nil {
		t.Fatalf("read saved file: %v", errReadEnabled)
	}
	var fileMetaEnabled map[string]any
	if errUnmarshalEnabled := json.Unmarshal(dataEnabled, &fileMetaEnabled); errUnmarshalEnabled != nil {
		t.Fatalf("unmarshal saved file: %v", errUnmarshalEnabled)
	}
	if disabled, ok := fileMetaEnabled["disabled"].(bool); ok && disabled {
		t.Fatalf("saved file metadata disabled after re-enable = %v, want false", fileMetaEnabled["disabled"])
	}

	reqUpdateDisabled, errMarshalUpdateDisabled := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: "test-auth.json",
		JSON: json.RawMessage(`{"type":"demo","email":"disabled@example.com","api_key":"test-key","disabled":true}`),
	})
	if errMarshalUpdateDisabled != nil {
		t.Fatalf("marshal update disabled request: %v", errMarshalUpdateDisabled)
	}
	rawRespUpdateDisabled, errCallUpdateDisabled := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, reqUpdateDisabled)
	if errCallUpdateDisabled != nil {
		t.Fatalf("callFromPlugin() update disabled error = %v", errCallUpdateDisabled)
	}
	if _, errDecodeUpdateDisabled := decodeRPCEnvelope[pluginapi.HostAuthSaveResponse](rawRespUpdateDisabled); errDecodeUpdateDisabled != nil {
		t.Fatalf("decode update disabled response: %v", errDecodeUpdateDisabled)
	}
	auths = host.currentAuthManager().List()
	if len(auths) != 1 {
		t.Fatalf("auths len = %d, want 1", len(auths))
	}
	if !auths[0].Disabled {
		t.Fatalf("auth.Disabled after update to disabled = %v, want true", auths[0].Disabled)
	}
	if auths[0].Status != coreauth.StatusDisabled {
		t.Fatalf("auth.Status after update to disabled = %v, want %v", auths[0].Status, coreauth.StatusDisabled)
	}

	dataUpdateDisabled, errReadUpdateDisabled := os.ReadFile(resp.Path)
	if errReadUpdateDisabled != nil {
		t.Fatalf("read update disabled file: %v", errReadUpdateDisabled)
	}
	var fileMetaUpdateDisabled map[string]any
	if errUnmarshalUpdateDisabled := json.Unmarshal(dataUpdateDisabled, &fileMetaUpdateDisabled); errUnmarshalUpdateDisabled != nil {
		t.Fatalf("unmarshal update disabled file: %v", errUnmarshalUpdateDisabled)
	}
	if disabled, ok := fileMetaUpdateDisabled["disabled"].(bool); !ok || !disabled {
		t.Fatalf("saved file metadata disabled after update to disabled = %v, want true", fileMetaUpdateDisabled["disabled"])
	}
}

func TestHostListAuthFilesFromDiskParsesDisabled(t *testing.T) {
	authDir := t.TempDir()
	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}

	disabledFile := filepath.Join(authDir, "disabled.json")
	if errWrite := os.WriteFile(disabledFile, []byte(`{"type":"demo","disabled":true}`), 0o600); errWrite != nil {
		t.Fatalf("write disabled file: %v", errWrite)
	}
	activeFile := filepath.Join(authDir, "active.json")
	if errWrite := os.WriteFile(activeFile, []byte(`{"type":"demo","disabled":false}`), 0o600); errWrite != nil {
		t.Fatalf("write active file: %v", errWrite)
	}

	files, errList := host.listAuthFilesFromDisk()
	if errList != nil {
		t.Fatalf("listAuthFilesFromDisk() error = %v", errList)
	}
	if len(files) != 2 {
		t.Fatalf("listAuthFilesFromDisk() len = %d, want 2", len(files))
	}
	for _, f := range files {
		if f.Name == "disabled.json" {
			if !f.Disabled {
				t.Fatalf("disabled.json entry.Disabled = %v, want true", f.Disabled)
			}
			if f.Status != string(coreauth.StatusDisabled) {
				t.Fatalf("disabled.json entry.Status = %v, want %v", f.Status, coreauth.StatusDisabled)
			}
		}
		if f.Name == "active.json" {
			if f.Disabled {
				t.Fatalf("active.json entry.Disabled = %v, want false", f.Disabled)
			}
			if f.Status != string(coreauth.StatusActive) {
				t.Fatalf("active.json entry.Status = %v, want %v", f.Status, coreauth.StatusActive)
			}
		}
	}
}

func TestHostAuthSaveCallbackDoesNotDuplicatePluginAuthWithNonJSONID(t *testing.T) {
	authDir := t.TempDir()
	const (
		authID   = "demo-72a36b69-d20f-444f-a57d-f3cd0944530b"
		fileName = authID + ".json"
	)
	initialPath := filepath.Join(authDir, fileName)
	initialPayload := []byte(`{"type":"demo","email":"user@example.com","access_token":"tok-1"}`)
	if errWrite := os.WriteFile(initialPath, initialPayload, 0o600); errWrite != nil {
		t.Fatalf("write initial auth file: %v", errWrite)
	}

	host := newHostWithRecords(capabilityRecord{
		id: "demo-plugin",
		plugin: pluginapi.Plugin{
			Capabilities: pluginapi.Capabilities{
				AuthProvider: fakeAuthProvider{
					identifier: "demo",
					parseAuth: func(_ context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
						var meta map[string]any
						if errUnmarshal := json.Unmarshal(req.RawJSON, &meta); errUnmarshal != nil {
							return pluginapi.AuthParseResponse{}, errUnmarshal
						}
						email, _ := meta["email"].(string)
						return pluginapi.AuthParseResponse{
							Handled: true,
							Auth: pluginapi.AuthData{
								Provider:    "demo",
								ID:          authID,
								FileName:    filepath.Base(req.FileName),
								Label:       email,
								StorageJSON: req.RawJSON,
								Metadata:    meta,
							},
						}, nil
					},
				},
			},
		},
	})
	host.runtimeConfig = &config.Config{AuthDir: authDir}

	sdkauth.RegisterPluginAuthParser(host)
	t.Cleanup(func() {
		sdkauth.RegisterPluginAuthParser(nil)
	})

	store := sdkauth.NewFileTokenStore()
	store.SetBaseDir(authDir)
	manager := coreauth.NewManager(store, nil, nil)
	if errLoad := manager.Load(context.Background()); errLoad != nil {
		t.Fatalf("manager.Load() error = %v", errLoad)
	}
	host.SetAuthManager(manager)

	initialAuths := manager.List()
	if len(initialAuths) != 1 || initialAuths[0].ID != authID || initialAuths[0].FileName != fileName {
		t.Fatalf("initial manager.List() = %#v, want single auth with ID=%q FileName=%q", initialAuths, authID, fileName)
	}

	reqSave, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: fileName,
		JSON: json.RawMessage(`{"type":"demo","email":"user@example.com","access_token":"tok-2"}`),
	})
	if errMarshal != nil {
		t.Fatalf("marshal save request: %v", errMarshal)
	}
	rawSaveResp, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, reqSave)
	if errCall != nil {
		t.Fatalf("callFromPlugin(host.auth.save) error = %v", errCall)
	}
	if _, errDecode := decodeRPCEnvelope[pluginapi.HostAuthSaveResponse](rawSaveResp); errDecode != nil {
		t.Fatalf("decode save response: %v", errDecode)
	}

	authsAfterSave := manager.List()
	if len(authsAfterSave) != 1 {
		ids := make([]string, 0, len(authsAfterSave))
		for _, a := range authsAfterSave {
			if a != nil {
				ids = append(ids, a.ID+"(name="+a.FileName+")")
			}
		}
		t.Fatalf("manager.List() after host.auth.save len = %d (%v), want 1", len(authsAfterSave), ids)
	}
	if authsAfterSave[0].ID != authID || authsAfterSave[0].FileName != fileName {
		t.Fatalf("auth after save = %#v, want ID=%q FileName=%q", authsAfterSave[0], authID, fileName)
	}
	if gotToken := authsAfterSave[0].Metadata["access_token"]; gotToken != "tok-2" {
		t.Fatalf("auth Metadata[access_token] = %v, want tok-2", gotToken)
	}

	rawListResp, errListCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthList, nil)
	if errListCall != nil {
		t.Fatalf("callFromPlugin(host.auth.list) error = %v", errListCall)
	}
	listResp, errDecodeList := decodeRPCEnvelope[rpcHostAuthListResponse](rawListResp)
	if errDecodeList != nil {
		t.Fatalf("decode list response: %v", errDecodeList)
	}
	if len(listResp.Files) != 1 || listResp.Files[0].ID != authID || listResp.Files[0].Name != fileName {
		t.Fatalf("host.auth.list files = %#v, want single entry with ID=%q Name=%q", listResp.Files, authID, fileName)
	}
}

func TestHostAuthSaveCallbackReusesExistingSameFileAuthIDWithoutAuthProvider(t *testing.T) {
	authDir := t.TempDir()
	const (
		authID   = "demo-existing-72a36b69"
		fileName = authID + ".json"
	)
	filePath := filepath.Join(authDir, fileName)
	if errWrite := os.WriteFile(filePath, []byte(`{"type":"demo","email":"user@example.com","access_token":"tok-1"}`), 0o600); errWrite != nil {
		t.Fatalf("write initial auth file: %v", errWrite)
	}

	manager := coreauth.NewManager(nil, nil, nil)
	existing := &coreauth.Auth{
		ID:       authID,
		Provider: "demo",
		FileName: fileName,
		Label:    "user@example.com",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"path":   filePath,
			"source": filePath,
		},
		Metadata: map[string]any{
			"type":         "demo",
			"email":        "user@example.com",
			"access_token": "tok-1",
		},
	}
	if _, errRegister := manager.Register(context.Background(), existing); errRegister != nil {
		t.Fatalf("register existing auth: %v", errRegister)
	}

	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	host.SetAuthManager(manager)

	reqSave, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: fileName,
		JSON: json.RawMessage(`{"type":"demo","email":"user@example.com","access_token":"tok-2"}`),
	})
	if errMarshal != nil {
		t.Fatalf("marshal save request: %v", errMarshal)
	}
	if _, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, reqSave); errCall != nil {
		t.Fatalf("callFromPlugin(host.auth.save) error = %v", errCall)
	}

	auths := manager.List()
	if len(auths) != 1 || auths[0].ID != authID || auths[0].FileName != fileName {
		t.Fatalf("manager.List() = %#v, want single auth with ID=%q FileName=%q", auths, authID, fileName)
	}
	if gotToken := auths[0].Metadata["access_token"]; gotToken != "tok-2" {
		t.Fatalf("auth Metadata[access_token] = %v, want tok-2", gotToken)
	}
}

func TestHostAuthSaveCallbackPreservesPluginDisabledChildWhenFileDisabledFalse(t *testing.T) {
	authDir := t.TempDir()
	const fileName = "multi-source.json"

	host := newHostWithRecords(capabilityRecord{
		id: "multi-plugin",
		plugin: pluginapi.Plugin{
			Capabilities: pluginapi.Capabilities{
				AuthProvider: fakeAuthProvider{
					identifier: "demo",
					parseAuth: func(_ context.Context, _ pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
						return pluginapi.AuthParseResponse{
							Handled: true,
							Auths: []pluginapi.AuthData{
								{
									Provider: "demo",
									ID:       "child-active",
									FileName: fileName,
									Disabled: false,
								},
								{
									Provider: "demo",
									ID:       "child-disabled",
									FileName: fileName,
									Disabled: true,
								},
							},
						}, nil
					},
				},
			},
		},
	})
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	manager := coreauth.NewManager(nil, nil, nil)
	host.SetAuthManager(manager)

	reqSave, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: fileName,
		JSON: json.RawMessage(`{"type":"demo","disabled":false}`),
	})
	if errMarshal != nil {
		t.Fatalf("marshal save request: %v", errMarshal)
	}
	if _, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, reqSave); errCall != nil {
		t.Fatalf("callFromPlugin(host.auth.save) error = %v", errCall)
	}

	activeAuth, okActive := manager.GetByID("child-active")
	if !okActive || activeAuth == nil || activeAuth.Disabled || activeAuth.Status != coreauth.StatusActive {
		t.Fatalf("child-active = %#v, want enabled active auth", activeAuth)
	}
	disabledAuth, okDisabled := manager.GetByID("child-disabled")
	if !okDisabled || disabledAuth == nil || !disabledAuth.Disabled || disabledAuth.Status != coreauth.StatusDisabled {
		t.Fatalf("child-disabled = %#v, want plugin-disabled state preserved", disabledAuth)
	}
}

func TestHostAuthSaveCallbackUnregistersModelsForRemovedStaleSameFileAuth(t *testing.T) {
	authDir := t.TempDir()
	const (
		canonicalID = "demo-canonical-72a36b69"
		staleID     = canonicalID + ".json"
		fileName    = staleID
	)
	filePath := filepath.Join(authDir, fileName)
	if errWrite := os.WriteFile(filePath, []byte(`{"type":"demo","access_token":"tok-1"}`), 0o600); errWrite != nil {
		t.Fatalf("write initial auth file: %v", errWrite)
	}

	reg := registry.GetGlobalRegistry()
	reg.RegisterClient(staleID, "demo", []*registry.ModelInfo{{ID: "demo-model-1"}})
	t.Cleanup(func() {
		reg.UnregisterClient(staleID)
		reg.UnregisterClient(canonicalID)
	})
	if len(reg.GetModelsForClient(staleID)) == 0 {
		t.Fatal("expected initial models registered for stale client ID")
	}

	manager := coreauth.NewManager(nil, nil, nil)
	for _, id := range []string{canonicalID, staleID} {
		auth := &coreauth.Auth{
			ID:       id,
			Provider: "demo",
			FileName: fileName,
			Status:   coreauth.StatusActive,
			Attributes: map[string]string{
				"path":   filePath,
				"source": filePath,
			},
			Metadata: map[string]any{
				"type":         "demo",
				"access_token": "tok-1",
			},
		}
		if _, errRegister := manager.Register(context.Background(), auth); errRegister != nil {
			t.Fatalf("register auth %s: %v", id, errRegister)
		}
	}

	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	host.SetAuthManager(manager)

	reqSave, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{
		Name: fileName,
		JSON: json.RawMessage(`{"type":"demo","access_token":"tok-2"}`),
	})
	if errMarshal != nil {
		t.Fatalf("marshal save request: %v", errMarshal)
	}
	if _, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthSave, reqSave); errCall != nil {
		t.Fatalf("callFromPlugin(host.auth.save) error = %v", errCall)
	}

	if _, exists := manager.GetByID(staleID); exists {
		t.Fatalf("expected stale auth %q to be removed from manager", staleID)
	}
	if models := reg.GetModelsForClient(staleID); len(models) != 0 {
		t.Fatalf("GetModelsForClient(%q) = %#v, want empty slice after stale auth removal", staleID, models)
	}
}

func TestHostAuthSaveRelativeAuthDirPreservesIdentity(t *testing.T) {
	for _, relative := range []bool{false, true} {
		name := "absolute"
		if relative {
			name = "relative"
		}
		t.Run(name, func(t *testing.T) {
			root := t.TempDir()
			work := filepath.Join(root, "core")
			authDir := filepath.Join(root, "oauth")
			for _, dir := range []string{work, authDir} {
				if errMkdir := os.MkdirAll(dir, 0o700); errMkdir != nil {
					t.Fatal(errMkdir)
				}
			}
			t.Chdir(work)
			if relative {
				authDir = filepath.Join("..", "oauth")
			}
			const fileName = "demo-credential.json"
			payload := []byte(`{"type":"demo","access_token":"synthetic-not-a-real-token"}`)
			path := filepath.Join(authDir, fileName)
			if errWrite := os.WriteFile(path, payload, 0o600); errWrite != nil {
				t.Fatal(errWrite)
			}
			host := newHostWithRecords(capabilityRecord{
				id: "demo-plugin",
				plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
					AuthProvider: fakeAuthProvider{
						identifier: "demo",
						parseAuth: func(_ context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
							return pluginapi.AuthParseResponse{
								Handled: true,
								Auth: pluginapi.AuthData{
									Provider:    "demo",
									FileName:    req.FileName,
									StorageJSON: req.RawJSON,
								},
							}, nil
						},
					},
				}},
			})
			host.runtimeConfig = &config.Config{AuthDir: authDir}
			ctx := context.Background()
			initial, handled, errParse := host.ParseAuth(ctx, pluginapi.AuthParseRequest{
				Provider: "demo",
				Path:     path,
				FileName: fileName,
				RawJSON:  payload,
			})
			if errParse != nil || !handled || initial == nil || initial.ID != fileName {
				t.Fatalf("initial parse: auth=%#v handled=%v err=%v", initial, handled, errParse)
			}
			manager := coreauth.NewManager(nil, nil, nil)
			host.SetAuthManager(manager)
			if _, errRegister := manager.Register(coreauth.WithSkipPersist(ctx), initial); errRegister != nil {
				t.Fatal(errRegister)
			}
			reg := registry.GetGlobalRegistry()
			reg.RegisterClient(initial.ID, "demo", []*registry.ModelInfo{{ID: "demo-model"}})
			t.Cleanup(func() {
				reg.UnregisterClient(initial.ID)
				for _, auth := range manager.List() {
					reg.UnregisterClient(auth.ID)
				}
			})
			request, errMarshal := json.Marshal(pluginapi.HostAuthSaveRequest{Name: fileName, JSON: payload})
			if errMarshal != nil {
				t.Fatal(errMarshal)
			}
			if _, errCall := host.callFromPlugin(ctx, pluginabi.MethodHostAuthSave, request); errCall != nil {
				t.Fatal(errCall)
			}
			auths := manager.List()
			if len(auths) != 1 {
				t.Fatalf("got %d auth records, want 1", len(auths))
			}
			if auths[0].ID != initial.ID {
				t.Errorf("Core changed auth ID: before=%q after=%q", initial.ID, auths[0].ID)
			}
			if models := reg.GetModelsForClient(initial.ID); len(models) != 1 {
				t.Errorf("Core removed registered models: got %d, want 1", len(models))
			}
		})
	}
}

func TestHostAuthModelsCallbackReturnsAllCredentialsIncludingConfigBacked(t *testing.T) {
	authDir := t.TempDir()
	filePath := filepath.Join(authDir, "file-codex.json")
	if errWrite := os.WriteFile(filePath, []byte(`{"type":"codex","email":"codex@example.com"}`), 0o600); errWrite != nil {
		t.Fatalf("write auth file: %v", errWrite)
	}

	fileAuth := &coreauth.Auth{
		ID:       "file-codex.json",
		Provider: "codex",
		FileName: "file-codex.json",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"path":   filePath,
			"source": filePath,
		},
	}
	configAuth := &coreauth.Auth{
		ID:       "claude:apikey:cfg-1",
		Provider: "claude",
		Label:    "claude-apikey",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"source":       "config:claude[cfg-1]",
			"config_index": "0",
			"api_key":      "sk-ant-config-1",
		},
	}
	runtimeAuth := &coreauth.Auth{
		ID:       "aistudio-ws-1",
		Provider: "aistudio",
		Status:   coreauth.StatusActive,
		Attributes: map[string]string{
			"runtime_only": "true",
		},
	}

	manager := coreauth.NewManager(nil, nil, nil)
	ctx := coreauth.WithSkipPersist(context.Background())
	for _, a := range []*coreauth.Auth{fileAuth, configAuth, runtimeAuth} {
		registered, errRegister := manager.Register(ctx, a)
		if errRegister != nil {
			t.Fatalf("register auth %s: %v", a.ID, errRegister)
		}
		a.Index = registered.Index
	}

	reg := registry.GetGlobalRegistry()
	reg.RegisterClient(fileAuth.ID, "codex", []*registry.ModelInfo{
		{ID: "codex/gpt-5.6-sol"},
		{ID: "codex/gpt-6-astra"},
	})
	reg.RegisterClient(configAuth.ID, "claude", []*registry.ModelInfo{
		{ID: "claude-sonnet-5-5"},
	})
	reg.UnregisterClient(runtimeAuth.ID)
	t.Cleanup(func() {
		reg.UnregisterClient(fileAuth.ID)
		reg.UnregisterClient(configAuth.ID)
		reg.UnregisterClient(runtimeAuth.ID)
	})

	host := New()
	host.runtimeConfig = &config.Config{AuthDir: authDir}
	host.SetAuthManager(manager)

	// Verify all credentials (including config-backed API key) are returned when AuthIndex is empty.
	rawAll, errCallAll := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthModels, nil)
	if errCallAll != nil {
		t.Fatalf("callFromPlugin(host.auth.models, nil) error = %v", errCallAll)
	}
	respAll, errDecodeAll := decodeRPCEnvelope[pluginapi.HostAuthModelsResponse](rawAll)
	if errDecodeAll != nil {
		t.Fatalf("decode response: %v", errDecodeAll)
	}
	if len(respAll.Credentials) != 3 {
		t.Fatalf("credentials len = %d (%#v), want 3", len(respAll.Credentials), respAll.Credentials)
	}

	byID := make(map[string]pluginapi.HostAuthModels, len(respAll.Credentials))
	for _, cred := range respAll.Credentials {
		byID[cred.ID] = cred
	}

	gotConfig, okConfig := byID[configAuth.ID]
	if !okConfig {
		t.Fatalf("missing config-backed credential %q in %#v", configAuth.ID, respAll.Credentials)
	}
	if gotConfig.AuthIndex != configAuth.Index || gotConfig.Provider != "claude" || len(gotConfig.Models) != 1 || gotConfig.Models[0] != "claude-sonnet-5-5" {
		t.Fatalf("config credential = %#v, want index=%q provider=claude models=[claude-sonnet-5-5]", gotConfig, configAuth.Index)
	}

	gotFile, okFile := byID[fileAuth.ID]
	if !okFile {
		t.Fatalf("missing file-backed credential %q in %#v", fileAuth.ID, respAll.Credentials)
	}
	if gotFile.AuthIndex != fileAuth.Index || gotFile.Provider != "codex" || len(gotFile.Models) != 2 || gotFile.Models[0] != "codex/gpt-5.6-sol" || gotFile.Models[1] != "codex/gpt-6-astra" {
		t.Fatalf("file credential = %#v, want index=%q provider=codex models=[codex/gpt-5.6-sol codex/gpt-6-astra]", gotFile, fileAuth.Index)
	}

	gotRuntime, okRuntime := byID[runtimeAuth.ID]
	if !okRuntime {
		t.Fatalf("missing runtime-only credential %q in %#v", runtimeAuth.ID, respAll.Credentials)
	}
	if gotRuntime.AuthIndex != runtimeAuth.Index || gotRuntime.Provider != "aistudio" || gotRuntime.Models == nil || len(gotRuntime.Models) != 0 {
		t.Fatalf("runtime credential = %#v, want index=%q provider=aistudio non-nil empty models", gotRuntime, runtimeAuth.Index)
	}

	// Verify filtering by AuthIndex for a config-backed credential.
	reqFiltered, errMarshal := json.Marshal(pluginapi.HostAuthModelsRequest{AuthIndex: configAuth.Index})
	if errMarshal != nil {
		t.Fatalf("marshal filter request: %v", errMarshal)
	}
	rawFiltered, errCallFiltered := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthModels, reqFiltered)
	if errCallFiltered != nil {
		t.Fatalf("callFromPlugin(host.auth.models, filtered) error = %v", errCallFiltered)
	}
	respFiltered, errDecodeFiltered := decodeRPCEnvelope[pluginapi.HostAuthModelsResponse](rawFiltered)
	if errDecodeFiltered != nil {
		t.Fatalf("decode filtered response: %v", errDecodeFiltered)
	}
	if len(respFiltered.Credentials) != 1 || respFiltered.Credentials[0].ID != configAuth.ID || respFiltered.Credentials[0].AuthIndex != configAuth.Index {
		t.Fatalf("filtered credentials = %#v, want single entry for %q", respFiltered.Credentials, configAuth.ID)
	}

	// Verify host.auth.models is callable from response.intercept_after via rpcPluginAdapter.InterceptResponse.
	loader := newTestSymbolLoader()
	loader.lookups["catalog-filter"] = newTestSymbolLookup(&testPlugin{
		registerResult: pluginapi.Plugin{
			Metadata: pluginapi.Metadata{
				Name:             "catalog-filter",
				Version:          "1.0.0",
				Author:           "test",
				GitHubRepository: "https://github.com/router-for-me/CLIProxyAPI",
			},
			Capabilities: pluginapi.Capabilities{
				ResponseInterceptor: responseInterceptorFunc{
					interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
						rawModelsResp, errModels := host.callFromPlugin(ctx, pluginabi.MethodHostAuthModels, nil)
						if errModels != nil {
							return pluginapi.ResponseInterceptResponse{}, errModels
						}
						modelsResp, errDecode := decodeRPCEnvelope[pluginapi.HostAuthModelsResponse](rawModelsResp)
						if errDecode != nil {
							return pluginapi.ResponseInterceptResponse{}, errDecode
						}
						allowed := make(map[string]bool)
						for _, cred := range modelsResp.Credentials {
							if cred.Provider == "codex" {
								for _, m := range cred.Models {
									allowed[m] = true
								}
							}
						}
						var catalog struct {
							Data []struct {
								ID string `json:"id"`
							} `json:"data"`
						}
						if errUnmarshal := json.Unmarshal(req.Body, &catalog); errUnmarshal != nil {
							return pluginapi.ResponseInterceptResponse{}, errUnmarshal
						}
						filtered := catalog.Data[:0]
						for _, item := range catalog.Data {
							if allowed[item.ID] {
								filtered = append(filtered, item)
							}
						}
						catalog.Data = filtered
						out, errMarshalOut := json.Marshal(catalog)
						if errMarshalOut != nil {
							return pluginapi.ResponseInterceptResponse{}, errMarshalOut
						}
						return pluginapi.ResponseInterceptResponse{Body: out}, nil
					},
				},
			},
		},
	})
	host.loader = loader
	host.ApplyConfig(context.Background(), &config.Config{
		AuthDir: authDir,
		Plugins: config.PluginsConfig{
			Enabled: true,
			Dir:     makePluginDir(t, "catalog-filter"),
			Configs: enabledPluginConfigs("catalog-filter"),
		},
	})
	intercepted := host.InterceptResponse(context.Background(), pluginapi.ResponseInterceptRequest{
		SourceFormat: "openai",
		Body:         []byte(`{"data":[{"id":"codex/gpt-5.6-sol"},{"id":"claude-sonnet-5-5"}]}`),
	})
	if string(intercepted.Body) != `{"data":[{"id":"codex/gpt-5.6-sol"}]}` {
		t.Fatalf("intercepted catalog body = %s, want codex-only filtered catalog", string(intercepted.Body))
	}
}

func TestHostAuthModelsCallbackValidationAndEmptyManager(t *testing.T) {
	hostNoManager := New()
	rawEmpty, errCallEmpty := hostNoManager.callFromPlugin(context.Background(), pluginabi.MethodHostAuthModels, []byte(`{}`))
	if errCallEmpty != nil {
		t.Fatalf("callFromPlugin with nil authManager and empty auth_index error = %v, want nil", errCallEmpty)
	}
	respEmpty, errDecodeEmpty := decodeRPCEnvelope[pluginapi.HostAuthModelsResponse](rawEmpty)
	if errDecodeEmpty != nil {
		t.Fatalf("decode empty response: %v", errDecodeEmpty)
	}
	if respEmpty.Credentials == nil || len(respEmpty.Credentials) != 0 {
		t.Fatalf("empty manager credentials = %#v, want non-nil empty slice", respEmpty.Credentials)
	}

	reqMissingManager, _ := json.Marshal(pluginapi.HostAuthModelsRequest{AuthIndex: "idx-1"})
	if _, errCall := hostNoManager.callFromPlugin(context.Background(), pluginabi.MethodHostAuthModels, reqMissingManager); errCall == nil {
		t.Fatal("expected error when filtering by auth_index without auth manager, got nil")
	}

	host := New()
	host.SetAuthManager(coreauth.NewManager(nil, nil, nil))
	if _, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthModels, []byte("{invalid-json")); errCall == nil {
		t.Fatal("expected error on invalid JSON request, got nil")
	}
	reqUnknown, _ := json.Marshal(pluginapi.HostAuthModelsRequest{AuthIndex: "unknown-index"})
	if _, errCall := host.callFromPlugin(context.Background(), pluginabi.MethodHostAuthModels, reqUnknown); errCall == nil {
		t.Fatal("expected error for unknown auth_index, got nil")
	}
}