iop/packages/go/config/protocol_profile_test.go
toki 892dc58369 fix(protocol-profile): 요청 경계와 후속 계획을 정리한다
Anthropic 전용 API key가 일반 OpenAI 요청에 적용되지 않도록 경계를 좁히고 잘못된 절대 operation URL을 fail-closed로 거부해야 한다.\n\n완료 Milestone archive와 principal credential 후속 agent-task를 같은 기준선에 맞춘다.
2026-08-01 12:52:56 +09:00

787 lines
28 KiB
Go

package config_test
import (
"os"
"path/filepath"
"strings"
"testing"
"iop/packages/go/config"
)
// --- S01: Overlay resolution, rejection, and legacy alias normalization ---
func TestProtocolProfileOverlayResolution(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"base": {
Driver: config.ProtocolDriverOpenAIChat,
BaseURL: "https://api.example.com/v1",
Operations: map[string]string{
"models": "/models",
},
Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"models", "chat"},
},
"overlay": {
Base: "base",
Operations: map[string]string{
"chat_completions": "/chat/completions",
},
Capabilities: []string{"models", "chat", "streaming"},
},
}
resolved, err := config.ResolveProtocolProfile("overlay", "", catalog)
if err != nil {
t.Fatalf("ResolveProtocolProfile: %v", err)
}
if resolved.ID != "overlay" {
t.Errorf("ID = %q, want %q", resolved.ID, "overlay")
}
if resolved.Driver != config.ProtocolDriverOpenAIChat {
t.Errorf("Driver = %q, want %q", resolved.Driver, config.ProtocolDriverOpenAIChat)
}
if resolved.BaseURL != "https://api.example.com/v1" {
t.Errorf("BaseURL = %q, want inherited value", resolved.BaseURL)
}
if _, ok := resolved.Operations["models"]; !ok {
t.Errorf("expected inherited operation models")
}
if _, ok := resolved.Operations["chat_completions"]; !ok {
t.Errorf("expected overlay operation chat_completions")
}
if len(resolved.Capabilities) != 3 {
t.Errorf("Capabilities = %v, want 3", resolved.Capabilities)
}
}
func TestProtocolProfileOverlayDeepCopyImmutability(t *testing.T) {
baseOps := map[string]string{"models": "/models"}
catalog := map[string]config.ProtocolProfileConf{
"base": {
Driver: config.ProtocolDriverOpenAIChat,
BaseURL: "https://api.example.com/v1",
Operations: baseOps,
Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"models", "chat"},
},
"overlay": {
Base: "base",
Operations: map[string]string{"chat_completions": "/chat/completions"},
},
}
resolved, err := config.ResolveProtocolProfile("overlay", "", catalog)
if err != nil {
t.Fatalf("ResolveProtocolProfile: %v", err)
}
// Mutate the resolved snapshot and verify the catalog is unaffected.
resolved.Operations["injected"] = "/injected"
if _, ok := catalog["base"].Operations["injected"]; ok {
t.Error("mutating resolved profile affected catalog base")
}
if _, ok := catalog["overlay"].Operations["injected"]; ok {
t.Error("mutating resolved profile affected catalog overlay")
}
}
func TestProtocolProfileOverlayRejected(t *testing.T) {
t.Run("cycle", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"a": {Base: "b", Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
"b": {Base: "a", Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://b/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
}
_, err := config.ResolveProtocolProfile("a", "", catalog)
if err == nil || !strings.Contains(err.Error(), "cycle") {
t.Fatalf("expected cycle error, got %v", err)
}
})
t.Run("unknown_base", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"overlay": {Base: "nonexistent", Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
}
_, err := config.ResolveProtocolProfile("overlay", "", catalog)
if err == nil || !strings.Contains(err.Error(), "unknown") {
t.Fatalf("expected unknown base error, got %v", err)
}
})
t.Run("conflicting_driver", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"base": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
"overlay": {Base: "base", Driver: config.ProtocolDriverAnthropicMessages, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m", "messages": "/msg"}, Capabilities: []string{"messages"}},
}
_, err := config.ResolveProtocolProfile("overlay", "", catalog)
if err == nil || !strings.Contains(err.Error(), "driver") {
t.Fatalf("expected driver conflict error, got %v", err)
}
})
t.Run("invalid_operation", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"messages": "/msg"}, Capabilities: []string{"chat"}},
}
_, err := config.ResolveProtocolProfile("bad", "", catalog)
if err == nil || !strings.Contains(err.Error(), "messages") {
t.Fatalf("expected invalid operation error, got %v", err)
}
})
t.Run("invalid_auth_header", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m", "chat_completions": "/chat"}, Auth: config.ProtocolAuthConf{Header: ""}, Capabilities: []string{"models", "chat"}},
}
_, err := config.ResolveProtocolProfile("bad", "", catalog)
if err == nil || !strings.Contains(err.Error(), "auth") {
t.Fatalf("expected auth validation error, got %v", err)
}
})
t.Run("empty_capabilities", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{}},
}
_, err := config.ResolveProtocolProfile("bad", "", catalog)
if err == nil || !strings.Contains(err.Error(), "capabilities") {
t.Fatalf("expected capabilities error, got %v", err)
}
})
t.Run("malformed_absolute_url", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "://not-a-url"}, Capabilities: []string{"chat"}},
}
_, err := config.ResolveProtocolProfile("bad", "", catalog)
if err == nil || !strings.Contains(err.Error(), "operation") {
t.Fatalf("expected URL validation error, got %v", err)
}
})
t.Run("absolute_url_without_host", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"bad": {
Driver: config.ProtocolDriverOpenAIChat,
BaseURL: "https://a/v1",
Operations: map[string]string{"chat_completions": "https:///v1/chat/completions"},
Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"chat"},
},
}
_, err := config.ResolveProtocolProfile("bad", "", catalog)
if err == nil || !strings.Contains(err.Error(), "absolute http(s) URL") {
t.Fatalf("expected absolute URL host validation error, got %v", err)
}
})
t.Run("relative_path_no_leading_slash", func(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "models"}, Capabilities: []string{"chat"}},
}
_, err := config.ResolveProtocolProfile("bad", "", catalog)
if err == nil || !strings.Contains(err.Error(), "leading") {
t.Fatalf("expected leading slash error, got %v", err)
}
})
}
func TestLegacyProviderAliasProfileNormalization(t *testing.T) {
cases := []struct {
legacyType string
wantID string
}{
{"vllm", "openai"},
{"openai_api", "openai"},
{"openai_compat", "openai"},
{"vllm-mlx", "openai"},
{"lemonade", "openai"},
{"sglang", "openai"},
{"seulgivibe_claude", "seulgi_messages"},
{"seulgivibe_openai", "seulgi_chat"},
{"ollama", ""},
{"cli", ""},
{"unknown_type", ""},
}
for _, c := range cases {
got := config.LegacyProviderTypeToProfile(c.legacyType)
if got != c.wantID {
t.Errorf("LegacyProviderTypeToProfile(%q) = %q, want %q", c.legacyType, got, c.wantID)
}
}
}
func TestLegacyProviderAliasResolvesToBuiltIn(t *testing.T) {
// No explicit selector: legacy type should normalize to the built-in.
resolved, err := config.ResolveProtocolProfile("", "vllm", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatalf("ResolveProtocolProfile: %v", err)
}
if resolved.ID != "openai" {
t.Errorf("ID = %q, want %q", resolved.ID, "openai")
}
if resolved.Driver != config.ProtocolDriverOpenAIChat {
t.Errorf("Driver = %q, want %q", resolved.Driver, config.ProtocolDriverOpenAIChat)
}
}
func TestExplicitProfilePrecedenceOverLegacyType(t *testing.T) {
// Explicit selector should win over legacy type mapping.
resolved, err := config.ResolveProtocolProfile("anthropic", "vllm", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatalf("ResolveProtocolProfile: %v", err)
}
if resolved.ID != "anthropic" {
t.Errorf("ID = %q, want %q", resolved.ID, "anthropic")
}
if resolved.Driver != config.ProtocolDriverAnthropicMessages {
t.Errorf("Driver = %q, want %q", resolved.Driver, config.ProtocolDriverAnthropicMessages)
}
}
func TestProtocolProfileMultiLevelOverlay(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"l1": {
Driver: config.ProtocolDriverOpenAIChat,
BaseURL: "https://l1/v1",
Operations: map[string]string{"models": "/models"},
Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"models", "chat"},
},
"l2": {
Base: "l1",
BaseURL: "https://l2/v1",
Operations: map[string]string{"chat_completions": "/chat/completions"},
},
"l3": {
Base: "l2",
Capabilities: []string{"models", "chat", "streaming"},
},
}
resolved, err := config.ResolveProtocolProfile("l3", "", catalog)
if err != nil {
t.Fatalf("ResolveProtocolProfile: %v", err)
}
if resolved.BaseURL != "https://l2/v1" {
t.Errorf("BaseURL = %q, want %q", resolved.BaseURL, "https://l2/v1")
}
if _, ok := resolved.Operations["models"]; !ok {
t.Error("expected inherited operation models")
}
if _, ok := resolved.Operations["chat_completions"]; !ok {
t.Error("expected inherited operation chat_completions")
}
if len(resolved.Capabilities) != 3 {
t.Errorf("Capabilities = %v, want 3", resolved.Capabilities)
}
}
func TestProtocolProfileCustomOverlay(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"openai": config.BuiltInProtocolProfiles["openai"],
"my-proxy": {
Base: "openai",
BaseURL: "https://my-proxy.example.invalid/v1",
},
}
resolved, err := config.ResolveProtocolProfile("my-proxy", "", catalog)
if err != nil {
t.Fatalf("ResolveProtocolProfile: %v", err)
}
if resolved.BaseURL != "https://my-proxy.example.invalid/v1" {
t.Errorf("BaseURL = %q, want overridden value", resolved.BaseURL)
}
if resolved.ID != "my-proxy" {
t.Errorf("ID = %q, want %q", resolved.ID, "my-proxy")
}
}
// --- S03: Built-in catalog admission ---
func TestBuiltInProtocolProfileCatalog(t *testing.T) {
expectedIDs := []string{
"anthropic", "gemini", "glm", "grok", "kimi", "minimax_chat", "minimax_messages",
"mimo_chat", "mimo_messages",
"openai", "seulgi_chat", "seulgi_messages",
}
for _, id := range expectedIDs {
if _, ok := config.BuiltInProtocolProfiles[id]; !ok {
t.Fatalf("built-in profile %q not found", id)
}
resolved, err := config.ResolveProtocolProfile(id, "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatalf("ResolveProtocolProfile(%q): %v", id, err)
}
if resolved.ID != id {
t.Errorf("profile %q: ID = %q, want %q", id, resolved.ID, id)
}
if resolved.Base != "" {
t.Errorf("profile %q: Base should be empty in concrete snapshot, got %q", id, resolved.Base)
}
}
// Verify all expected IDs are present
for _, id := range expectedIDs {
if _, ok := config.BuiltInProtocolProfiles[id]; !ok {
t.Errorf("expected built-in profile %q", id)
}
}
}
func TestBuiltInProtocolProfileOperations(t *testing.T) {
t.Run("openai_has_chat_completions", func(t *testing.T) {
r, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
if _, ok := r.Operations["chat_completions"]; !ok {
t.Error("openai should declare chat_completions")
}
if _, ok := r.Operations["responses"]; !ok {
t.Error("openai should declare responses")
}
})
t.Run("anthropic_has_messages", func(t *testing.T) {
r, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
if _, ok := r.Operations["messages"]; !ok {
t.Error("anthropic should declare messages")
}
if _, ok := r.Operations["chat_completions"]; ok {
t.Error("anthropic should NOT declare chat_completions")
}
})
t.Run("seulgi_messages_has_messages", func(t *testing.T) {
r, err := config.ResolveProtocolProfile("seulgi_messages", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
if _, ok := r.Operations["messages"]; !ok {
t.Error("seulgi_messages should declare messages")
}
})
t.Run("seulgi_chat_has_chat_completions", func(t *testing.T) {
r, err := config.ResolveProtocolProfile("seulgi_chat", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
if _, ok := r.Operations["chat_completions"]; !ok {
t.Error("seulgi_chat should declare chat_completions")
}
})
}
func TestBuiltInProtocolProfileURLs(t *testing.T) {
t.Run("anthropic_absolute_path", func(t *testing.T) {
r, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
url, err := r.ResolveOperationURL("messages")
if err != nil {
t.Fatal(err)
}
if !strings.HasSuffix(url, "/v1/messages") {
t.Errorf("expected /v1/messages suffix, got %q", url)
}
})
t.Run("gemini_v1beta_openai", func(t *testing.T) {
r, err := config.ResolveProtocolProfile("gemini", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
url, err := r.ResolveOperationURL("chat_completions")
if err != nil {
t.Fatal(err)
}
if !strings.HasSuffix(url, "/v1beta/openai/chat/completions") {
t.Errorf("expected /v1beta/openai/chat/completions suffix, got %q", url)
}
})
t.Run("glm_api_paas_v4", func(t *testing.T) {
r, err := config.ResolveProtocolProfile("glm", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
url, err := r.ResolveOperationURL("chat_completions")
if err != nil {
t.Fatal(err)
}
if !strings.HasSuffix(url, "/api/paas/v4/chat/completions") {
t.Errorf("expected /api/paas/v4/chat/completions suffix, got %q", url)
}
})
}
func TestProtocolProfileUnsupportedOperationRejected(t *testing.T) {
r, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
_, err = r.ResolveOperationURL("chat_completions")
if err == nil || !strings.Contains(err.Error(), "not supported") {
t.Fatalf("expected unsupported operation error, got %v", err)
}
}
func TestProtocolProfileResolveOperationURLAbsolute(t *testing.T) {
catalog := map[string]config.ProtocolProfileConf{
"custom": {
Driver: config.ProtocolDriverOpenAIChat,
BaseURL: "https://base.example.com/v1",
Operations: map[string]string{
"models": "https://override.example.com/v1/models",
"chat_completions": "/chat/completions",
},
Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"models", "chat"},
},
}
r, err := config.ResolveProtocolProfile("custom", "", catalog)
if err != nil {
t.Fatal(err)
}
url, err := r.ResolveOperationURL("models")
if err != nil {
t.Fatal(err)
}
if url != "https://override.example.com/v1/models" {
t.Errorf("expected absolute URL unchanged, got %q", url)
}
}
func TestProtocolProfileClone(t *testing.T) {
r, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatal(err)
}
clone := r.Clone()
if clone.ID != r.ID {
t.Errorf("ID mismatch: %q vs %q", clone.ID, r.ID)
}
clone.Operations["injected"] = "/injected"
if _, ok := r.Operations["injected"]; ok {
t.Error("clone mutation affected original")
}
}
func TestProtocolProfileProviderEndpointOverlay(t *testing.T) {
cfg, err := loadProtocolProfileYAML(t, `
nodes:
- id: node-profile
providers:
- id: provider-profile
type: vllm
category: api
endpoint: http://127.0.0.1:18080/v1/
base_url: http://127.0.0.1:18081/ignored
models: [upstream-model]
models:
- id: canonical-model
providers:
provider-profile: upstream-model
`)
if err != nil {
t.Fatalf("LoadEdge: %v", err)
}
profile := cfg.Nodes[0].Providers[0].RuntimeProfile
if profile == nil || profile.BaseURL != "http://127.0.0.1:18080/v1" {
t.Fatalf("runtime profile base_url = %#v", profile)
}
url, err := profile.ResolveOperationURL(string(config.OperationChatCompletions))
if err != nil || url != "http://127.0.0.1:18080/v1/chat/completions" {
t.Fatalf("resolved chat URL = %q, err=%v", url, err)
}
fresh := config.BuiltInProtocolProfileCatalog()["openai"]
if fresh.BaseURL != "https://api.openai.com/v1" {
t.Fatalf("provider endpoint mutated built-in: %q", fresh.BaseURL)
}
rootCfg, err := loadProtocolProfileYAML(t, `
nodes:
- id: node-root
providers:
- id: provider-root
type: lemonade
category: api
endpoint: http://127.0.0.1:18082
models: [upstream-model]
models:
- id: root-model
providers: {provider-root: upstream-model}
`)
if err != nil {
t.Fatalf("LoadEdge root endpoint: %v", err)
}
rootProfile := rootCfg.Nodes[0].Providers[0].RuntimeProfile
if rootProfile == nil || rootProfile.BaseURL != "http://127.0.0.1:18082/v1" {
t.Fatalf("root endpoint did not retain profile prefix: %#v", rootProfile)
}
}
func TestProtocolProfileCatalogCollisionRejected(t *testing.T) {
_, err := loadProtocolProfileYAML(t, `
protocol_profiles:
openai:
driver: openai_chat
base_url: https://collision.example.invalid/v1
operations: {chat_completions: /chat/completions}
auth: {header: Authorization, scheme: Bearer}
capabilities: [chat]
`)
if err == nil || !strings.Contains(err.Error(), "reserved") {
t.Fatalf("expected reserved built-in collision error, got %v", err)
}
}
func TestProtocolProfileCapabilityMatrixRejected(t *testing.T) {
base := config.ProtocolProfileConf{
Driver: config.ProtocolDriverOpenAIChat,
BaseURL: "https://matrix.example.invalid/v1",
Operations: map[string]string{"chat_completions": "/chat/completions"},
Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"chat"},
}
for _, tc := range []struct {
name string
edit func(*config.ProtocolProfileConf)
want string
}{
{name: "unknown_capability", edit: func(p *config.ProtocolProfileConf) { p.Capabilities = append(p.Capabilities, "telepathy") }, want: "not recognized"},
{name: "operation_without_capability", edit: func(p *config.ProtocolProfileConf) { p.Operations["models"] = "/models" }, want: "requires capability"},
{name: "wrong_driver_operation", edit: func(p *config.ProtocolProfileConf) { p.Operations["messages"] = "/messages" }, want: "not supported"},
{name: "raw_header_with_scheme", edit: func(p *config.ProtocolProfileConf) {
p.Auth = config.ProtocolAuthConf{Header: "X-Api-Key", Scheme: "Bearer"}
}, want: "raw credential"},
{name: "authorization_without_bearer", edit: func(p *config.ProtocolProfileConf) { p.Auth.Scheme = "Token" }, want: "Bearer"},
} {
t.Run(tc.name, func(t *testing.T) {
profile := base
profile.Operations = map[string]string{"chat_completions": "/chat/completions"}
profile.Capabilities = append([]string(nil), base.Capabilities...)
tc.edit(&profile)
_, err := config.ResolveProtocolProfile("bad", "", map[string]config.ProtocolProfileConf{"bad": profile})
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("expected %q error, got %v", tc.want, err)
}
})
}
}
func TestProtocolProfileChatOnlyTokenCounter(t *testing.T) {
base := func(profile, counter string) string {
return `
nodes:
- id: node-counter
providers:
- id: provider-counter
type: openai_api
category: api
profile: ` + profile + `
endpoint: http://127.0.0.1:18080/v1
models: [upstream-model]
models:
- id: canonical-model
providers:
provider-counter: upstream-model
token_counter:
` + counter
}
if _, err := loadProtocolProfileYAML(t, base("openai", " mode: deterministic\n")); err != nil {
t.Fatalf("Chat deterministic counter: %v", err)
}
for _, tc := range []struct {
name, profile, counter, want string
}{
{name: "messages", profile: "anthropic", counter: " mode: deterministic\n", want: "Chat-only"},
{name: "estimate_range", profile: "openai", counter: " mode: estimate\n per_1k_input: 1001\n", want: "between 1 and 1000"},
{name: "deterministic_parameter", profile: "openai", counter: " mode: deterministic\n per_1k_input: 250\n", want: "must be 0"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := loadProtocolProfileYAML(t, base(tc.profile, tc.counter))
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("expected %q error, got %v", tc.want, err)
}
})
}
_, err := loadProtocolProfileYAML(t, `
protocol_profiles:
responses_custom:
driver: openai_responses
base_url: https://responses.example.invalid/v1
operations: {responses: /responses}
auth: {header: Authorization, scheme: Bearer}
capabilities: [responses]
nodes:
- id: node-responses-counter
providers:
- id: responses-counter
type: openai_api
category: api
profile: responses_custom
models: [upstream-response]
models:
- id: canonical-response
providers: {responses-counter: upstream-response}
token_counter: {mode: deterministic}
`)
if err == nil || !strings.Contains(err.Error(), "Chat-only") {
t.Fatalf("expected Responses profile token-counter rejection, got %v", err)
}
}
func TestBuiltInProtocolProfileCatalogImmutable(t *testing.T) {
first := config.BuiltInProtocolProfileCatalog()
openai := first["openai"]
openai.Operations[string(config.OperationChatCompletions)] = "/mutated"
openai.Capabilities[0] = "mutated"
first["openai"] = openai
fresh := config.BuiltInProtocolProfileCatalog()["openai"]
if fresh.Operations[string(config.OperationChatCompletions)] != "/chat/completions" || fresh.Capabilities[0] == "mutated" {
t.Fatalf("built-in catalog mutation leaked: %+v", fresh)
}
}
// TestProtocolProfileHeaderValidation covers the RFC 9110 field-name token
// grammar independently from Go's permissive MIME canonicalization helpers.
func TestProtocolProfileHeaderValidation(t *testing.T) {
validProfile := func(header string) config.ProtocolProfileConf {
return config.ProtocolProfileConf{
Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://example.invalid/v1",
Operations: map[string]string{"chat_completions": "/chat/completions"},
Auth: config.ProtocolAuthConf{Header: header}, Capabilities: []string{"chat"},
}
}
for _, tc := range []struct {
name, header string
valid bool
}{
{name: "token_punctuation", header: "X!#$%&'*+-.^_`|~Key", valid: true},
{name: "empty", header: ""},
{name: "leading_space", header: " X-Key"},
{name: "trailing_space", header: "X-Key "},
{name: "embedded_space", header: "X Key"},
{name: "colon", header: "X:Key"},
{name: "tab", header: "X\tKey"},
{name: "control", header: "X\x01Key"},
{name: "non_ascii", header: "X-Kéy"},
{name: "separator", header: "X(Key)"},
} {
t.Run(tc.name, func(t *testing.T) {
profile := validProfile(tc.header)
_, err := config.ResolveProtocolProfile("header-test", "", map[string]config.ProtocolProfileConf{"header-test": profile})
if tc.valid && err != nil {
t.Fatalf("valid header rejected: %v", err)
}
if !tc.valid && (err == nil || !strings.Contains(err.Error(), "auth")) {
t.Fatalf("invalid header %q was not rejected: %v", tc.header, err)
}
})
}
}
// TestProtocolProfileBuiltInAuthMatrix is intentionally literal so catalog
// drift cannot update the expected values by sharing production data. MiniMax
// and MiMo Anthropic-compatible auth was rechecked against official provider
// documentation on 2026-08-01:
// https://platform.minimax.io/docs/token-plan/other-tools
// https://mimo.mi.com/docs/en-US/quick-start/summary/first-api-call
func TestProtocolProfileBuiltInAuthMatrix(t *testing.T) {
type wantAuth struct{ header, scheme string }
want := map[string]wantAuth{
"openai": {"Authorization", "Bearer"}, "gemini": {"Authorization", "Bearer"},
"anthropic": {"x-api-key", ""}, "glm": {"Authorization", "Bearer"},
"kimi": {"Authorization", "Bearer"}, "minimax_chat": {"Authorization", "Bearer"},
"minimax_messages": {"Authorization", "Bearer"}, "mimo_chat": {"Authorization", "Bearer"},
"mimo_messages": {"api-key", ""}, "grok": {"Authorization", "Bearer"},
"seulgi_chat": {"Authorization", "Bearer"}, "seulgi_messages": {"x-api-key", ""},
}
if len(config.BuiltInProtocolProfiles) != len(want) {
t.Fatalf("built-in profile count = %d, want %d", len(config.BuiltInProtocolProfiles), len(want))
}
for id, expected := range want {
resolved, err := config.ResolveProtocolProfile(id, "", config.BuiltInProtocolProfiles)
if err != nil {
t.Fatalf("ResolveProtocolProfile(%q): %v", id, err)
}
if resolved.Auth.Header != expected.header || resolved.Auth.Scheme != expected.scheme {
t.Errorf("%s auth = {%q %q}, want {%q %q}", id, resolved.Auth.Header, resolved.Auth.Scheme, expected.header, expected.scheme)
}
}
}
func TestProtocolProfileAdapterBackingResolution(t *testing.T) {
cfg, err := loadProtocolProfileYAML(t, `
nodes:
- id: node-backed
adapters:
openai_compat_instances:
- name: upstream
enabled: true
provider: lemonade
endpoint: http://127.0.0.1:18080
providers:
- id: profile-provider
type: lemonade
category: api
adapter: upstream
profile: openai
models: [served]
capacity: 1
models:
- id: canonical
providers: {profile-provider: served}
`)
if err != nil {
t.Fatalf("LoadEdge: %v", err)
}
profile := cfg.Nodes[0].Providers[0].RuntimeProfile
if profile == nil || profile.BaseURL != "http://127.0.0.1:18080/v1" {
t.Fatalf("backing endpoint was not applied: %#v", profile)
}
}
func TestProtocolProfileAdapterBackingRejected(t *testing.T) {
for _, tc := range []struct {
name, adapters, ref, want string
}{
{name: "missing", ref: "missing", adapters: " openai_compat_instances: []", want: "missing"},
{name: "disabled", ref: "backing", adapters: " openai_compat_instances:\n - {name: backing, enabled: false, endpoint: http://127.0.0.1:1}", want: "disabled"},
{name: "ambiguous", ref: "backing", adapters: " vllm_instances:\n - {name: backing, enabled: true, endpoint: http://127.0.0.1:1}\n openai_compat_instances:\n - {name: backing, enabled: true, endpoint: http://127.0.0.1:2}", want: "ambiguous"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := loadProtocolProfileYAML(t, `
nodes:
- id: node-bad-backing
adapters:
`+tc.adapters+`
providers:
- id: profile-provider
type: openai_compat
category: api
adapter: `+tc.ref+`
profile: openai
models: [served]
capacity: 1
`)
if err == nil || !strings.Contains(err.Error(), tc.want) || !strings.Contains(err.Error(), "profile-provider") || !strings.Contains(err.Error(), tc.ref) {
t.Fatalf("expected contextual %q error, got %v", tc.want, err)
}
})
}
}
func loadProtocolProfileYAML(t *testing.T, contents string) (*config.EdgeConfig, error) {
t.Helper()
path := filepath.Join(t.TempDir(), "edge.yaml")
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
return config.LoadEdge(path)
}