Ctrl K
ComunidadGuías…

internal/api/server_test.go

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

Archivo original ↗Preguntar a KiBot
package api

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/MoeForge/moego/ojson"
	gin "github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	managementHandlers "github.com/router-for-me/CLIProxyAPI/v8/internal/api/handlers/management"
	claudemodels "github.com/router-for-me/CLIProxyAPI/v8/internal/client/claude/models"
	codexmodels "github.com/router-for-me/CLIProxyAPI/v8/internal/client/codex/models"
	proxyconfig "github.com/router-for-me/CLIProxyAPI/v8/internal/config"
	"github.com/router-for-me/CLIProxyAPI/v8/internal/home"
	internallogging "github.com/router-for-me/CLIProxyAPI/v8/internal/logging"
	"github.com/router-for-me/CLIProxyAPI/v8/internal/pluginhost"
	"github.com/router-for-me/CLIProxyAPI/v8/internal/redisqueue"
	"github.com/router-for-me/CLIProxyAPI/v8/internal/registry"
	"github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor"
	runtimehelps "github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor/helps"
	sdkaccess "github.com/router-for-me/CLIProxyAPI/v8/sdk/access"
	"github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/auth"
	"github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/executionregistry"
	coreexecutor "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/executor"
	coreusage "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/usage"
	sdkconfig "github.com/router-for-me/CLIProxyAPI/v8/sdk/config"
	"github.com/router-for-me/CLIProxyAPI/v8/sdk/pluginapi"
	log "github.com/sirupsen/logrus"
	logtest "github.com/sirupsen/logrus/hooks/test"
	"gopkg.in/yaml.v3"
)

type codexSearchCaptureExecutor struct {
	request      *http.Request
	body         []byte
	authIDs      []string
	prepareErr   error
	httpErr      error
	responseBody io.ReadCloser
	statuses     []int
	refreshCalls int
	httpCalls    int
	beforeReturn func()
}

func (e *codexSearchCaptureExecutor) Identifier() string { return "codex" }

func (e *codexSearchCaptureExecutor) Execute(context.Context, *auth.Auth, coreexecutor.Request, coreexecutor.Options) (coreexecutor.Response, error) {
	return coreexecutor.Response{}, nil
}

func (e *codexSearchCaptureExecutor) ExecuteStream(context.Context, *auth.Auth, coreexecutor.Request, coreexecutor.Options) (*coreexecutor.StreamResult, error) {
	return nil, nil
}

func (e *codexSearchCaptureExecutor) Refresh(_ context.Context, a *auth.Auth) (*auth.Auth, error) {
	e.refreshCalls++
	updated := a.Clone()
	if updated.Metadata == nil {
		updated.Metadata = make(map[string]any)
	}
	updated.Metadata["access_token"] = "refreshed-home-search-token"
	return updated, nil
}

func (e *codexSearchCaptureExecutor) CountTokens(context.Context, *auth.Auth, coreexecutor.Request, coreexecutor.Options) (coreexecutor.Response, error) {
	return coreexecutor.Response{}, nil
}

func (e *codexSearchCaptureExecutor) PrepareRequest(req *http.Request, a *auth.Auth) error {
	if e.prepareErr != nil {
		return e.prepareErr
	}
	token, _ := a.Metadata["access_token"].(string)
	if strings.TrimSpace(token) == "" && a.Attributes != nil {
		token = a.Attributes[auth.AttributeAPIKey]
	}
	if strings.TrimSpace(token) != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	return nil
}

type codexSearchGinContextSelector struct {
	ginContext *gin.Context
}

func (s *codexSearchGinContextSelector) Pick(ctx context.Context, _ string, _ string, _ coreexecutor.Options, auths []*auth.Auth) (*auth.Auth, error) {
	s.ginContext, _ = ctx.Value("gin").(*gin.Context)
	if len(auths) == 0 {
		return nil, nil
	}
	return auths[0], nil
}

type codexSearchAPIKeyFirstSelector struct{}

type codexSearchModelRouter struct {
	response pluginapi.ModelRouteResponse
	handled  bool
	requests []pluginapi.ModelRouteRequest
}

func (r *codexSearchModelRouter) RouteModel(_ context.Context, req pluginapi.ModelRouteRequest) (pluginapi.ModelRouteResponse, bool) {
	r.requests = append(r.requests, req)
	return r.response, r.handled
}

func (s *codexSearchAPIKeyFirstSelector) Pick(_ context.Context, _ string, _ string, _ coreexecutor.Options, auths []*auth.Auth) (*auth.Auth, error) {
	for _, candidate := range auths {
		if candidate.AuthKind() == auth.AuthKindAPIKey {
			return candidate, nil
		}
	}
	if len(auths) == 0 {
		return nil, nil
	}
	return auths[0], nil
}

func (e *codexSearchCaptureExecutor) HttpRequest(_ context.Context, selected *auth.Auth, req *http.Request) (*http.Response, error) {
	if e.httpErr != nil {
		return nil, e.httpErr
	}
	e.request = req.Clone(req.Context())
	e.authIDs = append(e.authIDs, selected.ID)
	e.httpCalls++
	body, err := io.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}
	e.body = body
	responseBody := e.responseBody
	if responseBody == nil {
		responseBody = io.NopCloser(strings.NewReader(`{"results":[{"url":"https://example.com"}]}`))
	}
	statusCode := http.StatusOK
	if e.httpCalls <= len(e.statuses) && e.statuses[e.httpCalls-1] > 0 {
		statusCode = e.statuses[e.httpCalls-1]
	}
	if e.beforeReturn != nil {
		e.beforeReturn()
	}
	return &http.Response{
		StatusCode: statusCode,
		Header:     http.Header{"Content-Type": []string{"application/json"}},
		Body:       responseBody,
	}, nil
}

type codexSearchHomeDispatcher struct {
	authID string
	calls  atomic.Int32
	policy atomic.Value
}

type homeUnauthorizedUsageCapture struct {
	authID  string
	records chan coreusage.Record
}

func (p *homeUnauthorizedUsageCapture) HandleUsage(_ context.Context, record coreusage.Record) {
	if p == nil || record.ExecutorType != "home-result" || record.AuthID != p.authID {
		return
	}
	select {
	case p.records <- record:
	default:
	}
}

func (p *homeUnauthorizedUsageCapture) wait(t *testing.T) coreusage.Record {
	t.Helper()
	select {
	case record := <-p.records:
		return record
	case <-time.After(time.Second):
		t.Fatal("timed out waiting for Home unauthorized usage record")
		return coreusage.Record{}
	}
}

type noopHomeUnauthorizedUsagePlugin struct{}

func (noopHomeUnauthorizedUsagePlugin) HandleUsage(context.Context, coreusage.Record) {}

func registerHomeUnauthorizedUsageCapture(t *testing.T, name, authID string) *homeUnauthorizedUsageCapture {
	t.Helper()
	capture := &homeUnauthorizedUsageCapture{authID: authID, records: make(chan coreusage.Record, 1)}
	coreusage.RegisterNamedPlugin(name, capture)
	t.Cleanup(func() {
		coreusage.RegisterNamedPlugin(name, noopHomeUnauthorizedUsagePlugin{})
	})
	return capture
}

func (*codexSearchHomeDispatcher) HeartbeatOK() bool { return true }

func (d *codexSearchHomeDispatcher) RPopAuth(_ context.Context, model string, _ string, _ http.Header, _ int) ([]byte, error) {
	d.calls.Add(1)
	authID := d.authID
	if authID == "" {
		authID = "home-codex-search"
	}
	return json.Marshal(map[string]any{
		"model":      model,
		"auth_index": authID,
		"auth": map[string]any{
			"id":       authID,
			"provider": "codex",
			"status":   "active",
			"metadata": map[string]any{"access_token": "home-search-token"},
		},
		"concurrency": map[string]any{
			"accounted":     true,
			"credential_id": authID,
			"model":         model,
		},
	})
}

func (d *codexSearchHomeDispatcher) RPopAuthWithPolicy(ctx context.Context, model string, sessionID string, headers http.Header, count int, policy string) ([]byte, error) {
	d.policy.Store(policy)
	return d.RPopAuth(ctx, model, sessionID, headers, count)
}

func (*codexSearchHomeDispatcher) AbortAmbiguousDispatch() {}

type codexSearchBusyHomeDispatcher struct{}

func (*codexSearchBusyHomeDispatcher) HeartbeatOK() bool { return true }
func (*codexSearchBusyHomeDispatcher) RPopAuth(context.Context, string, string, http.Header, int) ([]byte, error) {
	return []byte(`{"error":{"type":"credential_concurrency_exceeded","message":"busy","retry_after_ms":750}}`), nil
}
func (d *codexSearchBusyHomeDispatcher) RPopAuthWithPolicy(ctx context.Context, model string, sessionID string, headers http.Header, count int, _ string) ([]byte, error) {
	return d.RPopAuth(ctx, model, sessionID, headers, count)
}
func (*codexSearchBusyHomeDispatcher) AbortAmbiguousDispatch() {}

type trackedSearchResponseBody struct {
	io.Reader
	closed atomic.Bool
}

func (b *trackedSearchResponseBody) Close() error {
	b.closed.Store(true)
	return nil
}

type errorSearchResponseBody struct {
	payload []byte
	read    atomic.Bool
	closed  atomic.Bool
}

func (b *errorSearchResponseBody) Read(p []byte) (int, error) {
	if !b.read.CompareAndSwap(false, true) {
		return 0, io.EOF
	}
	return copy(p, b.payload), io.ErrUnexpectedEOF
}

func (b *errorSearchResponseBody) Close() error {
	b.closed.Store(true)
	return nil
}

type drainAwareSearchResponseBody struct {
	started   chan struct{}
	closed    chan struct{}
	startOnce sync.Once
	closeOnce sync.Once
}

func newDrainAwareSearchResponseBody() *drainAwareSearchResponseBody {
	return &drainAwareSearchResponseBody{started: make(chan struct{}), closed: make(chan struct{})}
}

func (b *drainAwareSearchResponseBody) Read([]byte) (int, error) {
	b.startOnce.Do(func() { close(b.started) })
	<-b.closed
	return 0, io.EOF
}

func (b *drainAwareSearchResponseBody) Close() error {
	b.closeOnce.Do(func() { close(b.closed) })
	return nil
}

func TestAuditHomeBusyNormalAndStream429Headers(t *testing.T) {
	for _, stream := range []bool{false, true} {
		t.Run(map[bool]string{false: "normal", true: "stream"}[stream], func(t *testing.T) {
			server := newTestServer(t)
			server.handlers.AuthManager.SetConfig(&proxyconfig.Config{Home: proxyconfig.HomeConfig{Enabled: true}})
			server.handlers.AuthManager.PublishHomeDispatch(&codexSearchBusyHomeDispatcher{}, executionregistry.New(), 1)

			body := `{"model":"gpt-5-codex","input":[]}`
			if stream {
				body = `{"model":"gpt-5-codex","input":[],"stream":true}`
			}
			rr := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body))
			req.Header.Set("Authorization", "Bearer test-key")
			server.engine.ServeHTTP(rr, req)

			if rr.Code != http.StatusTooManyRequests {
				t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusTooManyRequests, rr.Body.String())
			}
			if got := rr.Header().Get("Retry-After"); got != "1" {
				t.Fatalf("Retry-After = %q, want 1", got)
			}
		})
	}
}

func TestAuditHomeCodexSearchBusyReturnsTrustedRetryAfter(t *testing.T) {
	server := newTestServer(t)
	server.handlers.AuthManager.SetConfig(&proxyconfig.Config{Home: proxyconfig.HomeConfig{Enabled: true}})
	server.handlers.AuthManager.PublishHomeDispatch(&codexSearchBusyHomeDispatcher{}, executionregistry.New(), 1)

	rr := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"model":"gpt-5-codex","query":"test"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusTooManyRequests {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusTooManyRequests, rr.Body.String())
	}
	if got := rr.Header().Get("Retry-After"); got != "1" {
		t.Fatalf("Retry-After = %q, want 1", got)
	}
	if !strings.Contains(rr.Body.String(), "busy") {
		t.Fatalf("body = %q, want busy error", rr.Body.String())
	}
}

func TestAuditHomeCodexSearchBodyCloseBeforeRelease(t *testing.T) {
	server := newTestServer(t)
	dispatcher := &codexSearchHomeDispatcher{}
	registry := executionregistry.New()
	body := newDrainAwareSearchResponseBody()
	var releaseAfterBodyClose atomic.Bool
	var releaseCount atomic.Int32
	registry.SetReleaseSink(func(group executionregistry.ReleaseGroup, _ int64) {
		if group != (executionregistry.ReleaseGroup{CredentialID: "home-codex-search", Model: "gpt-5-codex"}) {
			t.Errorf("release group = %#v", group)
		}
		select {
		case <-body.closed:
			releaseAfterBodyClose.Store(true)
		default:
		}
		releaseCount.Add(1)
	})
	server.handlers.AuthManager.SetConfig(&proxyconfig.Config{Home: proxyconfig.HomeConfig{Enabled: true}})
	server.handlers.AuthManager.PublishHomeDispatch(dispatcher, registry, 1)
	executor := &codexSearchCaptureExecutor{responseBody: body}
	server.handlers.AuthManager.RegisterExecutor(executor)

	rr := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"id":"home-search-drain","model":"gpt-5-codex","query":"test"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	handlerDone := make(chan struct{})
	go func() {
		server.engine.ServeHTTP(rr, req)
		close(handlerDone)
	}()

	select {
	case <-body.started:
	case <-time.After(time.Second):
		t.Fatal("search handler did not start reading the response body")
	}
	if errDrain := registry.Drain(context.Background()); errDrain != nil {
		t.Fatalf("Drain() error = %v", errDrain)
	}
	if got := releaseCount.Load(); got != 1 {
		t.Fatalf("accounted releases = %d, want 1", got)
	}
	if !releaseAfterBodyClose.Load() {
		t.Fatal("accounted Home selection released before the search response body closed")
	}
	select {
	case <-handlerDone:
	case <-time.After(time.Second):
		t.Fatal("search handler remained blocked after Home drain")
	}
	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
}

func TestHomeCodexAlphaSearchForwardsUnauthorizedResponseWithoutRefresh(t *testing.T) {
	const upstreamError = `{"error":{"message":"access token expired"}}`
	server := newTestServer(t)
	server.cfg.RequestLog = true
	dispatcher := &codexSearchHomeDispatcher{}
	server.handlers.AuthManager.SetConfig(&proxyconfig.Config{Home: proxyconfig.HomeConfig{Enabled: true}})
	server.handlers.AuthManager.PublishHomeDispatch(dispatcher, executionregistry.New(), 1)
	executor := &codexSearchCaptureExecutor{
		statuses:     []int{http.StatusUnauthorized},
		responseBody: io.NopCloser(strings.NewReader(upstreamError)),
	}
	server.handlers.AuthManager.RegisterExecutor(executor)

	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"id":"home-search-refresh","model":"gpt-5-codex","query":"test"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(rr)
	c.Request = req
	server.codexAlphaSearch(c)

	if rr.Code != http.StatusUnauthorized {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusUnauthorized, rr.Body.String())
	}
	if got := rr.Body.String(); got != upstreamError {
		t.Fatalf("body = %q, want original upstream error %q", got, upstreamError)
	}
	if executor.refreshCalls != 0 || executor.httpCalls != 1 {
		t.Fatalf("refresh/http calls = %d/%d, want 0/1", executor.refreshCalls, executor.httpCalls)
	}
	if got := executor.request.Header.Get("Authorization"); got != "Bearer home-search-token" {
		t.Fatalf("Authorization = %q, want original Home token", got)
	}
	if got := dispatcher.calls.Load(); got != 1 {
		t.Fatalf("Home RPOP calls = %d, want 1", got)
	}
	rawAPIResponse, okResponse := c.Get("API_RESPONSE")
	if !okResponse {
		t.Fatal("API_RESPONSE was not captured")
	}
	apiResponse, _ := rawAPIResponse.([]byte)
	if !strings.Contains(string(apiResponse), "Status: 401") || !strings.Contains(string(apiResponse), upstreamError) {
		t.Fatalf("API_RESPONSE = %q, want original upstream 401", apiResponse)
	}
}

func TestHomeCodexAlphaSearchReportsUnauthorizedBeforeEarlyReturn(t *testing.T) {
	const upstreamError = `{"error":{"message":"access token expired"}}`
	tests := []struct {
		name         string
		responseBody func() io.ReadCloser
		beforeReturn func(*executionregistry.Registry)
		wantStatus   int
		wantFailBody string
	}{
		{
			name: "response bind failure",
			responseBody: func() io.ReadCloser {
				return &trackedSearchResponseBody{Reader: strings.NewReader(upstreamError)}
			},
			beforeReturn: func(registry *executionregistry.Registry) {
				_ = registry.Close()
			},
			wantStatus:   http.StatusServiceUnavailable,
			wantFailBody: "upstream unauthorized",
		},
		{
			name: "response read failure",
			responseBody: func() io.ReadCloser {
				return &errorSearchResponseBody{payload: []byte(upstreamError)}
			},
			wantStatus:   http.StatusBadGateway,
			wantFailBody: upstreamError,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			server := newTestServer(t)
			registry := executionregistry.New()
			server.handlers.AuthManager.SetConfig(&proxyconfig.Config{Home: proxyconfig.HomeConfig{Enabled: true}})
			testAuthID := "home-codex-search-" + strings.ReplaceAll(test.name, " ", "-")
			server.handlers.AuthManager.PublishHomeDispatch(&codexSearchHomeDispatcher{authID: testAuthID}, registry, 1)
			executor := &codexSearchCaptureExecutor{
				statuses:     []int{http.StatusUnauthorized},
				responseBody: test.responseBody(),
			}
			if test.beforeReturn != nil {
				executor.beforeReturn = func() { test.beforeReturn(registry) }
			}
			server.handlers.AuthManager.RegisterExecutor(executor)
			usageCapture := registerHomeUnauthorizedUsageCapture(t, t.Name(), testAuthID)

			recorder := httptest.NewRecorder()
			request := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"model":"gpt-5-codex","query":"test"}`))
			request.Header.Set("Authorization", "Bearer test-key")
			server.engine.ServeHTTP(recorder, request)

			if recorder.Code != test.wantStatus {
				t.Fatalf("status = %d, want %d; body=%s", recorder.Code, test.wantStatus, recorder.Body.String())
			}
			record := usageCapture.wait(t)
			if record.Fail.StatusCode != http.StatusUnauthorized || record.Fail.Body != test.wantFailBody {
				t.Fatalf("Home unauthorized failure = status %d body %q, want status 401 body %q", record.Fail.StatusCode, record.Fail.Body, test.wantFailBody)
			}
		})
	}
}

func TestHomeCodexAlphaSearchRequestLogPreservesBodyReturnedWithReadError(t *testing.T) {
	const upstreamError = `{"error":{"message":"access token expired"}}`
	server := newTestServer(t)
	server.cfg.RequestLog = true
	server.handlers.AuthManager.SetConfig(&proxyconfig.Config{Home: proxyconfig.HomeConfig{Enabled: true}})
	server.handlers.AuthManager.PublishHomeDispatch(&codexSearchHomeDispatcher{}, executionregistry.New(), 1)
	server.handlers.AuthManager.RegisterExecutor(&codexSearchCaptureExecutor{
		statuses:     []int{http.StatusUnauthorized},
		responseBody: &errorSearchResponseBody{payload: []byte(upstreamError)},
	})

	recorder := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(recorder)
	c.Request = httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"model":"gpt-5-codex","query":"test"}`))
	server.codexAlphaSearch(c)

	if recorder.Code != http.StatusBadGateway {
		t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusBadGateway, recorder.Body.String())
	}
	rawAPIResponse, okResponse := c.Get("API_RESPONSE")
	apiResponse, _ := rawAPIResponse.([]byte)
	if !okResponse || !strings.Contains(string(apiResponse), upstreamError) || !strings.Contains(string(apiResponse), io.ErrUnexpectedEOF.Error()) {
		t.Fatalf("API_RESPONSE = %q, want upstream body and read error", apiResponse)
	}
}

func TestHomeCodexAlphaSearchEndsSelectionAcrossDirectHTTPPaths(t *testing.T) {
	tests := []struct {
		name       string
		configure  func(*codexSearchCaptureExecutor, *trackedSearchResponseBody)
		wantStatus int
		wantClosed bool
	}{
		{
			name: "request build failure",
			configure: func(executor *codexSearchCaptureExecutor, _ *trackedSearchResponseBody) {
				executor.prepareErr = errors.New("request preparation failed")
			},
			wantStatus: http.StatusBadGateway,
		},
		{
			name: "HTTP error",
			configure: func(executor *codexSearchCaptureExecutor, _ *trackedSearchResponseBody) {
				executor.httpErr = errors.New("upstream unavailable")
			},
			wantStatus: http.StatusBadGateway,
		},
		{
			name: "response body close",
			configure: func(executor *codexSearchCaptureExecutor, body *trackedSearchResponseBody) {
				executor.responseBody = body
			},
			wantStatus: http.StatusOK,
			wantClosed: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			server := newTestServer(t)
			dispatcher := &codexSearchHomeDispatcher{}
			registry := executionregistry.New()
			server.handlers.AuthManager.SetConfig(&proxyconfig.Config{Home: proxyconfig.HomeConfig{Enabled: true}})
			server.handlers.AuthManager.PublishHomeDispatch(dispatcher, registry, 1)
			body := &trackedSearchResponseBody{Reader: strings.NewReader(`{"results":[]}`)}
			executor := &codexSearchCaptureExecutor{}
			test.configure(executor, body)
			server.handlers.AuthManager.RegisterExecutor(executor)

			req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"id":"home-search-session","model":"gpt-5-codex","query":"test"}`))
			req.Header.Set("Authorization", "Bearer test-key")
			rr := httptest.NewRecorder()
			server.engine.ServeHTTP(rr, req)
			if rr.Code != test.wantStatus {
				t.Fatalf("status = %d, want %d; body=%s", rr.Code, test.wantStatus, rr.Body.String())
			}
			if got := dispatcher.calls.Load(); got != 1 {
				t.Fatalf("Home RPOP calls = %d, want 1", got)
			}
			if got, _ := dispatcher.policy.Load().(string); got != auth.CredentialPolicyCodexAlphaSearchV1 {
				t.Fatalf("Home credential policy = %q, want %q", got, auth.CredentialPolicyCodexAlphaSearchV1)
			}
			if got := body.closed.Load(); got != test.wantClosed {
				t.Fatalf("response body closed = %t, want %t", got, test.wantClosed)
			}
			if errDrain := registry.Drain(context.Background()); errDrain != nil {
				t.Fatalf("Drain() error = %v", errDrain)
			}
		})
	}
}

func newTestServer(t *testing.T) *Server {
	t.Helper()
	return newTestServerWithOptions(t)
}

func newTestServerWithOptions(t *testing.T, opts ...ServerOption) *Server {
	t.Helper()
	cfg := &proxyconfig.Config{
		SDKConfig: sdkconfig.SDKConfig{
			APIKeys: []string{"test-key"},
		},
	}
	return newTestServerWithConfig(t, cfg, opts...)
}

func newTestServerWithConfig(t *testing.T, cfg *proxyconfig.Config, opts ...ServerOption) *Server {
	t.Helper()

	gin.SetMode(gin.TestMode)

	tmpDir := t.TempDir()
	authDir := filepath.Join(tmpDir, "auth")
	if err := os.MkdirAll(authDir, 0o700); err != nil {
		t.Fatalf("failed to create auth dir: %v", err)
	}

	cfg.Port = 0
	cfg.AuthDir = authDir
	cfg.Debug = true
	cfg.LoggingToFile = false
	cfg.UsageStatisticsEnabled = false

	authManager := auth.NewManager(nil, nil, nil)
	accessManager := sdkaccess.NewManager()

	configPath := filepath.Join(tmpDir, "config.yaml")
	return NewServer(cfg, authManager, accessManager, configPath, opts...)
}

func TestNewServerAppliesTrustedProxyConfiguration(t *testing.T) {
	server := newTestServerWithConfig(t, &proxyconfig.Config{
		TrustedProxies: []string{"192.0.2.0/24"},
	})
	server.engine.GET("/test-client-ip", func(c *gin.Context) {
		c.String(http.StatusOK, c.ClientIP())
	})

	tests := []struct {
		name       string
		remoteAddr string
		wantIP     string
	}{
		{name: "trusted proxy", remoteAddr: "192.0.2.10:43123", wantIP: "203.0.113.5"},
		{name: "untrusted peer", remoteAddr: "198.51.100.20:43123", wantIP: "198.51.100.20"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/test-client-ip", nil)
			req.RemoteAddr = test.remoteAddr
			req.Header.Set("X-Forwarded-For", "203.0.113.5")
			recorder := httptest.NewRecorder()
			server.engine.ServeHTTP(recorder, req)
			if got := recorder.Body.String(); got != test.wantIP {
				t.Fatalf("client IP = %q, want %q", got, test.wantIP)
			}
		})
	}

	defaultServer := newTestServer(t)
	defaultServer.engine.GET("/test-client-ip", func(c *gin.Context) {
		c.String(http.StatusOK, c.ClientIP())
	})
	request := httptest.NewRequest(http.MethodGet, "/test-client-ip", nil)
	request.RemoteAddr = "198.51.100.20:43123"
	request.Header.Set("X-Forwarded-For", "203.0.113.5")
	recorder := httptest.NewRecorder()
	defaultServer.engine.ServeHTTP(recorder, request)
	if got := recorder.Body.String(); got != "198.51.100.20" {
		t.Fatalf("default client IP = %q, want direct peer IP", got)
	}
}

func TestHealthz(t *testing.T) {
	server := newTestServer(t)

	t.Run("GET", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)

		if rr.Code != http.StatusOK {
			t.Fatalf("unexpected status code: got %d want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}

		var resp struct {
			Status string `json:"status"`
		}
		if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
			t.Fatalf("failed to parse response JSON: %v; body=%s", err, rr.Body.String())
		}
		if resp.Status != "ok" {
			t.Fatalf("unexpected response status: got %q want %q", resp.Status, "ok")
		}
	})

	t.Run("HEAD", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodHead, "/healthz", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)

		if rr.Code != http.StatusOK {
			t.Fatalf("unexpected status code: got %d want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
		if rr.Body.Len() != 0 {
			t.Fatalf("expected empty body for HEAD request, got %q", rr.Body.String())
		}
	})
}

func TestHealthzAccessLogging(t *testing.T) {
	server := newTestServer(t)
	previousHome := home.Current()
	home.ClearCurrent()
	t.Cleanup(func() { home.SetCurrent(previousHome) })
	logger := log.StandardLogger()
	previousHooks := logger.ReplaceHooks(make(log.LevelHooks))
	previousLevel := logger.GetLevel()
	hook := logtest.NewLocal(logger)
	logger.SetLevel(log.InfoLevel)
	t.Cleanup(func() { logger.ReplaceHooks(previousHooks); logger.SetLevel(previousLevel) })
	for _, tc := range []struct {
		name        string
		homeEnabled bool
		status      int
	}{
		{"healthy", false, http.StatusOK},
		{"home_unavailable", true, http.StatusServiceUnavailable},
	} {
		t.Run(tc.name, func(t *testing.T) {
			server.cfg.Home.Enabled = tc.homeEnabled
			for _, method := range []string{http.MethodGet, http.MethodHead} {
				t.Run(method, func(t *testing.T) {
					hook.Reset()
					recorder := httptest.NewRecorder()
					server.engine.ServeHTTP(recorder, httptest.NewRequest(method, "/healthz", nil))
					if recorder.Code != tc.status {
						t.Fatalf("status = %d, want %d", recorder.Code, tc.status)
					}
					count := 0
					for _, entry := range hook.AllEntries() {
						if _, ok := entry.Data["request_id"]; ok && strings.Contains(entry.Message, `"/healthz"`) {
							count++
							if tc.homeEnabled && entry.Level != log.ErrorLevel {
								t.Errorf("failed probe log level = %v, want error", entry.Level)
							}
						}
					}
					wantCount := 0
					if tc.homeEnabled {
						wantCount = 1
					}
					if count != wantCount {
						t.Errorf("probe access logs = %d, want %d", count, wantCount)
					}
					hook.Reset()
					server.engine.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/healthz-access-log-control", nil))
					for _, entry := range hook.AllEntries() {
						if _, ok := entry.Data["request_id"]; ok && strings.Contains(entry.Message, `"/healthz-access-log-control"`) {
							return
						}
					}
					t.Error("ordinary request did not emit an access log after health probe")
				})
			}
		})
	}
}

func TestCodexLiveRoutesRequireAuthAndAreRegistered(t *testing.T) {
	server := newTestServer(t)

	for _, path := range []string{"/v1/live", "/v1/realtime/calls"} {
		unauthorized := httptest.NewRequest(http.MethodPost, path, nil)
		unauthorizedRecorder := httptest.NewRecorder()
		server.engine.ServeHTTP(unauthorizedRecorder, unauthorized)
		if unauthorizedRecorder.Code != http.StatusUnauthorized {
			t.Fatalf("%s unauthorized status = %d, want %d", path, unauthorizedRecorder.Code, http.StatusUnauthorized)
		}

		authorized := httptest.NewRequest(http.MethodPost, path, nil)
		authorized.Header.Set("Authorization", "Bearer test-key")
		authorizedRecorder := httptest.NewRecorder()
		server.engine.ServeHTTP(authorizedRecorder, authorized)
		if authorizedRecorder.Code != http.StatusServiceUnavailable {
			t.Fatalf("%s authorized status = %d, want %d; body=%s", path, authorizedRecorder.Code, http.StatusServiceUnavailable, authorizedRecorder.Body.String())
		}
	}

	for _, path := range []string{"/v1/live/call-123", "/v1/realtime/calls/call-123", "/v1/realtime?call_id=call-123"} {
		unauthorized := httptest.NewRequest(http.MethodGet, path, nil)
		unauthorized.Header.Set("Upgrade", "websocket")
		unauthorized.Header.Set("Connection", "Upgrade")
		unauthorizedRecorder := httptest.NewRecorder()
		server.engine.ServeHTTP(unauthorizedRecorder, unauthorized)
		if unauthorizedRecorder.Code != http.StatusUnauthorized {
			t.Fatalf("%s unauthorized status = %d, want %d", path, unauthorizedRecorder.Code, http.StatusUnauthorized)
		}

		authorized := httptest.NewRequest(http.MethodGet, path, nil)
		authorized.Header.Set("Authorization", "Bearer test-key")
		authorizedRecorder := httptest.NewRecorder()
		server.engine.ServeHTTP(authorizedRecorder, authorized)
		if authorizedRecorder.Code != http.StatusUpgradeRequired {
			t.Fatalf("%s authorized status = %d, want %d; body=%s", path, authorizedRecorder.Code, http.StatusUpgradeRequired, authorizedRecorder.Body.String())
		}
	}
}

func TestRealtimeStandardRoutesAndClientSecretAuth(t *testing.T) {
	server := newTestServer(t)

	unauthorizedSecret := httptest.NewRequest(http.MethodPost, "/v1/realtime/client_secrets", strings.NewReader(`{"session":{"type":"realtime","model":"gpt-realtime"}}`))
	unauthorizedSecretRecorder := httptest.NewRecorder()
	server.engine.ServeHTTP(unauthorizedSecretRecorder, unauthorizedSecret)
	if unauthorizedSecretRecorder.Code != http.StatusUnauthorized {
		t.Fatalf("client_secrets unauthorized status = %d, want %d", unauthorizedSecretRecorder.Code, http.StatusUnauthorized)
	}
	var unauthorizedResponse struct {
		Error struct {
			Type string `json:"type"`
			Code string `json:"code"`
		} `json:"error"`
	}
	if errUnmarshal := json.Unmarshal(unauthorizedSecretRecorder.Body.Bytes(), &unauthorizedResponse); errUnmarshal != nil {
		t.Fatalf("unmarshal unauthorized response: %v", errUnmarshal)
	}
	if unauthorizedResponse.Error.Type != "authentication_error" || unauthorizedResponse.Error.Code != "invalid_api_key" {
		t.Fatalf("unauthorized error = %+v", unauthorizedResponse.Error)
	}

	secretRequest := httptest.NewRequest(http.MethodPost, "/v1/realtime/client_secrets", strings.NewReader(`{"session":{"type":"realtime","model":"gpt-realtime"}}`))
	secretRequest.Header.Set("Authorization", "Bearer test-key")
	secretRecorder := httptest.NewRecorder()
	server.engine.ServeHTTP(secretRecorder, secretRequest)
	if secretRecorder.Code != http.StatusOK {
		t.Fatalf("client_secrets status = %d, want %d; body=%s", secretRecorder.Code, http.StatusOK, secretRecorder.Body.String())
	}
	var secretResponse struct {
		Value string `json:"value"`
	}
	if errUnmarshal := json.Unmarshal(secretRecorder.Body.Bytes(), &secretResponse); errUnmarshal != nil {
		t.Fatalf("unmarshal client secret: %v", errUnmarshal)
	}
	if secretResponse.Value == "" {
		t.Fatal("client secret is empty")
	}

	callRequest := httptest.NewRequest(http.MethodPost, "/v1/realtime/calls", strings.NewReader("v=0\r\n"))
	callRequest.Header.Set("Authorization", "Bearer "+secretResponse.Value)
	callRequest.Header.Set("Content-Type", "application/sdp")
	callRecorder := httptest.NewRecorder()
	server.engine.ServeHTTP(callRecorder, callRequest)
	if callRecorder.Code != http.StatusServiceUnavailable {
		t.Fatalf("ephemeral call status = %d, want %d; body=%s", callRecorder.Code, http.StatusServiceUnavailable, callRecorder.Body.String())
	}

	for _, testCase := range []struct {
		method string
		path   string
		status int
	}{
		{method: http.MethodGet, path: "/v1/realtime?model=gpt-realtime", status: http.StatusUpgradeRequired},
		{method: http.MethodPost, path: "/v1/realtime", status: http.StatusServiceUnavailable},
		{method: http.MethodPost, path: "/v1/realtime/sessions", status: http.StatusOK},
		{method: http.MethodPost, path: "/v1/realtime/transcription_sessions", status: http.StatusNotImplemented},
		{method: http.MethodGet, path: "/v1/realtime/translations", status: http.StatusNotImplemented},
		{method: http.MethodPost, path: "/v1/realtime/translations", status: http.StatusNotImplemented},
		{method: http.MethodPost, path: "/v1/realtime/translations/client_secrets", status: http.StatusNotImplemented},
		{method: http.MethodPost, path: "/v1/realtime/calls/call-123/accept", status: http.StatusNotImplemented},
		{method: http.MethodPost, path: "/v1/realtime/calls/call-123/reject", status: http.StatusNotImplemented},
		{method: http.MethodPost, path: "/v1/realtime/calls/call-123/refer", status: http.StatusNotImplemented},
		{method: http.MethodPost, path: "/v1/realtime/calls/call-123/hangup", status: http.StatusNotFound},
	} {
		request := httptest.NewRequest(testCase.method, testCase.path, nil)
		request.Header.Set("Authorization", "Bearer test-key")
		recorder := httptest.NewRecorder()
		server.engine.ServeHTTP(recorder, request)
		if recorder.Code != testCase.status {
			t.Errorf("%s %s status = %d, want %d; body=%s", testCase.method, testCase.path, recorder.Code, testCase.status, recorder.Body.String())
		}
		if testCase.method == http.MethodGet && testCase.path == "/v1/realtime?model=gpt-realtime" && recorder.Header().Get("Upgrade") != "websocket" {
			t.Errorf("Upgrade header = %q, want websocket", recorder.Header().Get("Upgrade"))
		}
	}
}

func TestCodexAlphaSearchForwardsRequest(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-auth",
		Provider: "codex",
		Status:   auth.StatusActive,
		Metadata: map[string]any{"access_token": "codex-token", "account_id": "account-123"},
	}
	if _, err := server.handlers.AuthManager.Register(context.Background(), credential); err != nil {
		t.Fatalf("register Codex auth: %v", err)
	}

	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"query":"GPT-5.6"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Session_id", "session-123")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	if executor.request == nil {
		t.Fatal("Codex executor did not receive a request")
	}
	if got, want := executor.request.URL.String(), "https://chatgpt.com/backend-api/codex/alpha/search"; got != want {
		t.Fatalf("upstream URL = %q, want %q", got, want)
	}
	if got, want := string(executor.body), `{"query":"GPT-5.6"}`; got != want {
		t.Fatalf("upstream body = %q, want %q", got, want)
	}
	if got := executor.request.Header.Get("Authorization"); got != "Bearer codex-token" {
		t.Fatalf("Authorization = %q", got)
	}
	if got := executor.request.Header.Get("Chatgpt-Account-Id"); got != "account-123" {
		t.Fatalf("Chatgpt-Account-Id = %q", got)
	}
	if got := executor.request.Header.Get("Session_id"); got != "session-123" {
		t.Fatalf("Session_id = %q", got)
	}
	if got := rr.Header().Get("Content-Type"); got != "application/json" {
		t.Fatalf("response Content-Type = %q", got)
	}
	traceID := rr.Header().Get(internallogging.CPATraceIDHeader)
	parts := strings.SplitN(traceID, "-", 3)
	if len(parts) != 3 || parts[1] != credential.Index || parts[2] == "" {
		t.Fatalf("trace ID = %q, want timestamp-%s-requestID", traceID, credential.Index)
	}
	if _, errParseUUID := uuid.Parse(parts[2]); errParseUUID != nil {
		t.Fatalf("trace requestID = %q: %v", parts[2], errParseUUID)
	}
	if _, errParse := time.Parse("20060102150405", parts[0]); errParse != nil {
		t.Fatalf("trace timestamp = %q: %v", parts[0], errParse)
	}
}

func TestCodexAlphaSearchUsesPluginProviderTargetModel(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	router := &codexSearchModelRouter{
		response: pluginapi.ModelRouteResponse{
			Handled:     true,
			TargetKind:  pluginapi.ModelRouteTargetProvider,
			Target:      "codex",
			TargetModel: "team-b/gpt-5.6-sol",
		},
		handled: true,
	}
	server.handlers.SetModelRouterHost(router)

	for _, credential := range []*auth.Auth{
		{
			ID:       "codex-team-a",
			Provider: "codex",
			Prefix:   "team-a",
			Status:   auth.StatusActive,
			Metadata: map[string]any{"access_token": "token-a"},
		},
		{
			ID:       "codex-team-b",
			Provider: "codex",
			Prefix:   "team-b",
			Status:   auth.StatusActive,
			Metadata: map[string]any{"access_token": "token-b"},
		},
	} {
		if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
			t.Fatalf("register Codex auth %s: %v", credential.ID, errRegister)
		}
		registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: credential.Prefix + "/gpt-5.6-sol"}})
		t.Cleanup(func() {
			registry.GetGlobalRegistry().UnregisterClient(credential.ID)
		})
	}

	payload := `{"id":"session-123","model":"gpt-5.6-sol","commands":{"search_query":[{"q":"golang"}]}}`
	paths := []string{"/v1/alpha/search?key=test-key", "/backend-api/codex/alpha/search?key=test-key"}
	for _, path := range paths {
		req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(payload))
		req.Header.Set("Authorization", "Bearer test-key")
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)

		if rr.Code != http.StatusOK {
			t.Fatalf("%s status = %d, want %d; body=%s", path, rr.Code, http.StatusOK, rr.Body.String())
		}
	}

	if got, want := executor.authIDs, []string{"codex-team-b", "codex-team-b"}; len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
		t.Fatalf("selected auth IDs = %v, want %v", got, want)
	}
	if got := string(executor.body); got != payload {
		t.Fatalf("upstream body = %q, want original unprefixed body %q", got, payload)
	}
	if got, want := len(router.requests), 2; got != want {
		t.Fatalf("model router requests = %d, want %d", got, want)
	}
	for index, routeReq := range router.requests {
		if routeReq.SourceFormat != "codex-alpha-search" {
			t.Fatalf("model router source format = %q", routeReq.SourceFormat)
		}
		if routeReq.RequestedModel != "gpt-5.6-sol" {
			t.Fatalf("model router requested model = %q", routeReq.RequestedModel)
		}
		if got := routeReq.Headers.Get("Authorization"); got != "Bearer test-key" {
			t.Fatalf("model router Authorization = %q", got)
		}
		if got := routeReq.Query.Get("key"); got != "test-key" {
			t.Fatalf("model router query key = %q", got)
		}
		if got, want := routeReq.Metadata[coreexecutor.RequestPathMetadataKey], strings.SplitN(paths[index], "?", 2)[0]; got != want {
			t.Fatalf("model router request path = %#v, want %q", got, want)
		}
		if got := string(routeReq.Body); got != payload {
			t.Fatalf("model router body = %q, want %q", got, payload)
		}
	}
}

func TestCodexAlphaSearchFallsBackWhenPluginDoesNotHandleRoute(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-auth",
		Provider: "codex",
		Status:   auth.StatusActive,
		Metadata: map[string]any{"access_token": "codex-token"},
	}
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
		t.Fatalf("register Codex auth: %v", errRegister)
	}
	registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: "gpt-5.6-sol"}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(credential.ID)
	})
	router := &codexSearchModelRouter{}
	server.handlers.SetModelRouterHost(router)

	payload := `{"model":"gpt-5.6-sol"}`
	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(payload))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	if got := executor.authIDs; len(got) != 1 || got[0] != credential.ID {
		t.Fatalf("selected auth IDs = %v, want [%s]", got, credential.ID)
	}
	if got := string(executor.body); got != payload {
		t.Fatalf("upstream body = %q, want %q", got, payload)
	}
	if got := len(router.requests); got != 1 {
		t.Fatalf("model router requests = %d, want 1", got)
	}
}

func TestCodexAlphaSearchRejectsUnsupportedPluginRouteTarget(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-auth",
		Provider: "codex",
		Status:   auth.StatusActive,
		Metadata: map[string]any{"access_token": "codex-token"},
	}
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
		t.Fatalf("register Codex auth: %v", errRegister)
	}
	registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: "gpt-5.6-sol"}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(credential.ID)
	})
	server.handlers.SetModelRouterHost(&codexSearchModelRouter{
		response: pluginapi.ModelRouteResponse{
			Handled:    true,
			TargetKind: pluginapi.ModelRouteTargetSelf,
			Target:     "user-routing",
		},
		handled: true,
	})

	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"model":"gpt-5.6-sol"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusServiceUnavailable {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusServiceUnavailable, rr.Body.String())
	}
	if executor.request != nil {
		t.Fatal("unsupported plugin route sent an upstream request")
	}
}

func TestCodexAlphaSearchSanitizesResponsesOnlyFields(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-auth",
		Provider: "codex",
		Status:   auth.StatusActive,
		Metadata: map[string]any{"access_token": "codex-token"},
	}
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
		t.Fatalf("register Codex auth: %v", errRegister)
	}
	registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: "gpt-5.6-sol"}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(credential.ID)
	})

	payload := `{"id":"session-123","model":"gpt-5.6-sol","commands":{"search_query":[{"q":"golang channels"}]},"prompt_cache_key":"cache-123","prompt_cache_retention":"24h"}`
	for _, path := range []string{"/v1/alpha/search", "/backend-api/codex/alpha/search"} {
		t.Run(path, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(payload))
			req.Header.Set("Authorization", "Bearer test-key")
			req.Header.Set("Content-Type", "application/json")
			rr := httptest.NewRecorder()
			server.engine.ServeHTTP(rr, req)

			if rr.Code != http.StatusOK {
				t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
			}
			var upstreamBody map[string]json.RawMessage
			if errUnmarshal := json.Unmarshal(executor.body, &upstreamBody); errUnmarshal != nil {
				t.Fatalf("unmarshal upstream body: %v; body=%s", errUnmarshal, executor.body)
			}
			if _, exists := upstreamBody["prompt_cache_key"]; exists {
				t.Fatalf("upstream body contains prompt_cache_key: %s", executor.body)
			}
			if _, exists := upstreamBody["prompt_cache_retention"]; exists {
				t.Fatalf("upstream body contains prompt_cache_retention: %s", executor.body)
			}
			for _, field := range []string{"id", "model", "commands"} {
				if _, exists := upstreamBody[field]; !exists {
					t.Fatalf("upstream body missing %s: %s", field, executor.body)
				}
			}
		})
	}
}

func TestCodexAlphaSearchCredentialPolicy(t *testing.T) {
	newServer := func(t *testing.T, credentials ...*auth.Auth) (*Server, *codexSearchCaptureExecutor) {
		t.Helper()
		server := newTestServer(t)
		server.handlers.AuthManager.SetSelector(&codexSearchAPIKeyFirstSelector{})
		executor := &codexSearchCaptureExecutor{}
		server.handlers.AuthManager.RegisterExecutor(executor)
		for _, credential := range credentials {
			if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
				t.Fatalf("register Codex auth %s: %v", credential.ID, errRegister)
			}
		}
		return server, executor
	}
	apiKeyCredential := func() *auth.Auth {
		return &auth.Auth{
			ID:         "codex-api-key",
			Provider:   "codex",
			Status:     auth.StatusActive,
			Attributes: map[string]string{auth.AttributeAPIKey: "codex-key"},
		}
	}
	oauthCredential := func() *auth.Auth {
		return &auth.Auth{
			ID:       "codex-oauth",
			Provider: "codex",
			Status:   auth.StatusActive,
			Metadata: map[string]any{"access_token": "codex-token"},
		}
	}

	t.Run("mixed credentials", func(t *testing.T) {
		server, executor := newServer(t, apiKeyCredential(), oauthCredential())
		req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"query":"GPT-5.6"}`))
		req.Header.Set("Authorization", "Bearer test-key")
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)

		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
		if got := executor.authIDs; len(got) != 1 || got[0] != "codex-oauth" {
			t.Fatalf("selected auth IDs = %v, want [codex-oauth]", got)
		}
	})

	t.Run("ordinary API key only", func(t *testing.T) {
		server, executor := newServer(t, apiKeyCredential())
		req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"query":"GPT-5.6"}`))
		req.Header.Set("Authorization", "Bearer test-key")
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)

		if rr.Code != http.StatusServiceUnavailable {
			t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusServiceUnavailable, rr.Body.String())
		}
		if len(executor.authIDs) != 0 {
			t.Fatalf("selected auth IDs = %v, want none", executor.authIDs)
		}
	})
}

func TestCodexAlphaSearchOptInAPIKeyUsesConfiguredEndpoint(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-alpha-api-key",
		Provider: "codex",
		Status:   auth.StatusActive,
		Attributes: map[string]string{
			auth.AttributeAPIKey:           "codex-alpha-key",
			auth.AttributeCodexAlphaSearch: "true",
			"base_url":                     "https://codex.example.com/v1/",
		},
	}
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
		t.Fatalf("register Codex API key: %v", errRegister)
	}

	payload := `{"query":"golang","prompt_cache_key":"cache","prompt_cache_retention":"24h"}`
	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(payload))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	if executor.request == nil {
		t.Fatal("Codex executor did not receive a request")
	}
	if got, want := executor.request.URL.String(), "https://codex.example.com/v1/alpha/search"; got != want {
		t.Fatalf("upstream URL = %q, want %q", got, want)
	}
	if got := executor.request.Header.Get("Authorization"); got != "Bearer codex-alpha-key" {
		t.Fatalf("Authorization = %q, want API key bearer", got)
	}
	var upstreamBody map[string]json.RawMessage
	if errUnmarshal := json.Unmarshal(executor.body, &upstreamBody); errUnmarshal != nil {
		t.Fatalf("unmarshal upstream body: %v", errUnmarshal)
	}
	for _, field := range []string{"prompt_cache_key", "prompt_cache_retention"} {
		if _, exists := upstreamBody[field]; exists {
			t.Fatalf("upstream body contains %s: %s", field, executor.body)
		}
	}
}

func TestCodexAlphaSearchOptInAPIKeyStripsCredentialPrefix(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-alpha-api-key-prefix",
		Provider: "codex",
		Prefix:   "vendor",
		Status:   auth.StatusActive,
		Attributes: map[string]string{
			auth.AttributeAPIKey:           "codex-alpha-key",
			auth.AttributeCodexAlphaSearch: "true",
			"base_url":                     "https://codex.example.com/v1",
		},
	}
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
		t.Fatalf("register Codex API key: %v", errRegister)
	}
	registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: "vendor/gpt-5.6-sol"}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(credential.ID)
	})

	payload := `{"id":"00000000-0000-4000-8000-000000000003","model":"vendor/gpt-5.6-sol","commands":{"search_query":[{"q":"Go programming language official website"}]}}`
	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(payload))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	if executor.request == nil {
		t.Fatal("Codex executor did not receive a request")
	}
	var upstreamBody map[string]json.RawMessage
	if errUnmarshal := json.Unmarshal(executor.body, &upstreamBody); errUnmarshal != nil {
		t.Fatalf("unmarshal upstream body: %v", errUnmarshal)
	}
	var upstreamModel string
	if errUnmarshal := json.Unmarshal(upstreamBody["model"], &upstreamModel); errUnmarshal != nil {
		t.Fatalf("unmarshal upstream model: %v", errUnmarshal)
	}
	if upstreamModel != "gpt-5.6-sol" {
		t.Fatalf("upstream model = %q, want gpt-5.6-sol", upstreamModel)
	}
}

func TestCodexAlphaSearchOptInAPIKeyResolvesModelAlias(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	server.handlers.AuthManager.SetConfig(&proxyconfig.Config{
		CodexKey: []proxyconfig.CodexKey{{
			APIKey:      "codex-alpha-key",
			Prefix:      "vendor",
			BaseURL:     "https://codex.example.com/v1",
			AlphaSearch: true,
			Models: []proxyconfig.CodexModel{{
				Name:  "gpt-5.6-sol",
				Alias: "sol-alias",
			}},
		}},
	})
	credential := &auth.Auth{
		ID:       "codex-alpha-api-key-alias",
		Provider: "codex",
		Prefix:   "vendor",
		Status:   auth.StatusActive,
		Attributes: map[string]string{
			auth.AttributeAPIKey:           "codex-alpha-key",
			auth.AttributeCodexAlphaSearch: "true",
			"base_url":                     "https://codex.example.com/v1",
		},
	}
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
		t.Fatalf("register Codex API key: %v", errRegister)
	}
	registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: "vendor/sol-alias"}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(credential.ID)
	})

	payload := `{"model":"vendor/sol-alias","commands":{"search_query":[{"q":"golang"}]}}`
	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(payload))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	if executor.request == nil {
		t.Fatal("Codex executor did not receive a request")
	}
	var upstreamBody map[string]json.RawMessage
	if errUnmarshal := json.Unmarshal(executor.body, &upstreamBody); errUnmarshal != nil {
		t.Fatalf("unmarshal upstream body: %v", errUnmarshal)
	}
	var upstreamModel string
	if errUnmarshal := json.Unmarshal(upstreamBody["model"], &upstreamModel); errUnmarshal != nil {
		t.Fatalf("unmarshal upstream model: %v", errUnmarshal)
	}
	if upstreamModel != "gpt-5.6-sol" {
		t.Fatalf("upstream model = %q, want gpt-5.6-sol", upstreamModel)
	}
}

func TestRewriteCodexAlphaSearchModel(t *testing.T) {
	original := []byte(`{"id":"search-1","model":"vendor/gpt-5.6-sol","commands":{"search_query":[{"q":"golang"}]}}`)
	rewritten := rewriteCodexAlphaSearchModel(original, "gpt-5.6-sol")
	var payload map[string]json.RawMessage
	if errUnmarshal := json.Unmarshal(rewritten, &payload); errUnmarshal != nil {
		t.Fatalf("unmarshal rewritten body: %v", errUnmarshal)
	}
	var model string
	if errUnmarshal := json.Unmarshal(payload["model"], &model); errUnmarshal != nil {
		t.Fatalf("unmarshal rewritten model: %v", errUnmarshal)
	}
	if model != "gpt-5.6-sol" {
		t.Fatalf("model = %q, want gpt-5.6-sol", model)
	}
	if _, exists := payload["commands"]; !exists {
		t.Fatal("commands field was dropped")
	}
	if string(rewriteCodexAlphaSearchModel([]byte(`{"query":"x"}`), "gpt-5.6-sol")) != `{"query":"x"}` {
		t.Fatal("body without model should remain unchanged")
	}
}

func TestCodexAlphaSearchOptInAPIKeyWithoutBaseURLFailsClosed(t *testing.T) {
	server := newTestServer(t)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), &auth.Auth{
		ID:       "codex-alpha-api-key",
		Provider: "codex",
		Status:   auth.StatusActive,
		Attributes: map[string]string{
			auth.AttributeAPIKey:           "codex-alpha-key",
			auth.AttributeCodexAlphaSearch: "true",
		},
	}); errRegister != nil {
		t.Fatalf("register Codex API key: %v", errRegister)
	}

	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"query":"GPT-5.6"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusServiceUnavailable {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusServiceUnavailable, rr.Body.String())
	}
	if executor.request != nil {
		t.Fatal("request was sent without an API key base URL")
	}
}

func TestCodexAlphaSearchPassesGinContextToAuthSelection(t *testing.T) {
	server := newTestServer(t)
	selector := &codexSearchGinContextSelector{}
	server.handlers.AuthManager.SetSelector(selector)
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-auth",
		Provider: "codex",
		Status:   auth.StatusActive,
		Metadata: map[string]any{"access_token": "codex-token"},
	}
	if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
		t.Fatalf("register Codex auth: %v", errRegister)
	}

	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search?key=home-query-key", strings.NewReader(`{"query":"GPT-5.6"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	if selector.ginContext == nil {
		t.Fatal("auth selection did not receive the Gin context required by Home scheduling")
	}
	if got := selector.ginContext.Query("key"); got != "home-query-key" {
		t.Fatalf("Gin query key = %q, want %q", got, "home-query-key")
	}
}

func TestCodexAlphaSearchUsesRequestIDForSessionAffinity(t *testing.T) {
	server := newTestServer(t)
	server.handlers.AuthManager.SetSelector(auth.NewSessionAffinitySelector(&auth.RoundRobinSelector{}))
	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	for _, id := range []string{"codex-auth-a", "codex-auth-b"} {
		registry.GetGlobalRegistry().RegisterClient(id, "codex", []*registry.ModelInfo{{ID: "gpt-5.6-luna"}})
		t.Cleanup(func() {
			registry.GetGlobalRegistry().UnregisterClient(id)
		})
		credential := &auth.Auth{
			ID:       id,
			Provider: "codex",
			Status:   auth.StatusActive,
			Metadata: map[string]any{"access_token": id},
		}
		if _, errRegister := server.handlers.AuthManager.Register(context.Background(), credential); errRegister != nil {
			t.Fatalf("register Codex auth: %v", errRegister)
		}
	}

	for _, payload := range []string{
		`{"id":"session-a","model":"gpt-5.6-luna"}`,
		`{"id":"session-b","model":"gpt-5.6-luna"}`,
		`{"id":"session-a","model":"gpt-5.6-luna"}`,
	} {
		req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(payload))
		req.Header.Set("Authorization", "Bearer test-key")
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
	}

	if got, want := len(executor.authIDs), 3; got != want {
		t.Fatalf("selected auth count = %d, want %d", got, want)
	}
	if executor.authIDs[0] == executor.authIDs[1] {
		t.Fatalf("different sessions selected the same auth %q", executor.authIDs[0])
	}
	if got, want := executor.authIDs[2], executor.authIDs[0]; got != want {
		t.Fatalf("session-affinity auth = %q, want %q", got, want)
	}
}

func TestCodexAlphaSearchRecordsRequestLog(t *testing.T) {
	server := newTestServer(t)
	server.cfg.RequestLog = true

	executor := &codexSearchCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(executor)
	credential := &auth.Auth{
		ID:       "codex-auth",
		Provider: "codex",
		Status:   auth.StatusActive,
		Metadata: map[string]any{"access_token": "codex-token", "account_id": "account-123"},
	}
	if _, err := server.handlers.AuthManager.Register(context.Background(), credential); err != nil {
		t.Fatalf("register Codex auth: %v", err)
	}

	rr := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(rr)
	req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"query":"GPT-5.6"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	req.Header.Set("Content-Type", "application/json")
	c.Request = req

	server.codexAlphaSearch(c)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d; body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	rawAPIRequest, okRequest := c.Get("API_REQUEST")
	if !okRequest {
		t.Fatal("API_REQUEST was not captured")
	}
	apiRequest, _ := rawAPIRequest.([]byte)
	if !strings.Contains(string(apiRequest), "=== API REQUEST 1 ===") {
		t.Fatalf("API_REQUEST missing request header section: %q", apiRequest)
	}
	if !strings.Contains(string(apiRequest), "https://chatgpt.com/backend-api/codex/alpha/search") {
		t.Fatalf("API_REQUEST missing upstream URL: %q", apiRequest)
	}
	if !strings.Contains(string(apiRequest), `{"query":"GPT-5.6"}`) {
		t.Fatalf("API_REQUEST missing body: %q", apiRequest)
	}
	rawAPIResponse, okResponse := c.Get("API_RESPONSE")
	if !okResponse {
		t.Fatal("API_RESPONSE was not captured")
	}
	apiResponse, _ := rawAPIResponse.([]byte)
	if !strings.Contains(string(apiResponse), "=== API RESPONSE 1 ===") {
		t.Fatalf("API_RESPONSE missing response header section: %q", apiResponse)
	}
	if !strings.Contains(string(apiResponse), `{"results":[{"url":"https://example.com"}]}`) {
		t.Fatalf("API_RESPONSE missing body: %q", apiResponse)
	}
}

func TestManagementResponseExposesPluginSupportHeaderForCORS(t *testing.T) {
	t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")

	server := newTestServer(t)
	req := httptest.NewRequest(http.MethodGet, "/v0/management/config", nil)
	req.Header.Set("Origin", "http://127.0.0.1:5173")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusUnauthorized {
		t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusUnauthorized, rr.Body.String())
	}
	if got := rr.Header().Get("X-CPA-SUPPORT-PLUGIN"); got != pluginhost.SupportPluginHeaderValue() {
		t.Fatalf("X-CPA-SUPPORT-PLUGIN = %q, want %q", got, pluginhost.SupportPluginHeaderValue())
	}

	exposedHeaders := make(map[string]struct{})
	for _, headerName := range strings.Split(rr.Header().Get("Access-Control-Expose-Headers"), ",") {
		headerName = strings.ToLower(strings.TrimSpace(headerName))
		if headerName != "" {
			exposedHeaders[headerName] = struct{}{}
		}
	}
	for _, headerName := range corsExposedResponseHeaders {
		if _, ok := exposedHeaders[strings.ToLower(headerName)]; !ok {
			t.Fatalf("Access-Control-Expose-Headers missing %s: %q", headerName, rr.Header().Get("Access-Control-Expose-Headers"))
		}
	}
}

func TestOAuthCallbackRouteSkipsManagementKeyMiddleware(t *testing.T) {
	t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")

	server := newTestServer(t)
	state := "server-plugin-oauth-state"
	if errRegister := managementHandlers.RegisterPluginOAuthSession(state, "gemini-cli", nil); errRegister != nil {
		t.Fatalf("register plugin oauth session: %v", errRegister)
	}
	defer managementHandlers.CompleteOAuthSession(state)

	req := httptest.NewRequest(http.MethodGet, "/v0/management/oauth-callback?state="+state+"&code=test-code", nil)
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}

	callbackPath := filepath.Join(server.cfg.AuthDir, ".oauth-gemini-cli-"+state+".oauth")
	if _, errRead := os.ReadFile(callbackPath); errRead != nil {
		t.Fatalf("expected callback file to be written without management key: %v", errRead)
	}
}

func TestNewServerWithPluginHostInjectsHandlerInterceptors(t *testing.T) {
	host := pluginhost.New()
	server := newTestServerWithOptions(t, WithPluginHost(host))

	if server.handlers == nil {
		t.Fatal("server handlers = nil")
	}
	got, ok := server.handlers.PluginHost.(*pluginhost.Host)
	if !ok || got != host {
		t.Fatalf("handler plugin host = %#v, want configured host", server.handlers.PluginHost)
	}
}

func TestNewServerWithoutPluginHostLeavesHandlerInterceptorsDisabled(t *testing.T) {
	server := newTestServer(t)

	if server.handlers == nil {
		t.Fatal("server handlers = nil")
	}
	if server.handlers.PluginHost != nil {
		t.Fatalf("handler plugin host = %#v, want nil", server.handlers.PluginHost)
	}
}

func TestManagementUsageRequiresManagementAuthAndPopsArray(t *testing.T) {
	t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")

	prevQueueEnabled := redisqueue.Enabled()
	redisqueue.SetEnabled(false)
	t.Cleanup(func() {
		redisqueue.SetEnabled(false)
		redisqueue.SetEnabled(prevQueueEnabled)
	})

	server := newTestServer(t)

	redisqueue.Enqueue([]byte(`{"id":1}`))
	redisqueue.Enqueue([]byte(`{"id":2}`))

	missingKeyReq := httptest.NewRequest(http.MethodGet, "/v0/management/usage-queue?count=2", nil)
	missingKeyRR := httptest.NewRecorder()
	server.engine.ServeHTTP(missingKeyRR, missingKeyReq)
	if missingKeyRR.Code != http.StatusUnauthorized {
		t.Fatalf("missing key status = %d, want %d body=%s", missingKeyRR.Code, http.StatusUnauthorized, missingKeyRR.Body.String())
	}

	legacyReq := httptest.NewRequest(http.MethodGet, "/v0/management/usage?count=2", nil)
	legacyReq.Header.Set("Authorization", "Bearer test-management-key")
	legacyRR := httptest.NewRecorder()
	server.engine.ServeHTTP(legacyRR, legacyReq)
	if legacyRR.Code != http.StatusNotFound {
		t.Fatalf("legacy usage status = %d, want %d body=%s", legacyRR.Code, http.StatusNotFound, legacyRR.Body.String())
	}

	authReq := httptest.NewRequest(http.MethodGet, "/v0/management/usage-queue?count=2", nil)
	authReq.Header.Set("Authorization", "Bearer test-management-key")
	authRR := httptest.NewRecorder()
	server.engine.ServeHTTP(authRR, authReq)
	if authRR.Code != http.StatusOK {
		t.Fatalf("authenticated status = %d, want %d body=%s", authRR.Code, http.StatusOK, authRR.Body.String())
	}

	var payload []json.RawMessage
	if errUnmarshal := json.Unmarshal(authRR.Body.Bytes(), &payload); errUnmarshal != nil {
		t.Fatalf("unmarshal response: %v body=%s", errUnmarshal, authRR.Body.String())
	}
	if len(payload) != 2 {
		t.Fatalf("response records = %d, want 2", len(payload))
	}
	for i, raw := range payload {
		var record struct {
			ID int `json:"id"`
		}
		if errUnmarshal := json.Unmarshal(raw, &record); errUnmarshal != nil {
			t.Fatalf("unmarshal record %d: %v", i, errUnmarshal)
		}
		if record.ID != i+1 {
			t.Fatalf("record %d id = %d, want %d", i, record.ID, i+1)
		}
	}

	if remaining := redisqueue.PopOldest(1); len(remaining) != 0 {
		t.Fatalf("remaining queue = %q, want empty", remaining)
	}
}

func TestManagementPluginsRouteRegistered(t *testing.T) {
	t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")

	server := newTestServer(t)
	enabled := true
	server.cfg.Plugins.Configs = map[string]proxyconfig.PluginInstanceConfig{
		"sample": {Enabled: &enabled, Priority: 4},
	}
	server.mgmt.SetConfig(server.cfg)
	if errWrite := os.WriteFile(server.configFilePath, []byte("{}\n"), 0o600); errWrite != nil {
		t.Fatalf("failed to write config file: %v", errWrite)
	}

	req := httptest.NewRequest(http.MethodGet, "/v0/management/plugins", nil)
	req.Header.Set("Authorization", "Bearer test-management-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}

	var payload struct {
		PluginsEnabled bool  `json:"plugins_enabled"`
		Plugins        []any `json:"plugins"`
	}
	if errUnmarshal := json.Unmarshal(rr.Body.Bytes(), &payload); errUnmarshal != nil {
		t.Fatalf("unmarshal response: %v body=%s", errUnmarshal, rr.Body.String())
	}
	if payload.Plugins == nil {
		t.Fatalf("plugins field = nil, want array; body=%s", rr.Body.String())
	}

	req = httptest.NewRequest(http.MethodGet, "/v0/management/plugins/sample/config", nil)
	req.Header.Set("Authorization", "Bearer test-management-key")
	rr = httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)
	if rr.Code != http.StatusOK {
		t.Fatalf("config status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
	var configPayload struct {
		Enabled  bool `json:"enabled"`
		Priority int  `json:"priority"`
	}
	if errUnmarshal := json.Unmarshal(rr.Body.Bytes(), &configPayload); errUnmarshal != nil {
		t.Fatalf("unmarshal config response: %v body=%s", errUnmarshal, rr.Body.String())
	}
	if !configPayload.Enabled || configPayload.Priority != 4 {
		t.Fatalf("plugin config = %#v, want enabled true priority 4", configPayload)
	}

	req = httptest.NewRequest(http.MethodDelete, "/v0/management/plugins/sample", nil)
	req.Header.Set("Authorization", "Bearer test-management-key")
	rr = httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)
	if rr.Code != http.StatusOK {
		t.Fatalf("delete status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}
}

func TestVideosRoutesKeepXAINativeAndExposeOpenAIPrefix(t *testing.T) {
	server := newTestServer(t)

	nativeReq := httptest.NewRequest(http.MethodPost, "/v1/videos", strings.NewReader(`{"model":"sora-2","prompt":"make a video"}`))
	nativeReq.Header.Set("Authorization", "Bearer test-key")
	nativeReq.Header.Set("Content-Type", "application/json")
	nativeRR := httptest.NewRecorder()
	server.engine.ServeHTTP(nativeRR, nativeReq)
	if nativeRR.Code != http.StatusBadRequest {
		t.Fatalf("native status = %d, want %d body=%s", nativeRR.Code, http.StatusBadRequest, nativeRR.Body.String())
	}
	if !strings.Contains(nativeRR.Body.String(), "/v1/videos/generations") {
		t.Fatalf("expected /v1/videos to keep xAI native validation, body=%s", nativeRR.Body.String())
	}

	openAIReq := httptest.NewRequest(http.MethodPost, "/openai/v1/videos", strings.NewReader(`{"model":`))
	openAIReq.Header.Set("Authorization", "Bearer test-key")
	openAIReq.Header.Set("Content-Type", "application/json")
	openAIRR := httptest.NewRecorder()
	server.engine.ServeHTTP(openAIRR, openAIReq)
	if openAIRR.Code != http.StatusBadRequest {
		t.Fatalf("openai create status = %d, want %d body=%s", openAIRR.Code, http.StatusBadRequest, openAIRR.Body.String())
	}
	if !strings.Contains(openAIRR.Body.String(), "body must be valid JSON") {
		t.Fatalf("expected /openai/v1/videos create handler, body=%s", openAIRR.Body.String())
	}

	contentReq := httptest.NewRequest(http.MethodGet, "/openai/v1/videos/video_123/content?variant=thumbnail", nil)
	contentReq.Header.Set("Authorization", "Bearer test-key")
	contentRR := httptest.NewRecorder()
	server.engine.ServeHTTP(contentRR, contentReq)
	if contentRR.Code != http.StatusBadRequest {
		t.Fatalf("content status = %d, want %d body=%s", contentRR.Code, http.StatusBadRequest, contentRR.Body.String())
	}
	if !strings.Contains(contentRR.Body.String(), "variant") {
		t.Fatalf("expected /openai/v1/videos content handler, body=%s", contentRR.Body.String())
	}
}

func TestHomeEnabledHidesManagementEndpointsAndControlPanel(t *testing.T) {
	t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")

	server := newTestServer(t)
	server.cfg.Home.Enabled = true

	t.Run("management endpoints return 404", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/v0/management/config", nil)
		req.Header.Set("Authorization", "Bearer test-management-key")
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusNotFound {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusNotFound, rr.Body.String())
		}
	})

	t.Run("management control panel returns 404", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/management.html", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusNotFound {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusNotFound, rr.Body.String())
		}
	})
}

func TestExampleAPIKeySafeModeShowsWarningAndKeepsManagement(t *testing.T) {
	t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")
	staticDir := t.TempDir()
	t.Setenv("MANAGEMENT_STATIC_PATH", staticDir)
	if err := os.WriteFile(filepath.Join(staticDir, "management.html"), []byte("<html>management app</html>"), 0o600); err != nil {
		t.Fatalf("failed to write management asset: %v", err)
	}

	server := newTestServerWithOptions(t, WithExampleAPIKeySafeMode())
	cfg := *server.cfg
	cfg.APIKeys = []string{"your-api-key-1"}
	server.UpdateClients(&cfg)

	t.Run("root warning page includes management link", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
		body := rr.Body.String()
		for _, want := range []string{"Example API key detected", "Open Management", `href="/management.html?safe-mode=configure"`} {
			if !strings.Contains(body, want) {
				t.Fatalf("warning page missing %q: %s", want, body)
			}
		}
	})

	t.Run("management html defaults to warning page", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/management.html", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
		if !strings.Contains(rr.Body.String(), "Example API key detected") {
			t.Fatalf("management.html did not show warning page: %s", rr.Body.String())
		}
	})

	t.Run("management html head stops at warning page", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodHead, "/management.html", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
		if rr.Body.Len() != 0 {
			t.Fatalf("HEAD body length = %d, want 0", rr.Body.Len())
		}
		if got := rr.Header().Get("Cache-Control"); got != "no-store" {
			t.Fatalf("Cache-Control = %q, want no-store", got)
		}
	})

	t.Run("management button query opens control panel", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/management.html?safe-mode=configure", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
		if !strings.Contains(rr.Body.String(), "management app") {
			t.Fatalf("management panel body missing: %s", rr.Body.String())
		}
	})

	t.Run("proxy endpoints are blocked", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusForbidden {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusForbidden, rr.Body.String())
		}
		if got := rr.Header().Get("X-CPA-SAFE-MODE"); got != "example-api-key" {
			t.Fatalf("X-CPA-SAFE-MODE = %q, want example-api-key", got)
		}
		if !strings.Contains(rr.Body.String(), "unsafe_example_api_key") {
			t.Fatalf("body missing safe-mode error: %s", rr.Body.String())
		}
		if strings.Contains(rr.Body.String(), "management_url") {
			t.Fatalf("body should not include management_url field: %s", rr.Body.String())
		}
		if !strings.Contains(rr.Body.String(), "/management.html?safe-mode=configure") {
			t.Fatalf("body missing management link in message: %s", rr.Body.String())
		}
		if got := rr.Header().Get(internallogging.CPATraceIDHeader); got != "" {
			t.Fatalf("trace ID = %q, want empty before auth selection", got)
		}
	})

	t.Run("management endpoints still work", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/v0/management/config", nil)
		req.Header.Set("Authorization", "Bearer test-management-key")
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}
		if got := rr.Header().Get(internallogging.CPATraceIDHeader); got != "" {
			t.Fatalf("management trace ID = %q, want empty", got)
		}
	})

	t.Run("safe mode clears after key update", func(t *testing.T) {
		nextCfg := cfg
		nextCfg.APIKeys = []string{"real-key"}
		server.UpdateClients(&nextCfg)

		req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
		req.Header.Set("Authorization", "Bearer real-key")
		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code == http.StatusForbidden && strings.Contains(rr.Body.String(), "unsafe_example_api_key") {
			t.Fatalf("proxy endpoint still blocked after key update: %s", rr.Body.String())
		}
	})
}

func TestModelsDispatchByAnthropicVersionHeader(t *testing.T) {
	modelRegistry := registry.GetGlobalRegistry()
	clientID := "test-anthropic-version-dispatch"
	modelRegistry.RegisterClient(clientID, "claude", []*registry.ModelInfo{
		{
			ID:                  "claude-sonnet-4-6",
			Object:              "model",
			OwnedBy:             "anthropic",
			Type:                "claude",
			DisplayName:         "Claude 4.6 Sonnet",
			ContextLength:       200000,
			MaxCompletionTokens: 64000,
		},
		{
			ID:      "gpt-4o",
			Object:  "model",
			OwnedBy: "openai",
			Type:    "openai",
		},
	})
	t.Cleanup(func() {
		modelRegistry.UnregisterClient(clientID)
	})

	server := newTestServer(t)

	// Anthropic API request (Anthropic-Version header, non-claude-cli User-Agent) -> Claude format.
	t.Run("anthropic version header routes to claude format", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
		req.Header.Set("Authorization", "Bearer test-key")
		req.Header.Set("User-Agent", "Zed/1.0")
		req.Header.Set("Anthropic-Version", "2023-06-01")

		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}

		var resp struct {
			Object  string           `json:"object"`
			HasMore *bool            `json:"has_more"`
			Data    []map[string]any `json:"data"`
		}
		if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
			t.Fatalf("failed to parse response JSON: %v; body=%s", err, rr.Body.String())
		}
		if resp.Object == "list" {
			t.Fatalf("expected Claude format (no object=list), got OpenAI format: %s", rr.Body.String())
		}
		if resp.HasMore == nil {
			t.Fatalf("expected Claude envelope with has_more, got %s", rr.Body.String())
		}

		var claudeModel map[string]any
		var rewrittenModel map[string]any
		for _, m := range resp.Data {
			id, _ := m["id"].(string)
			switch id {
			case "claude-sonnet-4-6":
				claudeModel = m
			case "claude-fable-5-dd-o4-tpg":
				rewrittenModel = m
			case "gpt-4o", "claude-gpt-4o":
				t.Fatalf("expected non-claude model id to be rewritten as claude-fable-5-dd-<reversed>, got %q", id)
			}
		}
		if claudeModel == nil {
			t.Fatalf("expected claude-sonnet-4-6 in response, got %s", rr.Body.String())
		}
		if rewrittenModel == nil {
			t.Fatalf("expected claude-fable-5-dd-o4-tpg in response, got %s", rr.Body.String())
		}
		for _, field := range []string{"max_input_tokens", "max_tokens", "display_name"} {
			if _, ok := claudeModel[field]; !ok {
				t.Fatalf("expected Claude model to include %q, got %v", field, claudeModel)
			}
		}
	})

	// Plain request (no Anthropic-Version, non-claude-cli User-Agent) -> OpenAI format, unaffected.
	t.Run("plain request stays on openai format", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
		req.Header.Set("Authorization", "Bearer test-key")
		req.Header.Set("User-Agent", "Mozilla/5.0")

		rr := httptest.NewRecorder()
		server.engine.ServeHTTP(rr, req)
		if rr.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
		}

		var resp struct {
			Object string           `json:"object"`
			Data   []map[string]any `json:"data"`
		}
		if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
			t.Fatalf("failed to parse response JSON: %v; body=%s", err, rr.Body.String())
		}
		if resp.Object != "list" {
			t.Fatalf("expected OpenAI format (object=list), got %s", rr.Body.String())
		}
		foundRawGPT := false
		for _, m := range resp.Data {
			if _, ok := m["max_input_tokens"]; ok {
				t.Fatalf("did not expect max_input_tokens in OpenAI format, got %v", m)
			}
			if id, _ := m["id"].(string); id == "gpt-4o" {
				foundRawGPT = true
			}
			if id, _ := m["id"].(string); id == "claude-gpt-4o" || id == "claude-fable-5-dd-o4-tpg" {
				t.Fatalf("did not expect Anthropic id rewrite on OpenAI format models, got %v", m)
			}
		}
		if !foundRawGPT {
			t.Fatalf("expected raw gpt-4o in OpenAI format response, got %s", rr.Body.String())
		}
	})
}

func TestClaudeModelListCloakingConfigHotReload(t *testing.T) {
	modelRegistry := registry.GetGlobalRegistry()
	clientID := "test-claude-model-list-cloaking-hot-reload"
	const modelID = "gpt-model-list-hot-reload"
	modelRegistry.RegisterClient(clientID, "claude", []*registry.ModelInfo{{
		ID: modelID, Object: "model", OwnedBy: "test", Type: "openai",
	}})
	t.Cleanup(func() {
		modelRegistry.UnregisterClient(clientID)
	})

	server := newTestServer(t)
	assertModelID := func(want string) {
		t.Helper()
		req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
		req.Header.Set("Authorization", "Bearer test-key")
		req.Header.Set("Anthropic-Version", "2023-06-01")

		recorder := httptest.NewRecorder()
		server.engine.ServeHTTP(recorder, req)
		if recorder.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d body=%s", recorder.Code, http.StatusOK, recorder.Body.String())
		}

		var response struct {
			Data []struct {
				ID string `json:"id"`
			} `json:"data"`
		}
		if errUnmarshal := json.Unmarshal(recorder.Body.Bytes(), &response); errUnmarshal != nil {
			t.Fatalf("decode response: %v", errUnmarshal)
		}
		for _, model := range response.Data {
			if model.ID == want {
				return
			}
		}
		t.Fatalf("model %q not found in response: %s", want, recorder.Body.String())
	}

	assertModelID(claudemodels.EnsureClaudeModelIDPrefix(modelID))

	updatedCfg := *server.cfg
	updatedCfg.SDKConfig = server.cfg.SDKConfig
	updatedCfg.ClaudeCode.DisableCloakingModelList = true
	server.UpdateClients(&updatedCfg)

	assertModelID(modelID)
}

func TestModelsWithClientVersionReturnsCodexCatalog(t *testing.T) {
	modelRegistry := registry.GetGlobalRegistry()
	clientID := "test-client-version-catalog"
	modelRegistry.RegisterClient(clientID, "codex", []*registry.ModelInfo{
		{
			ID:                  "gpt-5.5",
			Object:              "model",
			Created:             1776902400,
			OwnedBy:             "openai",
			Type:                "openai",
			DisplayName:         "GPT 5.5",
			Description:         "Frontier model for complex coding, research, and real-world work.",
			ContextLength:       272000,
			MaxCompletionTokens: 64000,
			Thinking:            &registry.ThinkingSupport{Levels: []string{"low", "medium", "high", "xhigh"}},
		},
		{
			ID:            "custom-codex-model-test",
			Object:        "model",
			OwnedBy:       "test",
			Type:          "openai",
			DisplayName:   "Custom Codex Model",
			Description:   "Custom model from registry",
			ContextLength: 123456,
			Thinking:      &registry.ThinkingSupport{Levels: []string{"none", "minimal", "low", "medium", "unsupported", "high", "xhigh"}},
		},
		{ID: "grok-imagine-image-quality", Object: "model", OwnedBy: "xai", Type: "openai"},
		{ID: "gpt-image-2", Object: "model", OwnedBy: "openai", Type: "openai"},
		{ID: "gpt-image-2.5-flare", Object: "model", OwnedBy: "openai", Type: "openai"},
		{ID: "gpt-image-2.5-sunburst", Object: "model", OwnedBy: "openai", Type: "openai"},
		{ID: "gpt-image-2.5", Object: "model", OwnedBy: "openai", Type: "openai"},
		{ID: "gpt-image-2.6-flare", Object: "model", OwnedBy: "openai", Type: "openai"},
		{ID: "gpt-image-2.6-sunburst", Object: "model", OwnedBy: "openai", Type: "openai"},
		{ID: "gpt-image-2.6", Object: "model", OwnedBy: "openai", Type: "openai"},
		{ID: "grok-imagine-image", Object: "model", OwnedBy: "xai", Type: "openai"},
		{ID: "grok-imagine-image-2.0", Object: "model", OwnedBy: "xai", Type: "openai"},
		{ID: "grok-imagine-video", Object: "model", OwnedBy: "xai", Type: "openai"},
		{ID: "grok-imagine-video-1.5", Object: "model", OwnedBy: "xai", Type: "openai"},
		{ID: "grok-imagine-video-1.5-preview", Object: "model", OwnedBy: "xai", Type: "openai"},
	})
	t.Cleanup(func() {
		modelRegistry.UnregisterClient(clientID)
	})

	cfg := &proxyconfig.Config{SDKConfig: sdkconfig.SDKConfig{APIKeys: []string{"test-key"}}}
	cfg.Client.Codex.EnableApplyPatch = true
	server := newTestServerWithConfig(t, cfg)
	server.handlers.AuthManager.RegisterExecutor(executor.NewCodexAutoExecutor(&proxyconfig.Config{}))

	req := httptest.NewRequest(http.MethodGet, "/v1/models?client_version", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	req.Header.Set("User-Agent", "claude-cli/1.0")

	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}

	var resp struct {
		Models []map[string]any `json:"models"`
		Object string           `json:"object"`
		Data   []any            `json:"data"`
	}
	if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to parse response JSON: %v; body=%s", err, rr.Body.String())
	}
	if resp.Object != "" || resp.Data != nil {
		t.Fatalf("expected codex catalog format without object/data, got object=%q data=%v", resp.Object, resp.Data)
	}
	if len(resp.Models) == 0 {
		t.Fatal("expected codex catalog models")
	}

	var gpt55 map[string]any
	var custom map[string]any
	for _, model := range resp.Models {
		switch slug, _ := model["slug"].(string); slug {
		case "gpt-5.5":
			gpt55 = model
		case "custom-codex-model-test":
			custom = model
		}
	}
	if gpt55 == nil {
		t.Fatal("expected gpt-5.5 codex catalog entry")
	}
	if got := gpt55["apply_patch_tool_type"]; got != "freeform" {
		t.Fatalf("gpt-5.5 apply_patch_tool_type = %#v, want freeform", got)
	}
	if _, ok := gpt55["minimal_client_version"]; !ok {
		t.Fatal("expected minimal_client_version in codex catalog")
	}
	if got, _ := gpt55["max_tokens"].(float64); got != 64000 {
		t.Fatalf("gpt-5.5 max_tokens = %v, want 64000", gpt55["max_tokens"])
	}
	serviceTiers, ok := gpt55["service_tiers"].([]any)
	if !ok || len(serviceTiers) != 1 {
		t.Fatalf("expected gpt-5.5 priority service tier, got %#v", gpt55["service_tiers"])
	}
	if custom == nil {
		t.Fatal("expected custom model codex catalog entry")
	}
	if got, _ := custom["display_name"].(string); got != "Custom Codex Model" {
		t.Fatalf("custom display_name = %q, want Custom Codex Model", got)
	}
	wantCustomPriority := codexClientTestMaxTemplatePriority(t) + 100
	if got := int(codexClientTestPriority(custom["priority"])); got != wantCustomPriority {
		t.Fatalf("custom priority = %v, want %d", custom["priority"], wantCustomPriority)
	}
	if got, _ := custom["description"].(string); got != "Custom model from registry" {
		t.Fatalf("custom description = %q, want Custom model from registry", got)
	}
	if got, _ := custom["context_window"].(float64); got != 123456 {
		t.Fatalf("custom context_window = %v, want 123456", custom["context_window"])
	}
	assertCodexSupportedReasoningLevels(t, custom, []string{"none", "minimal", "low", "medium", "high", "xhigh"})
	if custom["base_instructions"] == gpt55["base_instructions"] {
		t.Fatal("expected custom model to use compact instructions instead of the full gpt-5.5 template")
	}
	if got, _ := custom["base_instructions"].(string); got == "" {
		t.Fatal("expected custom model to include base_instructions")
	}
	customMessages, _ := custom["model_messages"].(map[string]any)
	if customMessages["instructions_template"] != custom["base_instructions"] {
		t.Fatalf("expected custom instructions_template to match base_instructions, got %#v", customMessages["instructions_template"])
	}
	if _, ok := custom["available_in_plans"].([]any); !ok {
		t.Fatalf("expected custom model to use gpt-5.5 available_in_plans fallback, got %#v", custom["available_in_plans"])
	}
	if got, _ := custom["prefer_websockets"].(bool); got {
		t.Fatalf("custom prefer_websockets = %v, want false", custom["prefer_websockets"])
	}
	customServiceTiers, ok := custom["service_tiers"].([]any)
	if !ok || len(customServiceTiers) != 0 {
		t.Fatalf("expected custom model service_tiers = [], got %#v", custom["service_tiers"])
	}
	if value, present := custom["apply_patch_tool_type"]; !present || value != "freeform" {
		t.Fatalf("custom apply_patch_tool_type = %#v (present %v), want freeform", value, present)
	}
	assertCodexNullableCatalogField(t, custom, "upgrade")
	assertCodexNullableCatalogField(t, custom, "availability_nux")

	hiddenModels := map[string]bool{
		"grok-imagine-image-quality":     false,
		"gpt-image-2":                    false,
		"gpt-image-2.5-flare":            false,
		"gpt-image-2.5-sunburst":         false,
		"gpt-image-2.5":                  false,
		"gpt-image-2.6-flare":            false,
		"gpt-image-2.6-sunburst":         false,
		"gpt-image-2.6":                  false,
		"grok-imagine-image":             false,
		"grok-imagine-image-2.0":         false,
		"grok-imagine-video":             false,
		"grok-imagine-video-1.5":         false,
		"grok-imagine-video-1.5-preview": false,
	}
	for _, model := range resp.Models {
		slug, _ := model["slug"].(string)
		if _, ok := hiddenModels[slug]; !ok {
			continue
		}
		if visibility, _ := model["visibility"].(string); visibility != "hide" {
			t.Fatalf("%s visibility = %q, want hide", slug, visibility)
		}
		hiddenModels[slug] = true
	}
	for slug, found := range hiddenModels {
		if !found {
			t.Fatalf("expected hidden model %s in codex catalog", slug)
		}
	}
}

func TestModelsWithClientVersion_DevinDisplayName(t *testing.T) {
	devinClientID := "test-devin-client-version-models"
	openaiClientID := "test-openai-client-version-models"
	modelRegistry := registry.GetGlobalRegistry()
	modelRegistry.RegisterClient(devinClientID, "devin", []*registry.ModelInfo{
		{
			ID:          "devin/swe-2",
			Object:      "model",
			OwnedBy:     "cognition",
			Type:        "devin",
			DisplayName: "SWE-2",
		},
		{
			ID:          "devin/gpt-6-astra",
			Object:      "model",
			OwnedBy:     "openai",
			Type:        "devin",
			DisplayName: "GPT-6 Astra",
		},
	})
	modelRegistry.RegisterClient(openaiClientID, "openai", []*registry.ModelInfo{
		{
			ID:          "standard-openai-model",
			Object:      "model",
			OwnedBy:     "openai",
			Type:        "openai",
			DisplayName: "Standard Model",
		},
	})
	t.Cleanup(func() {
		modelRegistry.UnregisterClient(devinClientID)
		modelRegistry.UnregisterClient(openaiClientID)
	})

	server := newTestServer(t)

	req := httptest.NewRequest(http.MethodGet, "/v1/models?client_version=0.153.4", nil)
	req.Header.Set("Authorization", "Bearer test-key")

	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
	}

	var resp struct {
		Models []map[string]any `json:"models"`
	}
	if errUnmarshal := json.Unmarshal(rr.Body.Bytes(), &resp); errUnmarshal != nil {
		t.Fatalf("failed to parse response JSON: %v", errUnmarshal)
	}

	bySlug := make(map[string]map[string]any, len(resp.Models))
	for _, m := range resp.Models {
		if slug, ok := m["slug"].(string); ok {
			bySlug[slug] = m
		}
	}

	swe2, ok := bySlug["devin/swe-2"]
	if !ok {
		t.Fatal("expected devin/swe-2 in models list")
	}
	if got, _ := swe2["display_name"].(string); got != "SWE-2 (Devin)" {
		t.Fatalf("devin/swe-2 display_name = %q, want SWE-2 (Devin)", got)
	}

	astra, ok := bySlug["devin/gpt-6-astra"]
	if !ok {
		t.Fatal("expected devin/gpt-6-astra in models list")
	}
	if got, _ := astra["display_name"].(string); got != "GPT-6 Astra (Devin)" {
		t.Fatalf("devin/gpt-6-astra display_name = %q, want GPT-6 Astra (Devin)", got)
	}

	std, ok := bySlug["standard-openai-model"]
	if !ok {
		t.Fatal("expected standard-openai-model in models list")
	}
	if got, _ := std["display_name"].(string); got != "Standard Model" {
		t.Fatalf("standard-openai-model display_name = %q, want Standard Model", got)
	}
}

func TestHomeCodexClientModels_DevinDisplayName(t *testing.T) {
	entries := []homeModelEntry{
		{
			id:          "devin/swe-2",
			displayName: "SWE-2",
			providers:   []string{"devin"},
		},
		{
			id:          "home-regular",
			displayName: "Home Regular",
			providers:   []string{"openai"},
		},
	}
	models := make([]map[string]any, 0, len(entries))
	for _, entry := range entries {
		models = append(models, formatHomeCodexModel(entry))
	}
	resp := codexmodels.BuildResponseForClient(models, nil, false, "0.153.4")
	catalog, ok := resp["models"].([]map[string]any)
	if !ok {
		t.Fatalf("expected []map[string]any, got %T", resp["models"])
	}
	bySlug := make(map[string]map[string]any, len(catalog))
	for _, m := range catalog {
		slug, _ := m["slug"].(string)
		bySlug[slug] = m
	}
	if got, _ := bySlug["devin/swe-2"]["display_name"].(string); got != "SWE-2 (Devin)" {
		t.Fatalf("devin/swe-2 display_name = %q, want SWE-2 (Devin)", got)
	}
	if got, _ := bySlug["home-regular"]["display_name"].(string); got != "Home Regular" {
		t.Fatalf("home-regular display_name = %q, want Home Regular", got)
	}
}

func TestCodexClientModelsEndpoint_FiltersMaxAndUltraForOlderClientVersion(t *testing.T) {
	clientID := "codex-client-version-filter-test"
	modelRegistry := registry.GetGlobalRegistry()
	modelRegistry.RegisterClient(clientID, "openai", []*registry.ModelInfo{
		{
			ID:          "gpt-5.6-sol",
			Object:      "model",
			OwnedBy:     "openai",
			Type:        "openai",
			DisplayName: "GPT-5.6-Sol",
		},
	})
	t.Cleanup(func() {
		modelRegistry.UnregisterClient(clientID)
	})

	server := newTestServer(t)

	// Older client version 0.137.0 should NOT have max or ultra reasoning levels
	reqOld := httptest.NewRequest(http.MethodGet, "/v1/models?client_version=0.137.0", nil)
	reqOld.Header.Set("Authorization", "Bearer test-key")
	rrOld := httptest.NewRecorder()
	server.engine.ServeHTTP(rrOld, reqOld)

	if rrOld.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d body=%s", rrOld.Code, http.StatusOK, rrOld.Body.String())
	}

	var respOld struct {
		Models []map[string]any `json:"models"`
	}
	if err := json.Unmarshal(rrOld.Body.Bytes(), &respOld); err != nil {
		t.Fatalf("failed to parse response JSON: %v", err)
	}

	foundSol := false
	for _, m := range respOld.Models {
		if slug, _ := m["slug"].(string); slug == "gpt-5.6-sol" {
			foundSol = true
			levels, ok := m["supported_reasoning_levels"].([]any)
			if !ok {
				t.Fatalf("expected supported_reasoning_levels for gpt-5.6-sol, got %#v", m["supported_reasoning_levels"])
			}
			for _, rawLevel := range levels {
				level, _ := rawLevel.(map[string]any)
				effort, _ := level["effort"].(string)
				if effort == "max" || effort == "ultra" {
					t.Fatalf("older client 0.137.0 received unsupported reasoning effort %q", effort)
				}
			}
		}
	}
	if !foundSol {
		t.Fatal("expected gpt-5.6-sol in codex catalog")
	}

	// Newer client version 0.149.1 should preserve max and ultra reasoning levels
	reqNew := httptest.NewRequest(http.MethodGet, "/v1/models?client_version=0.149.1", nil)
	reqNew.Header.Set("Authorization", "Bearer test-key")
	rrNew := httptest.NewRecorder()
	server.engine.ServeHTTP(rrNew, reqNew)

	if rrNew.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d body=%s", rrNew.Code, http.StatusOK, rrNew.Body.String())
	}

	var respNew struct {
		Models []map[string]any `json:"models"`
	}
	if err := json.Unmarshal(rrNew.Body.Bytes(), &respNew); err != nil {
		t.Fatalf("failed to parse response JSON: %v", err)
	}

	foundNewUltra := false
	for _, m := range respNew.Models {
		if slug, _ := m["slug"].(string); slug == "gpt-5.6-sol" {
			levels, ok := m["supported_reasoning_levels"].([]any)
			if !ok {
				t.Fatalf("expected supported_reasoning_levels for gpt-5.6-sol, got %#v", m["supported_reasoning_levels"])
			}
			for _, rawLevel := range levels {
				level, _ := rawLevel.(map[string]any)
				if effort, _ := level["effort"].(string); effort == "ultra" {
					foundNewUltra = true
				}
			}
		}
	}
	if !foundNewUltra {
		t.Fatal("expected ultra reasoning effort for newer client 0.149.1")
	}

	// Unparseable / empty client versions (e.g. client_version=, client_version=pi) should also preserve max and ultra (unfiltered)
	for _, unparsedVersion := range []string{"", "pi", "latest"} {
		path := "/v1/models?client_version=" + unparsedVersion
		reqUnparsed := httptest.NewRequest(http.MethodGet, path, nil)
		reqUnparsed.Header.Set("Authorization", "Bearer test-key")
		rrUnparsed := httptest.NewRecorder()
		server.engine.ServeHTTP(rrUnparsed, reqUnparsed)

		if rrUnparsed.Code != http.StatusOK {
			t.Fatalf("path %q status = %d, want %d body=%s", path, rrUnparsed.Code, http.StatusOK, rrUnparsed.Body.String())
		}

		var respUnparsed struct {
			Models []map[string]any `json:"models"`
		}
		if err := json.Unmarshal(rrUnparsed.Body.Bytes(), &respUnparsed); err != nil {
			t.Fatalf("path %q parse error: %v", path, err)
		}

		foundUltra := false
		for _, m := range respUnparsed.Models {
			if slug, _ := m["slug"].(string); slug == "gpt-5.6-sol" {
				levels, _ := m["supported_reasoning_levels"].([]any)
				for _, rawLevel := range levels {
					level, _ := rawLevel.(map[string]any)
					if effort, _ := level["effort"].(string); effort == "ultra" {
						foundUltra = true
					}
				}
			}
		}
		if !foundUltra {
			t.Fatalf("path %q expected ultra reasoning effort to be preserved for unparsed client_version", path)
		}
	}
}

func codexClientTestPriority(raw any) int {
	switch value := raw.(type) {
	case int:
		return value
	case float64:
		return int(value)
	default:
		return -1
	}
}

func codexClientTestMaxTemplatePriority(t *testing.T) int {
	t.Helper()
	var payload struct {
		Models []map[string]any `json:"models"`
	}
	if err := json.Unmarshal(registry.GetCodexClientModelsJSON(), &payload); err != nil {
		t.Fatalf("parse Codex client model templates: %v", err)
	}
	maxPriority := 0
	for _, model := range payload.Models {
		if priority := codexClientTestPriority(model["priority"]); priority > maxPriority {
			maxPriority = priority
		}
	}
	return maxPriority
}

func assertCodexNullableCatalogField(t *testing.T, model map[string]any, key string) {
	t.Helper()
	value, exists := model[key]
	if !exists {
		t.Fatalf("%s must be present and null so Codex can decode the catalog", key)
	}
	if value != nil {
		t.Fatalf("%s = %#v, want null", key, value)
	}
}

func assertCodexSupportedReasoningLevels(t *testing.T, model map[string]any, want []string) {
	t.Helper()

	rawLevels, ok := model["supported_reasoning_levels"].([]any)
	if !ok {
		t.Fatalf("expected supported_reasoning_levels, got %#v", model["supported_reasoning_levels"])
	}
	if len(rawLevels) != len(want) {
		t.Fatalf("supported_reasoning_levels length = %d, want %d: %#v", len(rawLevels), len(want), rawLevels)
	}
	for index, rawLevel := range rawLevels {
		levelEntry, ok := rawLevel.(map[string]any)
		if !ok {
			t.Fatalf("supported_reasoning_levels[%d] = %#v, want object", index, rawLevel)
		}
		if got, _ := levelEntry["effort"].(string); got != want[index] {
			t.Fatalf("supported_reasoning_levels[%d].effort = %q, want %q", index, got, want[index])
		}
	}
}

func TestDefaultRequestLoggerFactory_UsesResolvedLogDirectory(t *testing.T) {
	t.Setenv("WRITABLE_PATH", "")
	t.Setenv("writable_path", "")

	originalWD, errGetwd := os.Getwd()
	if errGetwd != nil {
		t.Fatalf("failed to get current working directory: %v", errGetwd)
	}

	tmpDir := t.TempDir()
	if errChdir := os.Chdir(tmpDir); errChdir != nil {
		t.Fatalf("failed to switch working directory: %v", errChdir)
	}
	defer func() {
		if errChdirBack := os.Chdir(originalWD); errChdirBack != nil {
			t.Fatalf("failed to restore working directory: %v", errChdirBack)
		}
	}()

	// Force ResolveLogDirectory to fallback to auth-dir/logs by making ./logs not a writable directory.
	if errWriteFile := os.WriteFile(filepath.Join(tmpDir, "logs"), []byte("not-a-directory"), 0o644); errWriteFile != nil {
		t.Fatalf("failed to create blocking logs file: %v", errWriteFile)
	}

	configDir := filepath.Join(tmpDir, "config")
	if errMkdirConfig := os.MkdirAll(configDir, 0o755); errMkdirConfig != nil {
		t.Fatalf("failed to create config dir: %v", errMkdirConfig)
	}
	configPath := filepath.Join(configDir, "config.yaml")

	authDir := filepath.Join(tmpDir, "auth")
	if errMkdirAuth := os.MkdirAll(authDir, 0o700); errMkdirAuth != nil {
		t.Fatalf("failed to create auth dir: %v", errMkdirAuth)
	}

	cfg := &proxyconfig.Config{
		SDKConfig: proxyconfig.SDKConfig{
			RequestLog: false,
		},
		AuthDir:           authDir,
		ErrorLogsMaxFiles: 10,
	}

	logger := defaultRequestLoggerFactory(cfg, configPath)
	fileLogger, ok := logger.(*internallogging.FileRequestLogger)
	if !ok {
		t.Fatalf("expected *FileRequestLogger, got %T", logger)
	}

	errLog := fileLogger.LogRequestWithOptions(
		"/v1/chat/completions",
		http.MethodPost,
		map[string][]string{"Content-Type": []string{"application/json"}},
		[]byte(`{"input":"hello"}`),
		http.StatusBadGateway,
		map[string][]string{"Content-Type": []string{"application/json"}},
		[]byte(`{"error":"upstream failure"}`),
		nil,
		nil,
		nil,
		nil,
		nil,
		true,
		"issue-1711",
		time.Now(),
		time.Now(),
	)
	if errLog != nil {
		t.Fatalf("failed to write forced error request log: %v", errLog)
	}

	authLogsDir := filepath.Join(authDir, "logs")
	authEntries, errReadAuthDir := os.ReadDir(authLogsDir)
	if errReadAuthDir != nil {
		t.Fatalf("failed to read auth logs dir %s: %v", authLogsDir, errReadAuthDir)
	}
	foundErrorLogInAuthDir := false
	for _, entry := range authEntries {
		if strings.HasPrefix(entry.Name(), "error-") && strings.HasSuffix(entry.Name(), ".log") {
			foundErrorLogInAuthDir = true
			break
		}
	}
	if !foundErrorLogInAuthDir {
		t.Fatalf("expected forced error log in auth fallback dir %s, got entries: %+v", authLogsDir, authEntries)
	}

	configLogsDir := filepath.Join(configDir, "logs")
	configEntries, errReadConfigDir := os.ReadDir(configLogsDir)
	if errReadConfigDir != nil && !os.IsNotExist(errReadConfigDir) {
		t.Fatalf("failed to inspect config logs dir %s: %v", configLogsDir, errReadConfigDir)
	}
	for _, entry := range configEntries {
		if strings.HasPrefix(entry.Name(), "error-") && strings.HasSuffix(entry.Name(), ".log") {
			t.Fatalf("unexpected forced error log in config dir %s", configLogsDir)
		}
	}
}

func TestFormatHomeClaudeModelIncludesAnthropicSchemaFields(t *testing.T) {
	withMetadata := formatHomeClaudeModel(homeModelEntry{
		id:                  "claude-sonnet-4-6",
		created:             1771372800,
		ownedBy:             "anthropic",
		displayName:         "Claude 4.6 Sonnet",
		contextLength:       200000,
		maxCompletionTokens: 64000,
	})
	if got := withMetadata["created_at"]; got != "2026-02-18T00:00:00Z" {
		t.Fatalf("created_at = %v, want RFC3339 timestamp", got)
	}
	if got := withMetadata["type"]; got != "model" {
		t.Fatalf("type = %v, want model", got)
	}
	if got := withMetadata["display_name"]; got != "Claude 4.6 Sonnet" {
		t.Fatalf("display_name = %v, want Claude 4.6 Sonnet", got)
	}
	if got := withMetadata["max_input_tokens"]; got != 200000 {
		t.Fatalf("max_input_tokens = %v, want 200000", got)
	}
	if got := withMetadata["max_tokens"]; got != 64000 {
		t.Fatalf("max_tokens = %v, want 64000", got)
	}

	withDefaults := formatHomeClaudeModel(homeModelEntry{id: "claude-no-limits"})
	if got := withDefaults["display_name"]; got != "claude-no-limits" {
		t.Fatalf("display_name fallback = %v, want claude-no-limits", got)
	}

	customModel := formatHomeClaudeModel(homeModelEntry{id: "gpt-4o", displayName: "GPT-4o"})
	if got := customModel["id"]; got != "gpt-4o" {
		t.Fatalf("id = %v, want gpt-4o", got)
	}
	if got := customModel["display_name"]; got != "GPT-4o" {
		t.Fatalf("display_name = %v, want GPT-4o", got)
	}
	if got := withDefaults["max_input_tokens"]; got != registry.DefaultClaudeMaxInputTokens {
		t.Fatalf("max_input_tokens fallback = %v, want %d", got, registry.DefaultClaudeMaxInputTokens)
	}
	if got := withDefaults["max_tokens"]; got != registry.DefaultClaudeMaxOutputTokens {
		t.Fatalf("max_tokens fallback = %v, want %d", got, registry.DefaultClaudeMaxOutputTokens)
	}
	if _, ok := withDefaults["created_at"]; ok {
		t.Fatalf("created_at should be omitted when source created is missing, got %v", withDefaults)
	}
}

func TestDecodeHomeModelsKeepsTokenMetadata(t *testing.T) {
	entries, errDecode := decodeHomeModels([]byte(`{
		"claude": [
			{
				"id": "claude-sonnet-4-6",
				"created": 1771372800,
				"owned_by": "anthropic",
				"context_length": 200000,
				"max_completion_tokens": 64000
			}
		],
		"gemini": [
			{
				"name": "models/gemini-3-pro",
				"inputTokenLimit": 1048576,
				"outputTokenLimit": 65536,
				"thinking": {
					"min": 128,
					"max": 65535,
					"dynamic_allowed": true,
					"levels": ["low", "medium", "high"]
				}
			}
		]
	}`))
	if errDecode != nil {
		t.Fatalf("decodeHomeModels returned error: %v", errDecode)
	}

	byID := make(map[string]homeModelEntry, len(entries))
	for _, entry := range entries {
		byID[entry.id] = entry
	}
	claudeEntry, ok := byID["claude-sonnet-4-6"]
	if !ok {
		t.Fatalf("expected claude-sonnet-4-6 entry, got %v", byID)
	}
	if claudeEntry.contextLength != 200000 || claudeEntry.maxCompletionTokens != 64000 {
		t.Fatalf("claude token metadata = %d/%d, want 200000/64000", claudeEntry.contextLength, claudeEntry.maxCompletionTokens)
	}
	geminiEntry, ok := byID["gemini-3-pro"]
	if !ok {
		t.Fatalf("expected gemini-3-pro entry, got %v", byID)
	}
	if geminiEntry.contextLength != 1048576 || geminiEntry.maxCompletionTokens != 65536 {
		t.Fatalf("gemini token metadata = %d/%d, want 1048576/65536", geminiEntry.contextLength, geminiEntry.maxCompletionTokens)
	}
	if geminiEntry.thinking == nil || !reflect.DeepEqual(geminiEntry.thinking.Levels, []string{"low", "medium", "high"}) {
		t.Fatalf("gemini thinking metadata = %#v, want low/medium/high", geminiEntry.thinking)
	}

	formatted := formatHomeCodexModel(geminiEntry)
	if got := homeModelInt64Value(formatted, "context_length"); got != 1048576 {
		t.Fatalf("formatted Gemini context_length = %d, want 1048576", got)
	}
	if got, ok := formatted["thinking"].(*registry.ThinkingSupport); !ok || !reflect.DeepEqual(got.Levels, []string{"low", "medium", "high"}) {
		t.Fatalf("formatted Gemini thinking metadata = %#v, want low/medium/high", formatted["thinking"])
	}
}

func TestHomeGeminiModelsIncludeTokenLimits(t *testing.T) {
	withLimits := formatHomeGeminiModel(homeModelEntry{
		id:                  "gemini-3.8-flash",
		displayName:         "Gemini 3.8 Flash",
		contextLength:       1048576,
		maxCompletionTokens: 65536,
	})
	if got := withLimits["name"]; got != "models/gemini-3.8-flash" {
		t.Fatalf("name = %v, want models/gemini-3.8-flash", got)
	}
	if got := withLimits["displayName"]; got != "Gemini 3.8 Flash" {
		t.Fatalf("displayName = %v, want Gemini 3.8 Flash", got)
	}
	if got := withLimits["inputTokenLimit"]; got != 1048576 {
		t.Fatalf("inputTokenLimit = %v, want 1048576", got)
	}
	if got := withLimits["outputTokenLimit"]; got != 65536 {
		t.Fatalf("outputTokenLimit = %v, want 65536", got)
	}

	withoutLimits := formatHomeGeminiModel(homeModelEntry{
		id: "gemini-no-limits",
	})
	if _, ok := withoutLimits["inputTokenLimit"]; ok {
		t.Fatalf("expected inputTokenLimit to be omitted when unknown, got %v", withoutLimits)
	}
	if _, ok := withoutLimits["outputTokenLimit"]; ok {
		t.Fatalf("expected outputTokenLimit to be omitted when unknown, got %v", withoutLimits)
	}

	server := newTestServer(t)
	server.cfg.Home.Enabled = true
	engine := gin.New()
	engine.GET("/v1beta/models", server.geminiModelsHandler(nil))
	engine.GET("/v1beta/models/*action", server.geminiGetHandler(nil))

	client := newHomeCatalogClient(t, `{
		"a-custom": [
			{
				"id": "gemini-3.8-flash"
			}
		],
		"gemini": [
			{
				"id": "gemini-3.8-flash",
				"display_name": "Gemini 3.8 Flash",
				"context_length": 1048576,
				"max_completion_tokens": 65536
			}
		]
	}`)
	previousHome := home.Current()
	home.SetCurrent(client)
	t.Cleanup(func() { home.SetCurrent(previousHome) })

	listRecorder := httptest.NewRecorder()
	engine.ServeHTTP(listRecorder, httptest.NewRequest(http.MethodGet, "/v1beta/models", nil))
	if listRecorder.Code != http.StatusOK {
		t.Fatalf("GET /v1beta/models status = %d; body=%s", listRecorder.Code, listRecorder.Body.String())
	}
	var listPayload struct {
		Models []map[string]any `json:"models"`
	}
	if errUnmarshal := json.Unmarshal(listRecorder.Body.Bytes(), &listPayload); errUnmarshal != nil {
		t.Fatalf("unmarshal /v1beta/models response: %v", errUnmarshal)
	}
	if len(listPayload.Models) != 1 {
		t.Fatalf("models len = %d, want 1; body=%s", len(listPayload.Models), listRecorder.Body.String())
	}
	if got := homeModelInt64Value(listPayload.Models[0], "inputTokenLimit"); got != 1048576 {
		t.Fatalf("list inputTokenLimit = %d, want 1048576; model=%v", got, listPayload.Models[0])
	}
	if got := homeModelInt64Value(listPayload.Models[0], "outputTokenLimit"); got != 65536 {
		t.Fatalf("list outputTokenLimit = %d, want 65536; model=%v", got, listPayload.Models[0])
	}

	getRecorder := httptest.NewRecorder()
	engine.ServeHTTP(getRecorder, httptest.NewRequest(http.MethodGet, "/v1beta/models/gemini-3.8-flash", nil))
	if getRecorder.Code != http.StatusOK {
		t.Fatalf("GET /v1beta/models/gemini-3.8-flash status = %d; body=%s", getRecorder.Code, getRecorder.Body.String())
	}
	var getPayload map[string]any
	if errUnmarshal := json.Unmarshal(getRecorder.Body.Bytes(), &getPayload); errUnmarshal != nil {
		t.Fatalf("unmarshal /v1beta/models/gemini-3.8-flash response: %v", errUnmarshal)
	}
	if got := homeModelInt64Value(getPayload, "inputTokenLimit"); got != 1048576 {
		t.Fatalf("get inputTokenLimit = %d, want 1048576; model=%v", got, getPayload)
	}
	if got := homeModelInt64Value(getPayload, "outputTokenLimit"); got != 65536 {
		t.Fatalf("get outputTokenLimit = %d, want 65536; model=%v", got, getPayload)
	}
}

func TestHomeCodexModels_MaxContextLength(t *testing.T) {
	entries, errDecode := decodeHomeModels([]byte(`{
		"codex": [
			{"id": "gpt-6-sol", "context_length": 272000, "max_context_length": 524288}
		]
	}`))
	if errDecode != nil {
		t.Fatalf("decodeHomeModels error = %v", errDecode)
	}
	if len(entries) != 1 || entries[0].maxContextLength != 524288 {
		t.Fatalf("unexpected decoded entry: %+v", entries)
	}

	formatted := formatHomeCodexModel(entries[0])
	if got := formatted["max_context_length"]; got != 524288 {
		t.Fatalf("formatHomeCodexModel max_context_length = %v, want 524288", got)
	}
}

func TestHomeCodexModels_OAuthSettingsChannelIsolation(t *testing.T) {
	cfg := &proxyconfig.Config{
		OAuthSettings: map[string][]proxyconfig.OAuthModelSetting{
			"codex": {
				{Name: "shared-model", MaxContextLength: 524288},
			},
			"claude": {
				{Name: "shared-model", MaxContextLength: 200000},
			},
		},
	}

	// 1. Entry from codex only -> receives codex setting (524288)
	codexEntry := homeModelEntry{
		id:        "shared-model",
		providers: []string{"codex"},
	}
	mCodex := formatHomeCodexModelWithSettings(codexEntry, cfg)
	if got := mCodex["max_context_length"]; got != 524288 {
		t.Errorf("mCodex max_context_length = %v, want 524288", got)
	}

	// 2. Entry from claude only -> receives claude setting (200000), NOT codex setting
	claudeEntry := homeModelEntry{
		id:        "shared-model",
		providers: []string{"claude"},
	}
	mClaude := formatHomeCodexModelWithSettings(claudeEntry, cfg)
	if got := mClaude["max_context_length"]; got != 200000 {
		t.Errorf("mClaude max_context_length = %v, want 200000", got)
	}

	// 3. Entry from other provider -> does not receive either setting
	otherEntry := homeModelEntry{
		id:        "shared-model",
		providers: []string{"vertex"},
	}
	mOther := formatHomeCodexModelWithSettings(otherEntry, cfg)
	if got := mOther["max_context_length"]; got != nil {
		t.Errorf("mOther max_context_length = %v, want nil", got)
	}

	// 4. Entry from multiple providers (claude, codex) -> deterministic codex precedence
	multiEntry := homeModelEntry{
		id:        "shared-model",
		providers: []string{"claude", "codex"},
	}
	mMulti := formatHomeCodexModelWithSettings(multiEntry, cfg)
	if got := mMulti["max_context_length"]; got != 524288 {
		t.Errorf("mMulti max_context_length = %v, want 524288", got)
	}
}

func TestHomeModelsAuthStatus(t *testing.T) {
	cases := []struct {
		name        string
		raw         string
		wantStatus  int
		wantHandled bool
	}{
		{"no credentials", `{"error":{"type":"no_credentials","message":"Missing API key"}}`, http.StatusUnauthorized, true},
		{"invalid credential", `{"error":{"type":"invalid_credential","message":"Invalid API key"}}`, http.StatusUnauthorized, true},
		{"internal error maps to bad gateway", `{"error":{"type":"internal_error","message":"boom"}}`, http.StatusBadGateway, true},
		{"models payload not an error", `{"openai":[{"id":"gpt-5.5"}]}`, 0, false},
		{"empty payload not an error", `{}`, 0, false},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			status, handled := homeModelsAuthStatus([]byte(tc.raw))
			if handled != tc.wantHandled {
				t.Fatalf("handled = %v, want %v (status=%d)", handled, tc.wantHandled, status)
			}
			if handled && status != tc.wantStatus {
				t.Fatalf("status = %d, want %d", status, tc.wantStatus)
			}
		})
	}
}

func TestHomeModelsErrorMessage(t *testing.T) {
	if msg := homeModelsErrorMessage([]byte(`{"error":{"type":"invalid_credential","message":"Invalid API key"}}`)); msg != "Invalid API key" {
		t.Fatalf("message = %q, want %q", msg, "Invalid API key")
	}
	if msg := homeModelsErrorMessage([]byte(`{"openai":[]}`)); msg != "home models request failed" {
		t.Fatalf("default message = %q, want fallback", msg)
	}
}

func TestInteractionsRouteRegistered(t *testing.T) {
	server := newTestServer(t)
	req := httptest.NewRequest(http.MethodPost, "/v1beta/interactions", strings.NewReader(`{"model":"gemini-3.5-flash","input":"hi"}`))
	req.Header.Set("Authorization", "Bearer test-key")
	rr := httptest.NewRecorder()
	server.engine.ServeHTTP(rr, req)
	if rr.Code == http.StatusNotFound {
		t.Fatalf("status = %d, want route registered; body=%s", rr.Code, rr.Body.String())
	}
}

func TestUpdateClientsContext_AntigravityConnectionPoolPurgesTransports(t *testing.T) {
	server := newTestServer(t)

	enabled := true
	disabled := false

	cfg1 := *server.cfg
	cfg1.Antigravity.ConnectionPool.Enabled = &enabled
	cfg1.Antigravity.ConnectionPool.IdleConnTimeout = "30s"
	server.oldConfigYaml, _ = yaml.Marshal(&cfg1)

	// Pre-populate the cache before reload
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}))
	defer srv.Close()

	testAuth := &auth.Auth{
		ID:         "hot-reload-test-auth",
		Provider:   "antigravity",
		Attributes: map[string]string{"base_url": srv.URL},
		Metadata: map[string]any{
			"access_token": "token",
			"project_id":   "proj",
			"expired":      time.Now().Add(time.Hour).Format(time.RFC3339),
		},
	}
	exec := executor.NewAntigravityExecutor(&cfg1)
	req := httptest.NewRequest(http.MethodGet, srv.URL, nil)
	_, _ = exec.HttpRequest(context.Background(), testAuth, req)

	if executor.AntigravityTransportsLen() == 0 {
		t.Fatal("expected Antigravity transports to be cached before hot reload")
	}

	cfg2 := cfg1
	cfg2.Antigravity.ConnectionPool.Enabled = &disabled
	cfg2.Antigravity.ConnectionPool.IdleConnTimeout = "10s"

	if ok := server.UpdateClientsContext(context.Background(), &cfg2); !ok {
		t.Fatal("UpdateClientsContext returned false")
	}

	if got := executor.AntigravityTransportsLen(); got != 0 {
		t.Fatalf("AntigravityTransportsLen() after reload = %d, want 0", got)
	}
}

type mockServerStreamingCaptureExecutor struct {
	cfg            *proxyconfig.Config
	capturedCtx    context.Context
	executionCalls int
}

func (e *mockServerStreamingCaptureExecutor) Identifier() string { return "codex-test" }

func (e *mockServerStreamingCaptureExecutor) Execute(ctx context.Context, _ *auth.Auth, _ coreexecutor.Request, _ coreexecutor.Options) (coreexecutor.Response, error) {
	e.capturedCtx = ctx
	return coreexecutor.Response{Payload: []byte(`{"id":"resp-1","status":"completed"}`)}, nil
}

func (e *mockServerStreamingCaptureExecutor) ExecuteStream(ctx context.Context, _ *auth.Auth, _ coreexecutor.Request, _ coreexecutor.Options) (*coreexecutor.StreamResult, error) {
	e.capturedCtx = ctx
	e.executionCalls++

	runtimehelps.RecordAPIRequest(ctx, e.cfg, runtimehelps.UpstreamRequestLog{
		URL:     "https://api.example.com/v1/responses",
		Method:  http.MethodPost,
		Headers: http.Header{"Content-Type": []string{"application/json"}},
		Body:    []byte(`{"model":"gpt-5-codex","input":[]}`),
	})

	ch := make(chan coreexecutor.StreamChunk, 2)
	chunkPayload := []byte("event: response.output_item.added\ndata: {\"type\":\"response.output_item.added\"}\n\n")
	runtimehelps.AppendAPIResponseChunk(ctx, e.cfg, chunkPayload)
	ch <- coreexecutor.StreamChunk{Payload: chunkPayload}

	terminalPayload := []byte("event: response.completed\ndata: {\"type\":\"response.completed\"}\n\n")
	runtimehelps.AppendAPIResponseChunk(ctx, e.cfg, terminalPayload)
	ch <- coreexecutor.StreamChunk{Payload: terminalPayload}
	close(ch)

	return &coreexecutor.StreamResult{Chunks: ch}, nil
}

func (e *mockServerStreamingCaptureExecutor) Refresh(_ context.Context, auth *auth.Auth) (*auth.Auth, error) {
	return auth, nil
}

func (e *mockServerStreamingCaptureExecutor) CountTokens(_ context.Context, _ *auth.Auth, _ coreexecutor.Request, _ coreexecutor.Options) (coreexecutor.Response, error) {
	return coreexecutor.Response{}, errors.New("not implemented")
}

func (e *mockServerStreamingCaptureExecutor) HttpRequest(_ context.Context, _ *auth.Auth, _ *http.Request) (*http.Response, error) {
	return nil, errors.New("not implemented")
}

func TestServerResponsesStreamingRequestLogCapturesUpstreamSections(t *testing.T) {
	gin.SetMode(gin.TestMode)

	tmpDir := t.TempDir()
	logsDir := filepath.Join(tmpDir, "logs")
	if err := os.MkdirAll(logsDir, 0o700); err != nil {
		t.Fatalf("failed to create logs dir: %v", err)
	}

	mockLogger := internallogging.NewFileRequestLogger(true, logsDir, "", 10)
	server := newTestServerWithOptions(t, WithRequestLoggerFactory(func(*proxyconfig.Config, string) internallogging.RequestLogger {
		return mockLogger
	}))
	server.cfg.RequestLog = true
	server.cfg.LoggingToFile = true

	mockExec := &mockServerStreamingCaptureExecutor{cfg: server.cfg}
	server.handlers.AuthManager.RegisterExecutor(mockExec)

	credential := &auth.Auth{
		ID:       "codex-stream-auth",
		Provider: mockExec.Identifier(),
		Status:   auth.StatusActive,
	}
	if _, err := server.handlers.AuthManager.Register(context.Background(), credential); err != nil {
		t.Fatalf("register auth: %v", err)
	}
	registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: "gpt-5-codex"}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(credential.ID)
	})

	rr := httptest.NewRecorder()
	body := `{"model":"gpt-5-codex","input":[{"type":"message","role":"user","content":"hi"}],"stream":true}`
	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body))
	req.Header.Set("Authorization", "Bearer test-key")
	req.Header.Set("Content-Type", "application/json")
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String())
	}
	if mockExec.executionCalls != 1 {
		t.Fatalf("executor calls = %d, want 1", mockExec.executionCalls)
	}

	entries, errReadDir := os.ReadDir(logsDir)
	if errReadDir != nil {
		t.Fatalf("read logs dir: %v", errReadDir)
	}
	var logPath string
	for _, entry := range entries {
		if strings.HasPrefix(entry.Name(), "v1-responses-") && strings.HasSuffix(entry.Name(), ".log") {
			logPath = filepath.Join(logsDir, entry.Name())
			break
		}
	}
	if logPath == "" {
		t.Fatal("streaming request log was not created in logs dir")
	}
	content, errReadLog := os.ReadFile(logPath)
	if errReadLog != nil {
		t.Fatalf("read log file: %v", errReadLog)
	}
	logText := string(content)
	apiRequestIdx := strings.Index(logText, "=== API REQUEST 1 ===")
	if apiRequestIdx == -1 {
		t.Fatalf("streaming log missing API REQUEST 1:\n%s", logText)
	}
	apiResponseIdx := strings.Index(logText, "=== API RESPONSE 1 ===")
	if apiResponseIdx == -1 {
		t.Fatalf("streaming log missing API RESPONSE 1:\n%s", logText)
	}
	downstreamResponseIdx := strings.Index(logText, "=== RESPONSE ===")
	if downstreamResponseIdx == -1 {
		t.Fatalf("streaming log missing downstream RESPONSE:\n%s", logText)
	}
	if apiRequestIdx >= apiResponseIdx || apiResponseIdx >= downstreamResponseIdx {
		t.Fatalf("unexpected section order (req=%d, apiResp=%d, resp=%d):\n%s", apiRequestIdx, apiResponseIdx, downstreamResponseIdx, logText)
	}
	apiRequestSection := logText[apiRequestIdx:apiResponseIdx]
	apiResponseSection := logText[apiResponseIdx:downstreamResponseIdx]
	if !strings.Contains(apiRequestSection, "https://api.example.com/v1/responses") {
		t.Fatalf("API REQUEST section missing upstream URL:\n%s", apiRequestSection)
	}
	if !strings.Contains(apiRequestSection, `"gpt-5-codex"`) {
		t.Fatalf("API REQUEST section missing request body:\n%s", apiRequestSection)
	}
	if !strings.Contains(apiResponseSection, "response.output_item.added") {
		t.Fatalf("API RESPONSE section missing response chunk data:\n%s", apiResponseSection)
	}
}

func TestServerCodexAPIKeyResponsesStreamingRequestLog(t *testing.T) {
	gin.SetMode(gin.TestMode)

	upstreamReceived := make(chan struct{}, 1)
	mockUpstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		select {
		case upstreamReceived <- struct{}{}:
		default:
		}
		w.Header().Set("Content-Type", "text/event-stream")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("data: {\"type\":\"response.output_item.added\"}\n\n"))
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
		_, _ = w.Write([]byte("data: {\"type\":\"response.completed\"}\n\n"))
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
	}))
	defer mockUpstream.Close()

	tmpDir := t.TempDir()
	logsDir := filepath.Join(tmpDir, "logs")
	if err := os.MkdirAll(logsDir, 0o700); err != nil {
		t.Fatalf("failed to create logs dir: %v", err)
	}

	mockLogger := internallogging.NewFileRequestLogger(true, logsDir, "", 10)
	server := newTestServerWithOptions(t, WithRequestLoggerFactory(func(*proxyconfig.Config, string) internallogging.RequestLogger {
		return mockLogger
	}))
	server.cfg.RequestLog = true
	server.cfg.LoggingToFile = true

	codexExec := executor.NewCodexExecutor(server.cfg)
	server.handlers.AuthManager.RegisterExecutor(codexExec)

	credential := &auth.Auth{
		ID:       "codex-api-key-test",
		Provider: "codex",
		Status:   auth.StatusActive,
		Attributes: map[string]string{
			auth.AttributeAPIKey: "test-codex-key",
			"base_url":           mockUpstream.URL,
		},
	}
	if _, err := server.handlers.AuthManager.Register(context.Background(), credential); err != nil {
		t.Fatalf("register auth: %v", err)
	}
	registry.GetGlobalRegistry().RegisterClient(credential.ID, credential.Provider, []*registry.ModelInfo{{ID: "gpt-5-codex"}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(credential.ID)
	})

	rr := httptest.NewRecorder()
	body := `{"model":"gpt-5-codex","input":[{"type":"message","role":"user","content":"hello"}],"stream":true}`
	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body))
	req.Header.Set("Authorization", "Bearer test-key")
	req.Header.Set("Content-Type", "application/json")
	server.engine.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String())
	}
	select {
	case <-upstreamReceived:
	case <-time.After(5 * time.Second):
		t.Fatal("timeout waiting for upstream request")
	}

	entries, errReadDir := os.ReadDir(logsDir)
	if errReadDir != nil {
		t.Fatalf("read logs dir: %v", errReadDir)
	}
	var logPath string
	for _, entry := range entries {
		if strings.HasPrefix(entry.Name(), "v1-responses-") && strings.HasSuffix(entry.Name(), ".log") {
			logPath = filepath.Join(logsDir, entry.Name())
			break
		}
	}
	if logPath == "" {
		t.Fatal("streaming request log was not created in logs dir")
	}
	content, errReadLog := os.ReadFile(logPath)
	if errReadLog != nil {
		t.Fatalf("read log file: %v", errReadLog)
	}
	logText := string(content)
	apiRequestIdx := strings.Index(logText, "=== API REQUEST 1 ===")
	if apiRequestIdx == -1 {
		t.Fatalf("streaming log missing API REQUEST 1:\n%s", logText)
	}
	apiResponseIdx := strings.Index(logText, "=== API RESPONSE 1 ===")
	if apiResponseIdx == -1 {
		t.Fatalf("streaming log missing API RESPONSE 1:\n%s", logText)
	}
	downstreamResponseIdx := strings.Index(logText, "=== RESPONSE ===")
	if downstreamResponseIdx == -1 {
		t.Fatalf("streaming log missing downstream RESPONSE:\n%s", logText)
	}
	if apiRequestIdx >= apiResponseIdx || apiResponseIdx >= downstreamResponseIdx {
		t.Fatalf("unexpected section order (req=%d, apiResp=%d, resp=%d):\n%s", apiRequestIdx, apiResponseIdx, downstreamResponseIdx, logText)
	}
	apiRequestSection := logText[apiRequestIdx:apiResponseIdx]
	apiResponseSection := logText[apiResponseIdx:downstreamResponseIdx]
	if !strings.Contains(apiRequestSection, mockUpstream.URL) {
		t.Fatalf("API REQUEST section missing upstream URL %s:\n%s", mockUpstream.URL, apiRequestSection)
	}
	if !strings.Contains(apiRequestSection, `"gpt-5-codex"`) {
		t.Fatalf("API REQUEST section missing request body:\n%s", apiRequestSection)
	}
	if !strings.Contains(apiResponseSection, "response.output_item.added") {
		t.Fatalf("API RESPONSE section missing response chunk data:\n%s", apiResponseSection)
	}
}

func TestModelsForCPAClientSerializesWebSearchCapabilities(t *testing.T) {
	modelRegistry := registry.GetGlobalRegistry()
	registrations := []struct {
		clientID string
		provider string
		modelID  string
	}{
		{"cpa-web-search-codex", "codex", "cpa-search-codex-model"},
		{"cpa-web-search-xai", "xai", "cpa-search-xai-model"},
		{"cpa-web-search-claude", "claude", "cpa-search-claude-model"},
		{"cpa-web-search-gemini", "gemini", "cpa-no-search-gemini-model"},
		{"cpa-web-search-mixed-codex", "codex", "cpa-mixed-search-model"},
		{"cpa-web-search-mixed-gemini", "gemini", "cpa-mixed-search-model"},
		{"cpa-web-search-prefixed", "codex", "team/cpa-prefixed-search-model"},
	}
	webSearch := true
	for _, registration := range registrations {
		modelRegistry.RegisterClient(registration.clientID, registration.provider, []*registry.ModelInfo{{
			ID: registration.modelID,
			NativeCapabilities: &registry.NativeCapabilities{
				WebSearch: &webSearch,
			},
		}})
	}
	t.Cleanup(func() {
		for _, registration := range registrations {
			modelRegistry.UnregisterClient(registration.clientID)
		}
	})

	server := newTestServer(t)
	req := httptest.NewRequest(http.MethodGet, "/v1/models?client_version=cpa", nil)
	req.Header.Set("Authorization", "Bearer test-key")
	recorder := httptest.NewRecorder()
	server.engine.ServeHTTP(recorder, req)
	if recorder.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d body=%s", recorder.Code, http.StatusOK, recorder.Body.String())
	}

	var response struct {
		Models []map[string]any `json:"models"`
	}
	if errUnmarshal := json.Unmarshal(recorder.Body.Bytes(), &response); errUnmarshal != nil {
		t.Fatalf("decode response: %v; body=%s", errUnmarshal, recorder.Body.String())
	}
	entries := make(map[string]map[string]any, len(response.Models))
	for _, model := range response.Models {
		slug, _ := model["slug"].(string)
		entries[slug] = model
	}
	for _, modelID := range []string{"cpa-search-codex-model", "cpa-search-xai-model", "cpa-search-claude-model", "team/cpa-prefixed-search-model"} {
		assertSerializedCPAWebSearch(t, entries[modelID], true)
	}
	for _, modelID := range []string{"cpa-no-search-gemini-model", "cpa-mixed-search-model"} {
		assertSerializedCPAWebSearch(t, entries[modelID], false)
	}

	legacyRequest := httptest.NewRequest(http.MethodGet, "/v1/models?client_version=0.153.4", nil)
	legacyRequest.Header.Set("Authorization", "Bearer test-key")
	legacyRecorder := httptest.NewRecorder()
	server.engine.ServeHTTP(legacyRecorder, legacyRequest)
	if strings.Contains(legacyRecorder.Body.String(), "cpa_capabilities") {
		t.Fatalf("non-CPA response exposed CPA capability: %s", legacyRecorder.Body.String())
	}
}

func TestHomeModelsRequirePerEntryWebSearchCapabilityAndConservativeRoutes(t *testing.T) {
	entries, errDecode := decodeHomeModels([]byte(`{
		"codex":[
			{"id":"home-codex","native_capabilities":{"web_search":true}},
			{"id":"home-unknown"},
			{"id":"home-duplicate","native_capabilities":{"web_search":true}},
			{"id":"home-duplicate","native_capabilities":{"web_search":false}}
		],
		"xai":[{"id":"home-xai","native_capabilities":{"web_search":true}}],
		"claude":[{"id":"gpt-5.5","native_capabilities":{"web_search":true}}],
		"gemini":[{"id":"home-gemini","native_capabilities":{"web_search":true}}],
		"custom":[{"id":"home-custom","native_capabilities":{"web_search":true}}]
	}`))
	if errDecode != nil {
		t.Fatalf("decode Home models: %v", errDecode)
	}

	models := make([]map[string]any, 0, len(entries))
	for _, entry := range entries {
		models = append(models, formatHomeCodexModel(entry))
	}
	response := codexmodels.BuildResponseForClientWithCPACapabilities(models, nil, homeWebSearchCapabilityForModel(entries), false, "cpa")
	catalog, ok := response["models"].([]map[string]any)
	if !ok {
		t.Fatalf("models = %#v, want []map[string]any", response["models"])
	}
	bySlug := make(map[string]map[string]any, len(catalog))
	for _, model := range catalog {
		slug, _ := model["slug"].(string)
		bySlug[slug] = model
	}
	for _, modelID := range []string{"home-codex", "home-xai", "gpt-5.5"} {
		assertSerializedCPAWebSearch(t, bySlug[modelID], true)
	}
	if supportsSearchTool, _ := bySlug["gpt-5.5"]["supports_search_tool"].(bool); !supportsSearchTool {
		t.Fatal("CPA capability metadata changed legacy Home supports_search_tool")
	}
	for _, modelID := range []string{"home-gemini", "home-duplicate"} {
		assertSerializedCPAWebSearch(t, bySlug[modelID], false)
	}
	for _, modelID := range []string{"home-unknown", "home-custom"} {
		if _, exists := bySlug[modelID]["cpa_capabilities"]; exists {
			t.Fatalf("%s cpa_capabilities = %#v, want omitted", modelID, bySlug[modelID]["cpa_capabilities"])
		}
	}
}

func assertSerializedCPAWebSearch(t *testing.T, model map[string]any, want bool) {
	t.Helper()
	if model == nil {
		t.Fatal("model entry is missing")
	}
	capabilities, ok := model["cpa_capabilities"].(map[string]any)
	if !ok {
		t.Fatalf("model %v cpa_capabilities = %#v, want object", model["slug"], model["cpa_capabilities"])
	}
	if got, ok := capabilities["web_search"].(bool); !ok || got != want {
		t.Fatalf("model %v web_search = %#v, want %v", model["slug"], capabilities["web_search"], want)
	}
}

// modelCatalogUnknownExecutor implements only the mandatory provider contract.
type modelCatalogUnknownExecutor struct{ auth.ProviderExecutor }

type modelCatalogUnsupportedExecutor struct{ auth.ProviderExecutor }

func (modelCatalogUnsupportedExecutor) SupportsApplyPatch() bool { return false }

func TestHomeApplyPatchCapabilityForModel(t *testing.T) {
	entries, errDecodeHomeModels := decodeHomeModels([]byte(`{
		"codex":[{"id":"home-patch-native"},{"id":"home-patch-mixed"},{"id":"home-patch-partial"},{"id":"home-patch-blank"}],
		"home-custom":[{"id":"home-patch-synthetic"},{"id":"home-patch-mixed"}],
		"remote":[{"id":"home-patch-remote"},{"id":"home-patch-partial"}],
		"plugin":[{"id":"home-patch-plugin"}],
		"disabled":[{"id":"home-patch-disabled"}],
		"":[{"id":"home-patch-blank"}]
	}`))
	if errDecodeHomeModels != nil {
		t.Fatalf("decode Home models: %v", errDecodeHomeModels)
	}
	entries = append(entries, homeModelEntry{id: "home-patch-no-routes"})
	manager := auth.NewManager(nil, nil, nil)
	cfg := &proxyconfig.Config{}
	manager.RegisterExecutor(executor.NewCodexAutoExecutor(cfg))
	manager.RegisterExecutor(executor.NewOpenAICompatExecutor("home-custom", cfg))
	manager.RegisterExecutor(modelCatalogUnknownExecutor{executor.NewOpenAICompatExecutor("remote", cfg)})
	manager.RegisterExecutor(modelCatalogUnknownExecutor{executor.NewOpenAICompatExecutor("plugin", cfg)})
	manager.RegisterExecutor(modelCatalogUnsupportedExecutor{executor.NewOpenAICompatExecutor("disabled", cfg)})
	lookup := homeApplyPatchCapabilityForModel(entries, manager)
	for _, tc := range []struct {
		id   string
		want bool
	}{
		{"home-patch-native", true}, {" home-patch-synthetic ", true}, {"home-patch-mixed", true},
		{"home-patch-remote", false}, {"home-patch-plugin", false}, {"home-patch-partial", false},
		{"home-patch-disabled", false}, {"home-patch-no-routes", false}, {"home-patch-blank", false},
		{"team/home-patch-native", false}, {"home-patch-native(high)", false}, {"missing", false},
	} {
		if got := lookup(tc.id); got != tc.want {
			t.Errorf("%q capability = %v, want %v", tc.id, got, tc.want)
		}
		if homeApplyPatchCapabilityForModel(entries, nil)(tc.id) {
			t.Errorf("%q supported without a manager", tc.id)
		}
	}
	// Multiple entries must never overwrite an unknown routing candidate.
	duplicateLookup := homeApplyPatchCapabilityForModel([]homeModelEntry{
		{id: "duplicate", providers: []string{"remote"}},
		{id: "duplicate", providers: []string{"codex"}},
	}, manager)
	if duplicateLookup("duplicate") || homeApplyPatchCapabilityForModel(nil, manager)("home-patch-native") {
		t.Fatal("unknown or absent route was advertised")
	}
}

// newHomeCatalogClient serves the existing Home models protocol without new metadata.
func newHomeCatalogClient(t *testing.T, payload string) *home.Client {
	t.Helper()
	listener, errListen := net.Listen("tcp", "127.0.0.1:0")
	if errListen != nil {
		t.Fatalf("listen: %v", errListen)
	}
	done := make(chan struct{})
	var connections sync.WaitGroup
	go func() {
		defer close(done)
		for {
			conn, errAccept := listener.Accept()
			if errAccept != nil {
				return
			}
			connections.Add(1)
			go func() {
				defer connections.Done()
				defer func() {
					if errClose := conn.Close(); errClose != nil {
						t.Errorf("close Home test connection: %v", errClose)
					}
				}()
				reader := bufio.NewReader(conn)
				for {
					args, errReadRESPArrayOfBulkStrings := readRESPArrayOfBulkStrings(reader)
					if errReadRESPArrayOfBulkStrings != nil || len(args) == 0 {
						return
					}
					response := "+OK\r\n"
					switch strings.ToUpper(string(args[0])) {
					case "HELLO":
						response = "-ERR unknown command 'hello'\r\n"
					case "GET":
						var request struct {
							Type string `json:"type"`
						}
						if len(args) != 2 {
							t.Error("invalid Home GET request")
							return
						}
						if errUnmarshal := json.Unmarshal(args[1], &request); errUnmarshal != nil || request.Type != "models" {
							t.Errorf("unexpected Home request %q: %v", args[1], errUnmarshal)
							return
						}
						response = fmt.Sprintf("$%d\r\n%s\r\n", len(payload), payload)
					}
					if _, errWriteString := io.WriteString(conn, response); errWriteString != nil {
						return
					}
				}
			}()
		}
	}()
	host, portText, errSplitHostPort := net.SplitHostPort(listener.Addr().String())
	if errSplitHostPort != nil {
		t.Fatalf("split Home test address: %v", errSplitHostPort)
	}
	port, errAtoi := strconv.Atoi(portText)
	if errAtoi != nil {
		t.Fatalf("parse Home test port: %v", errAtoi)
	}
	client := home.New(proxyconfig.HomeConfig{Enabled: true, Host: host, Port: port, DisableClusterDiscovery: true})
	t.Cleanup(func() {
		client.Close()
		if errClose := listener.Close(); errClose != nil {
			t.Errorf("close Home test listener: %v", errClose)
		}
		<-done
		connections.Wait()
	})
	return client
}

func TestModelsWithClientVersionHomeApplyPatchRouting(t *testing.T) {
	server := newTestServer(t)
	server.cfg.Home.Enabled = true
	server.cfg.Client.Codex.EnableApplyPatch = true
	// Exercise the production models router without unrelated heartbeat readiness.
	engine := gin.New()
	engine.GET("/v1/models", server.unifiedModelsHandler(nil, nil))
	manager := server.handlers.AuthManager
	cfg := &proxyconfig.Config{}
	manager.RegisterExecutor(executor.NewCodexAutoExecutor(cfg))
	manager.RegisterExecutor(executor.NewOpenAICompatExecutor("home-custom", cfg))
	manager.RegisterExecutor(modelCatalogUnknownExecutor{executor.NewOpenAICompatExecutor("remote", cfg)})
	modelRegistry := registry.GetGlobalRegistry()
	// These local routes intentionally disagree with the Home routing evidence.
	modelRegistry.RegisterClient("home-patch-local-native", "codex", []*registry.ModelInfo{{ID: "home-patch-local-only"}})
	modelRegistry.RegisterClient("home-patch-local-remote", "remote", []*registry.ModelInfo{{ID: "home-patch-supported"}})
	t.Cleanup(func() {
		modelRegistry.UnregisterClient("home-patch-local-native")
		modelRegistry.UnregisterClient("home-patch-local-remote")
	})
	client := newHomeCatalogClient(t, `{
		"codex":[{"id":"gpt-5.5"},{"id":"gpt-reserve"},{"id":"gpt-image-2"},{"id":"home-patch-mixed"},{"id":"home-patch-partial"}],
		"home-custom":[{"id":"home-patch-supported"},{"id":"home-patch-mixed"},{"id":"team/gpt-5.5"}],
		"remote":[{"id":"home-patch-local-only"},{"id":"home-patch-partial"}]
	}`)
	previousHome := home.Current()
	home.SetCurrent(client)
	t.Cleanup(func() { home.SetCurrent(previousHome) })

	for _, version := range []string{"", "0.137.0", "0.153.4", "cpa"} {
		t.Run(version, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/v1/models?client_version="+version, nil)
			req.Header.Set("Authorization", "Bearer test-key")
			recorder := httptest.NewRecorder()
			engine.ServeHTTP(recorder, req)
			if recorder.Code != http.StatusOK {
				t.Fatalf("status = %d, body=%s", recorder.Code, recorder.Body.String())
			}
			if strings.Contains(recorder.Body.String(), "\n") {
				t.Fatal("Home catalog is not compact JSON")
			}
			var response struct {
				Models []map[string]any `json:"models"`
			}
			if errUnmarshal := json.Unmarshal(recorder.Body.Bytes(), &response); errUnmarshal != nil {
				t.Fatalf("decode Home catalog: %v", errUnmarshal)
			}
			entries := make(map[string]map[string]any)
			for _, entry := range response.Models {
				entries[entry["slug"].(string)] = entry
			}
			for _, id := range []string{"gpt-5.5", "gpt-reserve", "home-patch-supported", "home-patch-mixed", "team/gpt-5.5"} {
				if value, present := entries[id]["apply_patch_tool_type"]; !present || value != "freeform" {
					t.Errorf("%s apply_patch_tool_type = %#v (present %v), want freeform", id, value, present)
				}
			}
			for _, id := range []string{"gpt-image-2", "home-patch-local-only", "home-patch-partial"} {
				assertCodexNullableCatalogField(t, entries[id], "apply_patch_tool_type")
			}
			// Home retains its existing template and synthesized protocol restrictions.
			if search, _ := entries["gpt-5.5"]["supports_search_tool"].(bool); !search {
				t.Error("legacy Home template search support changed")
			}
			for _, id := range []string{"home-patch-supported", "home-patch-mixed"} {
				entry := entries[id]
				if entry["supports_search_tool"] != false || entry["prefer_websockets"] != false || len(entry["service_tiers"].([]any)) != 0 {
					t.Errorf("%s unrelated protocol capabilities widened", id)
				}
				assertCodexNullableCatalogField(t, entry, "upgrade")
				assertCodexNullableCatalogField(t, entry, "availability_nux")
			}
		})
	}
	t.Run("missing-manager", func(t *testing.T) {
		server.handlers.AuthManager = nil
		defer func() { server.handlers.AuthManager = manager }()
		recorder := httptest.NewRecorder()
		request := httptest.NewRequest(http.MethodGet, "/v1/models?client_version=0.153.4", nil)
		engine.ServeHTTP(recorder, request)
		if recorder.Code != http.StatusOK {
			t.Fatalf("status = %d, body=%s", recorder.Code, recorder.Body.String())
		}
		var response struct {
			Models []map[string]any `json:"models"`
		}
		if errUnmarshal := json.Unmarshal(recorder.Body.Bytes(), &response); errUnmarshal != nil {
			t.Fatalf("decode Home catalog: %v", errUnmarshal)
		}
		for _, entry := range response.Models {
			assertCodexNullableCatalogField(t, entry, "apply_patch_tool_type")
		}
	})

	request := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
	request.Header.Set("Authorization", "Bearer test-key")
	recorder := httptest.NewRecorder()
	engine.ServeHTTP(recorder, request)
	if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), `"object":"list"`) || strings.Contains(recorder.Body.String(), "apply_patch_tool_type") {
		t.Fatalf("ordinary Home catalog changed: %s", recorder.Body.String())
	}
}

func TestModelsWithClientVersionHomeSearchToolRouting(t *testing.T) {
	for _, tc := range []struct {
		name       string
		toolSearch bool
		want       map[string]bool
	}{
		{
			// Home routing evidence decides search support exactly as local routes do:
			// pure Codex template routes, or pure Claude routes whose every credential
			// supports native tool search. A Home without tool_search fails closed.
			name:       "tool search on",
			toolSearch: true,
			want: map[string]bool{
				"home-claude-only":        true,
				"gpt-5.5":                 true,
				"home-claude-mixed":       false,
				"home-claude-third-party": false,
				"home-claude-old-home":    false,
				"team/gpt-5.5":            false,
			},
		},
		{
			// Off keeps the catalog that predates tool_search: Home routes are not
			// consulted, so template models keep their search flag and Claude
			// models never advertise it.
			name: "tool search off",
			want: map[string]bool{
				"home-claude-only":        false,
				"gpt-5.5":                 true,
				"home-claude-mixed":       false,
				"home-claude-third-party": false,
				"home-claude-old-home":    false,
				"team/gpt-5.5":            true,
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			server := newTestServer(t)
			server.cfg.Home.Enabled = true
			setClaudeToolSearchForTest(t, tc.toolSearch)
			engine := gin.New()
			engine.GET("/v1/models", server.unifiedModelsHandler(nil, nil))
			client := newHomeCatalogClient(t, `{
				"claude":[
					{"id":"home-claude-only","native_capabilities":{"tool_search":true}},
					{"id":"home-claude-mixed","native_capabilities":{"tool_search":true}},
					{"id":"home-claude-third-party","native_capabilities":{"tool_search":false}},
					{"id":"home-claude-old-home"}
				],
				"codex":[{"id":"gpt-5.5"}],
				"home-custom":[{"id":"home-claude-mixed"},{"id":"team/gpt-5.5"}]
			}`)
			previousHome := home.Current()
			home.SetCurrent(client)
			t.Cleanup(func() { home.SetCurrent(previousHome) })

			request := httptest.NewRequest(http.MethodGet, "/v1/models?client_version=0.153.4", nil)
			request.Header.Set("Authorization", "Bearer test-key")
			recorder := httptest.NewRecorder()
			engine.ServeHTTP(recorder, request)
			if recorder.Code != http.StatusOK {
				t.Fatalf("status = %d, body=%s", recorder.Code, recorder.Body.String())
			}
			var response struct {
				Models []map[string]any `json:"models"`
			}
			if errUnmarshal := json.Unmarshal(recorder.Body.Bytes(), &response); errUnmarshal != nil {
				t.Fatalf("decode Home catalog: %v", errUnmarshal)
			}
			want := make(map[string]bool, len(tc.want))
			for slug, wantSearch := range tc.want {
				want[slug] = wantSearch
			}
			for _, entry := range response.Models {
				slug, _ := entry["slug"].(string)
				wantSearch, ok := want[slug]
				if !ok {
					continue
				}
				delete(want, slug)
				if got, _ := entry["supports_search_tool"].(bool); got != wantSearch {
					t.Errorf("%s supports_search_tool = %v, want %v", slug, got, wantSearch)
				}
			}
			if len(want) != 0 {
				t.Fatalf("missing Home catalog entries: %v", want)
			}
		})
	}
}

func TestModelsWithClientVersionApplyPatchRequiresExecutor(t *testing.T) {
	modelRegistry := registry.GetGlobalRegistry()
	clientID := "http-patch-missing-executor"
	modelRegistry.RegisterClient(clientID, "codex", []*registry.ModelInfo{{ID: "gpt-5.5"}, {ID: "http-patch-synthetic"}})
	t.Cleanup(func() { modelRegistry.UnregisterClient(clientID) })
	server := newTestServer(t)
	updatedCfg := *server.cfg
	updatedCfg.Client.Codex.EnableApplyPatch = true
	server.UpdateClients(&updatedCfg)
	for _, version := range []string{"", "0.137.0", "0.153.4", "cpa"} {
		request := httptest.NewRequest(http.MethodGet, "/v1/models?client_version="+version, nil)
		request.Header.Set("Authorization", "Bearer test-key")
		recorder := httptest.NewRecorder()
		server.engine.ServeHTTP(recorder, request)
		if recorder.Code != http.StatusOK {
			t.Fatalf("status = %d", recorder.Code)
		}
		var response struct {
			Models []map[string]any `json:"models"`
		}
		if errUnmarshal := json.Unmarshal(recorder.Body.Bytes(), &response); errUnmarshal != nil {
			t.Fatalf("decode catalog: %v", errUnmarshal)
		}
		for _, entry := range response.Models {
			assertCodexNullableCatalogField(t, entry, "apply_patch_tool_type")
		}
	}
	server.handlers.AuthManager.RegisterExecutor(executor.NewCodexAutoExecutor(&proxyconfig.Config{}))
	request := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
	request.Header.Set("Authorization", "Bearer test-key")
	recorder := httptest.NewRecorder()
	server.engine.ServeHTTP(recorder, request)
	if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), `"object":"list"`) || strings.Contains(recorder.Body.String(), "apply_patch_tool_type") {
		t.Fatalf("ordinary local catalog changed: %s", recorder.Body.String())
	}
}

type revocableServerAccessProvider struct {
	revoked  atomic.Bool
	mu       sync.Mutex
	requests []recordedAccessRequest
}

type recordedAccessRequest struct {
	Method  string
	Path    string
	Headers http.Header
	Body    []byte
}

func (p *revocableServerAccessProvider) Identifier() string {
	return "revocable-test-provider"
}

func (p *revocableServerAccessProvider) Authenticate(_ context.Context, r *http.Request) (*sdkaccess.Result, *sdkaccess.AuthError) {
	var body []byte
	if r != nil && r.Body != nil {
		body, _ = io.ReadAll(r.Body)
		r.Body = io.NopCloser(strings.NewReader(string(body)))
	}
	if r != nil {
		p.mu.Lock()
		p.requests = append(p.requests, recordedAccessRequest{
			Method:  r.Method,
			Path:    r.URL.Path,
			Headers: r.Header.Clone(),
			Body:    append([]byte(nil), body...),
		})
		p.mu.Unlock()
	}
	if r == nil || r.Header.Get("Authorization") != "Bearer key-1" {
		return nil, sdkaccess.NewNoCredentialsError()
	}
	if p.revoked.Load() {
		return nil, sdkaccess.NewInvalidCredentialError()
	}
	return &sdkaccess.Result{
		Provider:  p.Identifier(),
		Principal: "key-1",
	}, nil
}

type serverResponsesWebsocketCaptureExecutor struct {
	calls atomic.Int32
}

func (e *serverResponsesWebsocketCaptureExecutor) Identifier() string {
	return "codex"
}

func (e *serverResponsesWebsocketCaptureExecutor) Execute(context.Context, *auth.Auth, coreexecutor.Request, coreexecutor.Options) (coreexecutor.Response, error) {
	return coreexecutor.Response{}, nil
}

func (e *serverResponsesWebsocketCaptureExecutor) ExecuteStream(_ context.Context, _ *auth.Auth, _ coreexecutor.Request, _ coreexecutor.Options) (*coreexecutor.StreamResult, error) {
	n := e.calls.Add(1)
	chunks := make(chan coreexecutor.StreamChunk, 1)
	chunks <- coreexecutor.StreamChunk{
		Payload: []byte(fmt.Sprintf(`{"type":"response.completed","response":{"id":"resp-%d","output":[]}}`, n)),
	}
	close(chunks)
	return &coreexecutor.StreamResult{Chunks: chunks}, nil
}

func (e *serverResponsesWebsocketCaptureExecutor) Refresh(_ context.Context, a *auth.Auth) (*auth.Auth, error) {
	return a, nil
}

func (e *serverResponsesWebsocketCaptureExecutor) CountTokens(context.Context, *auth.Auth, coreexecutor.Request, coreexecutor.Options) (coreexecutor.Response, error) {
	return coreexecutor.Response{}, nil
}

func (e *serverResponsesWebsocketCaptureExecutor) HttpRequest(context.Context, *auth.Auth, *http.Request) (*http.Response, error) {
	return nil, nil
}

func TestServerResponsesWebsocketReauthorizesEachTurn(t *testing.T) {
	server := newTestServer(t)
	provider := &revocableServerAccessProvider{}
	server.accessManager.SetProviders([]sdkaccess.Provider{provider})

	wsExec := &serverResponsesWebsocketCaptureExecutor{}
	server.handlers.AuthManager.RegisterExecutor(wsExec)
	authEntry := &auth.Auth{
		ID:         "ws-reauth-auth",
		Provider:   "codex",
		Status:     auth.StatusActive,
		Attributes: map[string]string{"websockets": "true"},
	}
	if _, errReg := server.handlers.AuthManager.Register(context.Background(), authEntry); errReg != nil {
		t.Fatalf("Register auth: %v", errReg)
	}
	modelName := "ws-reauth-model"
	registry.GetGlobalRegistry().RegisterClient(authEntry.ID, authEntry.Provider, []*registry.ModelInfo{{ID: modelName}})
	t.Cleanup(func() {
		registry.GetGlobalRegistry().UnregisterClient(authEntry.ID)
	})

	httpServer := httptest.NewServer(server.engine)
	defer httpServer.Close()

	wsURL := "ws" + strings.TrimPrefix(httpServer.URL, "http") + "/v1/responses"
	headers := http.Header{"Authorization": []string{"Bearer key-1"}}
	conn, _, errDial := websocket.DefaultDialer.Dial(wsURL, headers)
	if errDial != nil {
		t.Fatalf("dial websocket: %v", errDial)
	}
	defer func() { _ = conn.Close() }()

	turn1 := []byte(`{"type":"response.create","model":"ws-reauth-model","input":[{"type":"message","role":"user","content":"turn 1"}]}`)
	if errWrite := conn.WriteMessage(websocket.TextMessage, turn1); errWrite != nil {
		t.Fatalf("write turn 1: %v", errWrite)
	}
	_, msg1, errRead1 := conn.ReadMessage()
	if errRead1 != nil {
		t.Fatalf("read turn 1: %v", errRead1)
	}
	if got := ojson.GetBytesNoCopy(msg1, "type").String(); got != "response.completed" {
		t.Fatalf("turn 1 event type = %q, want response.completed (%s)", got, msg1)
	}

	provider.revoked.Store(true)

	// Control: fresh dial with the revoked key must fail at the handshake with 401.
	freshConn, resp, errFreshDial := websocket.DefaultDialer.Dial(wsURL, headers)
	if errFreshDial == nil {
		_ = freshConn.Close()
		t.Fatal("expected fresh dial with revoked key to fail")
	}
	if resp == nil || resp.StatusCode != http.StatusUnauthorized {
		status := 0
		if resp != nil {
			status = resp.StatusCode
		}
		t.Fatalf("fresh dial status = %d, want %d", status, http.StatusUnauthorized)
	}
	if resp.Body != nil {
		_ = resp.Body.Close()
	}

	// Second turn on the already-open socket must be rejected with an error event and not execute.
	turn2 := []byte(`{"type":"response.create","model":"ws-reauth-model","input":[{"type":"message","role":"user","content":"turn 2"}]}`)
	if errWrite := conn.WriteMessage(websocket.TextMessage, turn2); errWrite != nil {
		t.Fatalf("write turn 2: %v", errWrite)
	}
	_, msg2, errRead2 := conn.ReadMessage()
	if errRead2 != nil {
		t.Fatalf("read turn 2: %v", errRead2)
	}
	if got := ojson.GetBytesNoCopy(msg2, "type").String(); got != "error" {
		t.Fatalf("turn 2 event type = %q, want error (%s)", got, msg2)
	}
	if got := int(ojson.GetBytesNoCopy(msg2, "status").Int()); got != http.StatusUnauthorized {
		t.Fatalf("turn 2 error status = %d, want %d (%s)", got, http.StatusUnauthorized, msg2)
	}
	if gotCalls := wsExec.calls.Load(); gotCalls != 1 {
		t.Fatalf("executor calls after revoked turn = %d, want 1", gotCalls)
	}
}