iop/packages/go/config/edge_openai_config_test.go
toki 01dc2ef78b refactor: readability baseline 및 테스트 구조 개선
- agent-readable-repository-refactor 완료: 기존 테스트 파일 아카이브 이동
- 새 테스트 파일 추가 (edge, node, client, config, readability)
- readability_audit 스크립트 및 baseline 추가
- roadmap/SDD 문서 갱신
- agent-client/pi/extensions/openai-sampling-parameters 추가
2026-07-17 16:02:12 +09:00

935 lines
28 KiB
Go

package config_test
import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"iop/packages/go/config"
)
func TestLoadEdge_EdgeIdentity(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := "server:\n listen: \"0.0.0.0:9090\"\nedge:\n id: \"edge-dgx-group\"\n name: \"DGX Group\"\n"
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Edge.ID != "edge-dgx-group" {
t.Fatalf("expected edge.id=%q, got %q", "edge-dgx-group", cfg.Edge.ID)
}
if cfg.Edge.Name != "DGX Group" {
t.Fatalf("expected edge.name=%q, got %q", "DGX Group", cfg.Edge.Name)
}
}
func TestLoadEdge_EdgeIdentityEmptyByDefault(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
if err := os.WriteFile(f, []byte("server:\n listen: \"0.0.0.0:9090\"\n"), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Edge.ID != "" || cfg.Edge.Name != "" {
t.Fatalf("expected empty edge identity by default, got id=%q name=%q", cfg.Edge.ID, cfg.Edge.Name)
}
}
func TestLoadEdge_OpenAIDefaults(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
if err := os.WriteFile(f, []byte("server:\n listen: \"0.0.0.0:9090\"\n"), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.OpenAI.Enabled {
t.Fatal("expected openai.enabled=false by default")
}
if cfg.OpenAI.Listen != "0.0.0.0:18081" {
t.Fatalf("expected openai.listen default, got %q", cfg.OpenAI.Listen)
}
if cfg.OpenAI.BearerToken != "" {
t.Fatalf("expected openai.bearer_token empty by default, got %q", cfg.OpenAI.BearerToken)
}
if cfg.OpenAI.ProviderAuth.Enabled {
t.Fatal("expected openai.provider_auth.enabled=false by default")
}
if cfg.OpenAI.ProviderAuth.FromHeader != "" || cfg.OpenAI.ProviderAuth.TargetHeader != "" || cfg.OpenAI.ProviderAuth.Scheme != "" || cfg.OpenAI.ProviderAuth.Required {
t.Fatalf("expected openai.provider_auth fields empty/false by default, got %+v", cfg.OpenAI.ProviderAuth)
}
if cfg.OpenAI.Adapter != "ollama" {
t.Fatalf("expected openai.adapter=%q, got %q", "ollama", cfg.OpenAI.Adapter)
}
if cfg.OpenAI.SessionID != "openai" {
t.Fatalf("expected openai.session_id=%q, got %q", "openai", cfg.OpenAI.SessionID)
}
if cfg.OpenAI.TimeoutSec != 120 {
t.Fatalf("expected openai.timeout_sec=120, got %d", cfg.OpenAI.TimeoutSec)
}
if !cfg.OpenAI.StrictOutput {
t.Fatal("expected openai.strict_output=true")
}
if cfg.OpenAI.StrictStreamBuffer {
t.Fatal("expected openai.strict_stream_buffer=false")
}
if cfg.Metrics.Port != 19092 {
t.Fatalf("expected metrics.port=19092, got %d", cfg.Metrics.Port)
}
if cfg.LongContextThresholdTokens != 100000 {
t.Fatalf("expected long_context_threshold_tokens=100000 by default, got %d", cfg.LongContextThresholdTokens)
}
}
func TestLoadEdge_LongContextThresholdOverride(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
long_context_threshold_tokens: 120000
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.LongContextThresholdTokens != 120000 {
t.Fatalf("expected long_context_threshold_tokens=120000, got %d", cfg.LongContextThresholdTokens)
}
}
func TestLoadEdge_LongContextThresholdRejectsNonPositive(t *testing.T) {
for _, tc := range []struct {
name string
value int
}{
{name: "zero", value: 0},
{name: "negative", value: -1},
} {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := fmt.Sprintf(`
server:
listen: "0.0.0.0:9090"
long_context_threshold_tokens: %d
`, tc.value)
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for non-positive long_context_threshold_tokens")
}
if !strings.Contains(err.Error(), "long_context_threshold_tokens must be positive") {
t.Fatalf("expected error mentioning positive threshold, got %v", err)
}
})
}
}
func TestLoadEdge_OpenAIOverride(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
listen: "127.0.0.1:8088"
bearer_token: "secret-token"
node: "node0"
adapter: "ollama"
target: "llama-test"
models:
- "llama-test"
session_id: "cline"
timeout_sec: 45
strict_output: false
strict_stream_buffer: true
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if !cfg.OpenAI.Enabled {
t.Fatal("expected openai.enabled=true")
}
if cfg.OpenAI.Listen != "127.0.0.1:8088" || cfg.OpenAI.NodeRef != "node0" {
t.Fatalf("unexpected openai routing config: %+v", cfg.OpenAI)
}
if cfg.OpenAI.BearerToken != "secret-token" {
t.Fatalf("expected openai.bearer_token=%q, got %q", "secret-token", cfg.OpenAI.BearerToken)
}
if cfg.OpenAI.Target != "llama-test" || len(cfg.OpenAI.Models) != 1 || cfg.OpenAI.Models[0] != "llama-test" {
t.Fatalf("unexpected openai model config: %+v", cfg.OpenAI)
}
if cfg.OpenAI.SessionID != "cline" || cfg.OpenAI.TimeoutSec != 45 {
t.Fatalf("unexpected openai execution config: %+v", cfg.OpenAI)
}
if cfg.OpenAI.StrictOutput {
t.Fatalf("unexpected openai strict output config: %+v", cfg.OpenAI)
}
if !cfg.OpenAI.StrictStreamBuffer {
t.Fatalf("unexpected openai strict stream buffer config: %+v", cfg.OpenAI)
}
}
func TestLoadEdge_OpenAIProviderAuthEnabledDefaults(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
provider_auth:
enabled: true
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
auth := cfg.OpenAI.ProviderAuth
if !auth.Enabled {
t.Fatal("expected provider_auth.enabled=true")
}
if auth.FromHeader != "X-IOP-Provider-Authorization" {
t.Fatalf("expected default from_header, got %q", auth.FromHeader)
}
if auth.TargetHeader != "Authorization" {
t.Fatalf("expected default target_header, got %q", auth.TargetHeader)
}
if auth.Scheme != "Bearer" {
t.Fatalf("expected default scheme Bearer, got %q", auth.Scheme)
}
if !auth.Required {
t.Fatal("expected required=true by default when provider_auth is enabled")
}
}
func TestLoadEdge_OpenAIProviderAuthOverride(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
provider_auth:
enabled: true
from_header: "X-Seulgivibe-Token"
target_header: "X-Provider-Authorization"
scheme: "Token"
required: false
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
auth := cfg.OpenAI.ProviderAuth
if !auth.Enabled {
t.Fatal("expected provider_auth.enabled=true")
}
if auth.FromHeader != "X-Seulgivibe-Token" || auth.TargetHeader != "X-Provider-Authorization" || auth.Scheme != "Token" || auth.Required {
t.Fatalf("unexpected provider_auth override: %+v", auth)
}
}
func TestLoadEdge_OpenAIProviderAuthRejectsBlankHeaders(t *testing.T) {
for _, tc := range []struct {
name string
field string
wantMsg string
}{
{name: "from_header", field: "from_header", wantMsg: "from_header"},
{name: "target_header", field: "target_header", wantMsg: "target_header"},
} {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := fmt.Sprintf(`
server:
listen: "0.0.0.0:9090"
openai:
provider_auth:
enabled: true
%s: " "
`, tc.field)
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected blank provider_auth header error")
}
if !strings.Contains(err.Error(), tc.wantMsg) {
t.Fatalf("expected error mentioning %q, got %v", tc.wantMsg, err)
}
})
}
}
func TestNormalizeProviderTypeOpenAICompatibleAliases(t *testing.T) {
cases := []struct {
name string
in string
want string
}{
{name: "openai compat", in: "openai_compat", want: "openai_compat"},
{name: "openai api", in: "openai_api", want: "openai_compat"},
{name: "vllm", in: "vllm", want: "openai_compat"},
{name: "vllm mlx", in: "vllm-mlx", want: "openai_compat"},
{name: "lemonade", in: "lemonade", want: "openai_compat"},
{name: "sglang", in: "sglang", want: "openai_compat"},
{name: "seulgivibe claude", in: "seulgivibe_claude", want: "openai_compat"},
{name: "seulgivibe openai", in: "seulgivibe_openai", want: "openai_compat"},
{name: "normalized case and space", in: " VLLM-MLX ", want: "openai_compat"},
{name: "ollama", in: "ollama", want: "ollama"},
{name: "cli", in: "cli", want: "cli"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := config.NormalizeProviderType(tc.in); got != tc.want {
t.Fatalf("NormalizeProviderType(%q) = %q, want %q", tc.in, got, tc.want)
}
})
}
}
func TestLoadEdge_OpenAIPrincipalTokens(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
principal_tokens:
- token_ref: "iop-tok-alice"
token_hash_sha256: "` + strings.Repeat("a1", 32) + `"
principal_ref: "user:alice"
principal_alias: "alice"
- token_ref: "iop-tok-bob"
token_hash_sha256: "` + strings.Repeat("b2", 32) + `"
principal_ref: "user:bob"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.OpenAI.PrincipalTokens) != 2 {
t.Fatalf("expected 2 principal_tokens, got %d", len(cfg.OpenAI.PrincipalTokens))
}
first := cfg.OpenAI.PrincipalTokens[0]
if first.TokenRef != "iop-tok-alice" || first.PrincipalRef != "user:alice" || first.PrincipalAlias != "alice" {
t.Fatalf("unexpected first principal token: %+v", first)
}
if first.TokenHashSHA256 != strings.Repeat("a1", 32) {
t.Fatalf("unexpected token hash: %q", first.TokenHashSHA256)
}
second := cfg.OpenAI.PrincipalTokens[1]
if second.TokenRef != "iop-tok-bob" || second.PrincipalRef != "user:bob" || second.PrincipalAlias != "" {
t.Fatalf("unexpected second principal token: %+v", second)
}
}
func TestLoadEdge_OpenAIPrincipalTokensAllowMultipleTokensPerPrincipal(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
principal_tokens:
- token_ref: "alice-app-a"
token_hash_sha256: "` + strings.Repeat("a1", 32) + `"
principal_ref: "user:alice"
principal_alias: "alice"
- token_ref: "alice-app-b"
token_hash_sha256: "` + strings.Repeat("b2", 32) + `"
principal_ref: "user:alice"
principal_alias: "alice"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.OpenAI.PrincipalTokens) != 2 {
t.Fatalf("expected 2 principal_tokens, got %d", len(cfg.OpenAI.PrincipalTokens))
}
if cfg.OpenAI.PrincipalTokens[0].PrincipalRef != cfg.OpenAI.PrincipalTokens[1].PrincipalRef {
t.Fatalf("expected both tokens to map to the same principal: %+v", cfg.OpenAI.PrincipalTokens)
}
if cfg.OpenAI.PrincipalTokens[0].TokenRef == cfg.OpenAI.PrincipalTokens[1].TokenRef {
t.Fatalf("token_ref must distinguish app/integration tokens: %+v", cfg.OpenAI.PrincipalTokens)
}
}
func TestLoadEdge_OpenAIPrincipalTokensRejectInvalid(t *testing.T) {
validHash := strings.Repeat("a1", 32)
for _, tc := range []struct {
name string
yaml string
}{
{
name: "duplicate token_ref",
yaml: `
openai:
principal_tokens:
- token_ref: "dup"
token_hash_sha256: "` + validHash + `"
principal_ref: "user:alice"
- token_ref: "dup"
token_hash_sha256: "` + strings.Repeat("b2", 32) + `"
principal_ref: "user:bob"
`,
},
{
name: "duplicate token_hash_sha256",
yaml: `
openai:
principal_tokens:
- token_ref: "one"
token_hash_sha256: "` + validHash + `"
principal_ref: "user:alice"
- token_ref: "two"
token_hash_sha256: "` + validHash + `"
principal_ref: "user:bob"
`,
},
{
name: "empty principal_ref",
yaml: `
openai:
principal_tokens:
- token_ref: "one"
token_hash_sha256: "` + validHash + `"
principal_ref: ""
`,
},
{
name: "non-hex hash",
yaml: `
openai:
principal_tokens:
- token_ref: "one"
token_hash_sha256: "` + strings.Repeat("z", 64) + `"
principal_ref: "user:alice"
`,
},
{
name: "non-64-length hash",
yaml: `
openai:
principal_tokens:
- token_ref: "one"
token_hash_sha256: "abcd"
principal_ref: "user:alice"
`,
},
} {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
body := "server:\n listen: \"0.0.0.0:9090\"\n" + tc.yaml
if err := os.WriteFile(f, []byte(body), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
if _, err := config.LoadEdge(f); err == nil {
t.Fatal("expected validation error, got nil")
}
})
}
}
// TestOpenAIPrincipalTokenConf_NoRawTokenField guards against reintroducing a
// raw-token-storing field on the principal token mapping struct: only
// token_ref, token_hash_sha256, principal_ref, and principal_alias are
// allowed YAML keys.
func TestOpenAIPrincipalTokenConf_NoRawTokenField(t *testing.T) {
allowed := map[string]struct{}{
"token_ref": {},
"token_hash_sha256": {},
"principal_ref": {},
"principal_alias": {},
}
typ := reflect.TypeOf(config.OpenAIPrincipalTokenConf{})
for i := 0; i < typ.NumField(); i++ {
tag := typ.Field(i).Tag.Get("yaml")
key := strings.SplitN(tag, ",", 2)[0]
if _, ok := allowed[key]; !ok {
t.Fatalf("unexpected field %q (yaml tag %q) on OpenAIPrincipalTokenConf; raw token values must not be stored in config", typ.Field(i).Name, tag)
}
}
}
func TestEdgeOpenAIProviderAuthConf_NoRawTokenField(t *testing.T) {
allowed := map[string]struct{}{
"enabled": {},
"from_header": {},
"target_header": {},
"scheme": {},
"required": {},
}
typ := reflect.TypeOf(config.EdgeOpenAIProviderAuthConf{})
for i := 0; i < typ.NumField(); i++ {
tag := typ.Field(i).Tag.Get("yaml")
key := strings.SplitN(tag, ",", 2)[0]
if _, ok := allowed[key]; !ok {
t.Fatalf("unexpected field %q (yaml tag %q) on EdgeOpenAIProviderAuthConf; raw provider tokens must not be stored in config", typ.Field(i).Name, tag)
}
}
}
func TestLoadEdge_OpenAIRouteCatalog(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
listen: "0.0.0.0:18081"
adapter: "ollama"
model_routes:
- model: "model-a"
adapter: "ollama"
target: "llama3"
node: "node-01"
session_id: "sess-a"
timeout_sec: 30
max_queue: 10
queue_timeout_ms: 5000
- model: "model-b"
adapter: "vllm"
target: "qwen"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.OpenAI.ModelRoutes) != 2 {
t.Fatalf("expected 2 model_routes, got %d", len(cfg.OpenAI.ModelRoutes))
}
r0 := cfg.OpenAI.ModelRoutes[0]
if r0.Model != "model-a" || r0.Adapter != "ollama" || r0.Target != "llama3" {
t.Errorf("route[0] mismatch: %+v", r0)
}
if r0.NodeRef != "node-01" || r0.SessionID != "sess-a" || r0.TimeoutSec != 30 {
t.Errorf("route[0] optional fields mismatch: %+v", r0)
}
if r0.MaxQueue != 10 || r0.QueueTimeoutMS != 5000 {
t.Errorf("route[0] queue policy mismatch: max_queue=%d, queue_timeout_ms=%d", r0.MaxQueue, r0.QueueTimeoutMS)
}
if r0.WorkspaceRequired {
t.Errorf("route[0] workspace_required should default to false: %+v", r0)
}
r1 := cfg.OpenAI.ModelRoutes[1]
if r1.Model != "model-b" || r1.Adapter != "vllm" || r1.Target != "qwen" {
t.Errorf("route[1] mismatch: %+v", r1)
}
if r1.MaxQueue != 0 || r1.QueueTimeoutMS != 0 {
t.Errorf("route[1] queue policy should default to 0: max_queue=%d, queue_timeout_ms=%d", r1.MaxQueue, r1.QueueTimeoutMS)
}
if r1.WorkspaceRequired {
t.Errorf("route[1] workspace_required should default to false: %+v", r1)
}
}
func TestLoadEdge_OpenAIRouteCatalogWorkspaceRequired(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
model_routes:
- model: "codex"
adapter: "cli"
target: "codex"
workspace_required: true
- model: "llama3"
adapter: "ollama"
target: "llama3:8b"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.OpenAI.ModelRoutes) != 2 {
t.Fatalf("expected 2 model_routes, got %d", len(cfg.OpenAI.ModelRoutes))
}
agent := cfg.OpenAI.ModelRoutes[0]
if agent.Model != "codex" || agent.Adapter != "cli" || agent.Target != "codex" {
t.Errorf("agent route mismatch: %+v", agent)
}
if !agent.WorkspaceRequired {
t.Errorf("agent route workspace_required should be true: %+v", agent)
}
inference := cfg.OpenAI.ModelRoutes[1]
if inference.Model != "llama3" || inference.Adapter != "ollama" || inference.Target != "llama3:8b" {
t.Errorf("inference route mismatch: %+v", inference)
}
if inference.WorkspaceRequired {
t.Errorf("inference route workspace_required should be false: %+v", inference)
}
}
func TestLoadEdge_OpenAIRouteCatalogDuplicateModelRejects(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
model_routes:
- model: "model-a"
adapter: "ollama"
target: "llama3"
- model: "model-a"
adapter: "vllm"
target: "qwen"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for duplicate model route")
}
if !strings.Contains(err.Error(), "model_routes") || !strings.Contains(err.Error(), "model-a") {
t.Fatalf("expected error mentioning model_routes and model-a, got %v", err)
}
}
func TestLoadEdge_OpenAIRouteCatalogEmptyModelRejects(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
model_routes:
- model: ""
target: "llama3"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for empty model in route catalog")
}
if !strings.Contains(err.Error(), "model_routes") {
t.Fatalf("expected error mentioning model_routes, got %v", err)
}
}
func TestLoadEdge_OpenAIRouteCatalogQueuePolicyRejectsNegative(t *testing.T) {
dir := t.TempDir()
// Test negative max_queue
f1 := filepath.Join(dir, "edge_neg_max.yaml")
yaml1 := `
server:
listen: "0.0.0.0:9090"
openai:
model_routes:
- model: "model-a"
target: "llama3"
max_queue: -1
`
if err := os.WriteFile(f1, []byte(yaml1), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f1)
if err == nil {
t.Fatal("expected error for negative max_queue")
}
if !strings.Contains(err.Error(), "max_queue must be non-negative") {
t.Fatalf("expected error mentioning max_queue must be non-negative, got %v", err)
}
// Test negative queue_timeout_ms
f2 := filepath.Join(dir, "edge_neg_timeout.yaml")
yaml2 := `
server:
listen: "0.0.0.0:9090"
openai:
model_routes:
- model: "model-a"
target: "llama3"
queue_timeout_ms: -500
`
if err := os.WriteFile(f2, []byte(yaml2), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err = config.LoadEdge(f2)
if err == nil {
t.Fatal("expected error for negative queue_timeout_ms")
}
if !strings.Contains(err.Error(), "queue_timeout_ms must be non-negative") {
t.Fatalf("expected error mentioning queue_timeout_ms must be non-negative, got %v", err)
}
}
// NormalizeAdapters regression tests
func TestLoadEdge_OpenAICompatInstances(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
nodes:
- alias: "lemonade-node"
adapters:
openai_compat_instances:
- name: "lemonade"
enabled: true
provider: "lemonade"
endpoint: "http://127.0.0.1:13305"
headers:
Authorization: "Bearer test-key"
capacity: 4
max_queue: 10
queue_timeout_ms: 1500
request_timeout_ms: 30000
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.Nodes) == 0 {
t.Fatal("expected 1 node")
}
insts := cfg.Nodes[0].Adapters.OpenAICompatInstances
if len(insts) != 1 {
t.Fatalf("expected 1 openai_compat instance, got %d", len(insts))
}
inst := insts[0]
if inst.Name != "lemonade" || inst.Provider != "lemonade" || inst.Endpoint != "http://127.0.0.1:13305" {
t.Errorf("unexpected instance config: %+v", inst)
}
if inst.Headers["authorization"] != "Bearer test-key" {
t.Errorf("expected header authorization Bearer test-key, got %q", inst.Headers["authorization"])
}
if inst.Capacity != 4 || inst.MaxQueue != 10 || inst.QueueTimeoutMS != 1500 || inst.RequestTimeoutMS != 30000 {
t.Errorf("unexpected queue config: %+v", inst)
}
}
func TestNormalizeAdapters_OpenAICompatIdempotent(t *testing.T) {
a := config.AdaptersConf{
OpenAICompat: config.OpenAICompatConf{
Enabled: true,
Provider: "lemonade",
Endpoint: "http://127.0.0.1:13305",
Headers: map[string]string{"X-Test": "val"},
},
}
if err := config.NormalizeAdapters(&a); err != nil {
t.Fatalf("first normalize: %v", err)
}
if len(a.OpenAICompatInstances) != 1 {
t.Fatalf("expected 1 instance after first normalize, got %d", len(a.OpenAICompatInstances))
}
if err := config.NormalizeAdapters(&a); err != nil {
t.Fatalf("second normalize (idempotent): %v", err)
}
if len(a.OpenAICompatInstances) != 1 {
t.Fatalf("expected still 1 instance after second normalize, got %d", len(a.OpenAICompatInstances))
}
}
func TestNormalizeAdapters_OpenAICompatConflictErrors(t *testing.T) {
a := config.AdaptersConf{
OpenAICompat: config.OpenAICompatConf{Enabled: true, Endpoint: "http://127.0.0.1:13305"},
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
{Name: "openai_compat", Enabled: true, Endpoint: "http://other-host:13305"},
},
}
if err := config.NormalizeAdapters(&a); err == nil {
t.Fatal("expected error for conflicting legacy openai_compat and explicit instance with same name")
}
}
func TestNormalizeAdapters_OpenAICompatDuplicateNameRejects(t *testing.T) {
a := config.AdaptersConf{
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
{Name: "same", Enabled: true, Endpoint: "http://127.0.0.1:13305"},
{Name: "same", Enabled: true, Endpoint: "http://127.0.0.2:13305"},
},
}
if err := config.NormalizeAdapters(&a); err == nil {
t.Fatal("expected error for duplicate openai_compat instance name")
}
}
func TestNormalizeAdapters_OpenAICompatQueueValidation(t *testing.T) {
a := config.AdaptersConf{
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
{Name: "test", Enabled: true, Endpoint: "http://127.0.0.1:13305", Capacity: -1},
},
}
err := config.NormalizeAdapters(&a)
if err == nil {
t.Fatal("expected negative provider queue config error")
}
if !strings.Contains(err.Error(), "capacity") {
t.Fatalf("expected error to mention capacity, got %v", err)
}
}
// S02 REVIEW_VLLM_CONFIG: vLLM OpenAI-compatible route + instance fixture
// verifies that alias/target/provider/endpoint/no-headers/timeout/queue are
// preserved through config load and mapper/factory contract.
func TestLoadEdge_VLLMOpenAIRouteAndInstance(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
model_routes:
- model: "qwen3.6:35b"
adapter: "openai_compat"
target: "nvidia/Qwen3.6-35B-A3B-NVFP4"
node: "node-vllm-01"
max_queue: 10
queue_timeout_ms: 30000
- model: "codex-agent"
adapter: "cli"
target: "codex"
workspace_required: true
nodes:
- id: "node-vllm-01"
alias: "vllm-gpu-node"
token: "<node-token>"
adapters:
openai_compat_instances:
- name: "vllm-gpu"
enabled: true
provider: "vllm"
endpoint: "http://127.0.0.1:8000/v1"
capacity: 4
max_queue: 16
queue_timeout_ms: 30000
request_timeout_ms: 120000
cli:
enabled: true
profiles:
codex:
command: "codex"
mode: "codex-exec"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
// Verify model_routes: vLLM route has no provider field (only adapter/target).
if len(cfg.OpenAI.ModelRoutes) != 2 {
t.Fatalf("expected 2 model_routes, got %d", len(cfg.OpenAI.ModelRoutes))
}
vllmRoute := cfg.OpenAI.ModelRoutes[0]
if vllmRoute.Model != "qwen3.6:35b" {
t.Errorf("model: got %q, want %q", vllmRoute.Model, "qwen3.6:35b")
}
if vllmRoute.Adapter != "openai_compat" {
t.Errorf("adapter: got %q, want %q", vllmRoute.Adapter, "openai_compat")
}
if vllmRoute.Target != "nvidia/Qwen3.6-35B-A3B-NVFP4" {
t.Errorf("target: got %q, want %q", vllmRoute.Target, "nvidia/Qwen3.6-35B-A3B-NVFP4")
}
if vllmRoute.NodeRef != "node-vllm-01" {
t.Errorf("node: got %q, want %q", vllmRoute.NodeRef, "node-vllm-01")
}
if vllmRoute.MaxQueue != 10 || vllmRoute.QueueTimeoutMS != 30000 {
t.Errorf("queue policy mismatch: max_queue=%d, queue_timeout_ms=%d", vllmRoute.MaxQueue, vllmRoute.QueueTimeoutMS)
}
// Codex agent route: workspace_required=true.
agentRoute := cfg.OpenAI.ModelRoutes[1]
if agentRoute.Model != "codex-agent" || agentRoute.Adapter != "cli" || agentRoute.Target != "codex" {
t.Errorf("agent route mismatch: %+v", agentRoute)
}
if !agentRoute.WorkspaceRequired {
t.Error("agent route workspace_required should be true")
}
// Verify openai_compat_instances: provider="vllm", endpoint, no headers, queue policy.
nodes := cfg.Nodes
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
insts := nodes[0].Adapters.OpenAICompatInstances
if len(insts) != 1 {
t.Fatalf("expected 1 openai_compat instance, got %d", len(insts))
}
inst := insts[0]
if inst.Name != "vllm-gpu" {
t.Errorf("instance name: got %q, want %q", inst.Name, "vllm-gpu")
}
if inst.Provider != "vllm" {
t.Errorf("provider: got %q, want %q", inst.Provider, "vllm")
}
if inst.Endpoint != "http://127.0.0.1:8000/v1" {
t.Errorf("endpoint: got %q, want %q", inst.Endpoint, "http://127.0.0.1:8000/v1")
}
if len(inst.Headers) != 0 {
t.Errorf("expected no headers for vLLM, got %+v", inst.Headers)
}
if inst.Capacity != 4 || inst.MaxQueue != 16 || inst.QueueTimeoutMS != 30000 || inst.RequestTimeoutMS != 120000 {
t.Errorf("queue config mismatch: %+v", inst)
}
}