package config_test import ( "os" "path/filepath" "strings" "testing" "iop/packages/go/config" ) func TestLoadEdge_MultiOllamaInstances(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - alias: "multi" adapters: ollama_instances: - name: "local" enabled: true base_url: "http://127.0.0.1:11434" context_size: 131072 capacity: 2 max_queue: 4 queue_timeout_ms: 1000 request_timeout_ms: 20000 - name: "dgx" enabled: true base_url: "http://192.168.0.91:11434" context_size: 262144 capacity: 6 max_queue: 12 queue_timeout_ms: 2000 request_timeout_ms: 60000 ` 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.OllamaInstances if len(insts) != 2 { t.Fatalf("expected 2 ollama instances, got %d", len(insts)) } if insts[0].Name != "local" || insts[0].BaseURL != "http://127.0.0.1:11434" || insts[0].ContextSize != 131072 { t.Errorf("unexpected instance[0]: %+v", insts[0]) } if insts[0].Capacity != 2 || insts[0].MaxQueue != 4 || insts[0].QueueTimeoutMS != 1000 || insts[0].RequestTimeoutMS != 20000 { t.Errorf("unexpected instance[0] queue config: %+v", insts[0]) } if insts[1].Name != "dgx" || insts[1].BaseURL != "http://192.168.0.91:11434" || insts[1].ContextSize != 262144 { t.Errorf("unexpected instance[1]: %+v", insts[1]) } if insts[1].Capacity != 6 || insts[1].MaxQueue != 12 || insts[1].QueueTimeoutMS != 2000 || insts[1].RequestTimeoutMS != 60000 { t.Errorf("unexpected instance[1] queue config: %+v", insts[1]) } } func TestLoadEdge_MultiVllmInstances(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - alias: "multi-vllm" adapters: vllm_instances: - name: "a100" enabled: true endpoint: "http://10.0.0.5:8000" capacity: 4 max_queue: 10 queue_timeout_ms: 1500 request_timeout_ms: 45000 - name: "h100" enabled: true endpoint: "http://10.0.0.6:8000" capacity: 8 max_queue: 16 queue_timeout_ms: 2500 request_timeout_ms: 90000 ` 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.VllmInstances if len(insts) != 2 { t.Fatalf("expected 2 vllm instances, got %d", len(insts)) } if insts[0].Name != "a100" || insts[0].Endpoint != "http://10.0.0.5:8000" { t.Errorf("unexpected instance[0]: %+v", insts[0]) } if insts[0].Capacity != 4 || insts[0].MaxQueue != 10 || insts[0].QueueTimeoutMS != 1500 || insts[0].RequestTimeoutMS != 45000 { t.Errorf("unexpected instance[0] queue config: %+v", insts[0]) } if insts[1].Name != "h100" || insts[1].Endpoint != "http://10.0.0.6:8000" { t.Errorf("unexpected instance[1]: %+v", insts[1]) } if insts[1].Capacity != 8 || insts[1].MaxQueue != 16 || insts[1].QueueTimeoutMS != 2500 || insts[1].RequestTimeoutMS != 90000 { t.Errorf("unexpected instance[1] queue config: %+v", insts[1]) } } func TestLoadEdge_LegacyOllamaPromotedToInstances(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - alias: "local" adapters: ollama: enabled: true base_url: "http://192.168.0.91:11434" context_size: 262144 capacity: 5 max_queue: 9 queue_timeout_ms: 1700 request_timeout_ms: 55000 ` 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) } insts := cfg.Nodes[0].Adapters.OllamaInstances if len(insts) != 1 { t.Fatalf("expected 1 ollama instance from legacy field, got %d", len(insts)) } if insts[0].Name != "ollama" { t.Errorf("expected instance name %q, got %q", "ollama", insts[0].Name) } if insts[0].BaseURL != "http://192.168.0.91:11434" { t.Errorf("expected base_url %q, got %q", "http://192.168.0.91:11434", insts[0].BaseURL) } if insts[0].Capacity != 5 || insts[0].MaxQueue != 9 || insts[0].QueueTimeoutMS != 1700 || insts[0].RequestTimeoutMS != 55000 { t.Errorf("unexpected promoted queue config: %+v", insts[0]) } } func TestLoadEdge_DuplicateOllamaInstanceNameRejected(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - alias: "dup" adapters: ollama_instances: - name: "same" enabled: true base_url: "http://127.0.0.1:11434" - name: "same" enabled: true base_url: "http://127.0.0.2:11434" ` 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 ollama instance name") } } func TestLoadEdge_ProviderQueueConfigRejectsNegative(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - alias: "bad-queue" adapters: ollama_instances: - name: "local" enabled: true base_url: "http://127.0.0.1:11434" capacity: -1 ` 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 negative provider queue config error") } if !strings.Contains(err.Error(), "capacity") || !strings.Contains(err.Error(), "non-negative") { t.Fatalf("expected error to mention capacity non-negative, got %v", err) } } func TestNormalizeAdapters_OllamaIdempotent(t *testing.T) { a := config.AdaptersConf{ Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434", ContextSize: 4096}, } if err := config.NormalizeAdapters(&a); err != nil { t.Fatalf("first normalize: %v", err) } if len(a.OllamaInstances) != 1 { t.Fatalf("expected 1 instance after first normalize, got %d", len(a.OllamaInstances)) } if err := config.NormalizeAdapters(&a); err != nil { t.Fatalf("second normalize (idempotent): %v", err) } if len(a.OllamaInstances) != 1 { t.Fatalf("expected still 1 instance after second normalize, got %d", len(a.OllamaInstances)) } } func TestNormalizeAdapters_VllmIdempotent(t *testing.T) { a := config.AdaptersConf{ Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"}, } if err := config.NormalizeAdapters(&a); err != nil { t.Fatalf("first normalize: %v", err) } if len(a.VllmInstances) != 1 { t.Fatalf("expected 1 instance after first normalize, got %d", len(a.VllmInstances)) } if err := config.NormalizeAdapters(&a); err != nil { t.Fatalf("second normalize (idempotent): %v", err) } if len(a.VllmInstances) != 1 { t.Fatalf("expected still 1 instance after second normalize, got %d", len(a.VllmInstances)) } } func TestNormalizeAdapters_OllamaConflictErrors(t *testing.T) { a := config.AdaptersConf{ Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"}, OllamaInstances: []config.OllamaInstanceConf{ {Name: "ollama", Enabled: true, BaseURL: "http://other-host:11434"}, }, } if err := config.NormalizeAdapters(&a); err == nil { t.Fatal("expected error for conflicting legacy ollama and explicit instance with same name") } } func TestNormalizeAdapters_VllmConflictErrors(t *testing.T) { a := config.AdaptersConf{ Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"}, VllmInstances: []config.VllmInstanceConf{ {Name: "vllm", Enabled: true, Endpoint: "http://other-host:8000"}, }, } if err := config.NormalizeAdapters(&a); err == nil { t.Fatal("expected error for conflicting legacy vllm and explicit instance with same name") } } func TestNormalizeAdapters_OllamaDisabledSameNameErrors(t *testing.T) { a := config.AdaptersConf{ Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"}, OllamaInstances: []config.OllamaInstanceConf{ {Name: "ollama", Enabled: false, BaseURL: "http://127.0.0.1:11434"}, }, } if err := config.NormalizeAdapters(&a); err == nil { t.Fatal("expected error for legacy enabled ollama conflicting with same-name disabled explicit instance") } } func TestNormalizeAdapters_VllmDisabledSameNameErrors(t *testing.T) { a := config.AdaptersConf{ Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"}, VllmInstances: []config.VllmInstanceConf{ {Name: "vllm", Enabled: false, Endpoint: "http://127.0.0.1:8000"}, }, } if err := config.NormalizeAdapters(&a); err == nil { t.Fatal("expected error for legacy enabled vllm conflicting with same-name disabled explicit instance") } } func TestLoadEdge_OllamaContextSize(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - alias: "local" adapters: ollama: enabled: true base_url: "http://192.168.0.91:11434" context_size: 262144 capacity: 3 max_queue: 8 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) } ollama := cfg.Nodes[0].Adapters.Ollama if !ollama.Enabled { t.Fatal("expected ollama.enabled=true") } if ollama.BaseURL != "http://192.168.0.91:11434" || ollama.ContextSize != 262144 { t.Fatalf("unexpected ollama config: %+v", ollama) } if ollama.Capacity != 3 || ollama.MaxQueue != 8 || ollama.QueueTimeoutMS != 1500 || ollama.RequestTimeoutMS != 30000 { t.Fatalf("unexpected ollama queue config: %+v", ollama) } } // Provider pool schema validation tests (G06) func TestLoadEdge_ModelCatalogBasicHappyPath(t *testing.T) { // Happy path: models[].providers values must match nodes[].providers[].models. dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" models: - id: "qwen3.6:35b" display_name: "Qwen 3.6 35B" context_window_tokens: 262144 default_max_tokens: 32768 min_max_tokens: 32768 default_thinking_token_budget: 8192 providers: vllm-gpu: "nvidia/Qwen3.6-35B-A3B-NVFP4" ollama-local: "qwen3.6:35b" nodes: - id: "node-gpu-01" alias: "gpu-node" providers: - id: "vllm-gpu" type: "vllm" category: "api" models: - "nvidia/Qwen3.6-35B-A3B-NVFP4" capacity: 4 - id: "ollama-local" type: "ollama" category: "local_inference" models: - "qwen3.6:35b" capacity: 2 ` 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.Models) != 1 { t.Fatalf("expected 1 model catalog entry, got %d", len(cfg.Models)) } m := cfg.Models[0] if m.ID != "qwen3.6:35b" { t.Errorf("models[0].ID = %q, want %q", m.ID, "qwen3.6:35b") } if m.DisplayName != "Qwen 3.6 35B" { t.Errorf("models[0].DisplayName = %q, want %q", m.DisplayName, "Qwen 3.6 35B") } if m.ContextWindowTokens != 262144 { t.Errorf("models[0].ContextWindowTokens = %d, want 262144", m.ContextWindowTokens) } if m.DefaultMaxTokens != 32768 { t.Errorf("models[0].DefaultMaxTokens = %d, want 32768", m.DefaultMaxTokens) } if m.MinMaxTokens != 32768 { t.Errorf("models[0].MinMaxTokens = %d, want 32768", m.MinMaxTokens) } if m.DefaultThinkingTokenBudget != 8192 { t.Errorf("models[0].DefaultThinkingTokenBudget = %d, want 8192", m.DefaultThinkingTokenBudget) } if len(m.Providers) != 2 { t.Fatalf("expected 2 providers, got %d", len(m.Providers)) } if m.Providers["vllm-gpu"] != "nvidia/Qwen3.6-35B-A3B-NVFP4" { t.Errorf("providers[vllm-gpu] = %q, want %q", m.Providers["vllm-gpu"], "nvidia/Qwen3.6-35B-A3B-NVFP4") } if m.Providers["ollama-local"] != "qwen3.6:35b" { t.Errorf("providers[ollama-local] = %q, want %q", m.Providers["ollama-local"], "qwen3.6:35b") } if len(cfg.Nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(cfg.Nodes)) } if len(cfg.Nodes[0].Providers) != 2 { t.Fatalf("expected 2 node providers, got %d", len(cfg.Nodes[0].Providers)) } // Check category resolution. for _, p := range cfg.Nodes[0].Providers { if p.ID == "vllm-gpu" && p.Category != config.CategoryAPI { t.Errorf("providers[vllm-gpu].Category = %q, want %q", p.Category, config.CategoryAPI) } if p.ID == "ollama-local" && p.Category != config.CategoryLocalInference { t.Errorf("providers[ollama-local].Category = %q, want %q", p.Category, config.CategoryLocalInference) } } } func TestLoadEdge_ConfigExampleMixedAndOllamaOnlyProviderGroups(t *testing.T) { f := filepath.Join("..", "..", "..", "configs", "edge.yaml") cfg, err := config.LoadEdge(f) if err != nil { t.Fatalf("load example config: %v", err) } models := map[string]config.ModelCatalogEntry{} for _, m := range cfg.Models { models[m.ID] = m } mixed, ok := models["qwen3.6:35b"] if !ok { t.Fatal("expected qwen3.6:35b mixed model group in example config") } if mixed.Providers["mac-mlx-vllm"] != "mlx-community/Qwen3.6-35B-A3B-4bit" { t.Fatalf("expected mac-mlx-vllm served model in mixed group, got %+v", mixed.Providers) } if mixed.Providers["ollama-local"] != "qwen3.6:35b" { t.Fatalf("expected ollama-local fallback in mixed group, got %+v", mixed.Providers) } ollamaOnly, ok := models["local-llama3"] if !ok { t.Fatal("expected local-llama3 Ollama-only model group in example config") } if len(ollamaOnly.Providers) != 1 || ollamaOnly.Providers["ollama-local"] != "llama3.1:8b" { t.Fatalf("expected local-llama3 to route only to ollama-local, got %+v", ollamaOnly.Providers) } providers := map[string]config.NodeProviderConf{} for _, n := range cfg.Nodes { for _, p := range n.Providers { providers[p.ID] = p } } vllm := providers["mac-mlx-vllm"] ollama := providers["ollama-local"] if vllm.Type == "" || ollama.Type == "" { t.Fatalf("expected both mac-mlx-vllm and ollama-local providers, got %+v", providers) } if config.NormalizeProviderType(vllm.Type) != "openai_compat" { t.Fatalf("mac-mlx-vllm should classify as OpenAI-compatible, got %q", vllm.Type) } if config.NormalizeProviderType(ollama.Type) != "ollama" { t.Fatalf("ollama-local should classify as normalized Ollama, got %q", ollama.Type) } if ollama.Capacity != 1 || ollama.Priority <= vllm.Priority { t.Fatalf("expected Ollama example to use lower capacity and higher priority number than vLLM, got vllm=%+v ollama=%+v", vllm, ollama) } } func TestLoadEdge_SeulgivibeProviderFirstAliases(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 models: - id: "claude-sonnet-4-5" providers: seulgivibe-claude: "claude-sonnet-4-5" - id: "claude-opus-4-8" providers: seulgivibe-claude: "claude-opus-4-8" - id: "claude-fable-5" providers: seulgivibe-claude: "claude-fable-5" - id: "gpt-5.1" providers: seulgivibe-openai: "gpt-5.1" - id: "gpt-5.5" providers: seulgivibe-openai: "gpt-5.5" nodes: - id: "node-seulgivibe" alias: "seulgivibe-node" providers: - id: "seulgivibe-claude" type: "seulgivibe_claude" category: "api" endpoint: "https://seulgivibe.example.invalid/anthropic/v1" models: - "claude-sonnet-4-5" - "claude-opus-4-8" - "claude-fable-5" capacity: 4 - id: "seulgivibe-openai" type: "seulgivibe_openai" category: "api" endpoint: "https://seulgivibe.example.invalid/openai/v1" models: - "gpt-5.1" - "gpt-5.5" capacity: 4 ` 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.Models) != 5 { t.Fatalf("expected 5 Seulgivibe model catalog entries, got %d", len(cfg.Models)) } if got := config.NormalizeProviderType(cfg.Nodes[0].Providers[0].Type); got != "openai_compat" { t.Fatalf("seulgivibe_claude should normalize to openai_compat, got %q", got) } if got := config.NormalizeProviderType(cfg.Nodes[0].Providers[1].Type); got != "openai_compat" { t.Fatalf("seulgivibe_openai should normalize to openai_compat, got %q", got) } auth := cfg.OpenAI.ProviderAuth if !auth.Enabled || auth.FromHeader != "X-IOP-Provider-Authorization" || auth.TargetHeader != "Authorization" || auth.Scheme != "Bearer" || !auth.Required { t.Fatalf("unexpected provider_auth defaults: %+v", auth) } for _, m := range cfg.Models { for providerID, servedModel := range m.Providers { if strings.TrimSpace(servedModel) == "" { t.Fatalf("model %q provider %q has empty served model", m.ID, providerID) } } } } func TestLoadEdge_ModelCatalogEmptyModelIDRejected(t *testing.T) { // No models[] on node, so serve model check is skipped (legacy compat). dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" models: - id: "" providers: vllm-gpu: "model-a" nodes: - id: "node-01" providers: - id: "vllm-gpu" type: "vllm" category: "api" ` 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 id") } if !strings.Contains(err.Error(), "models[0]") || !strings.Contains(err.Error(), "id must not be empty") { t.Fatalf("expected error mentioning models[0] and id must not be empty, got %v", err) } } func TestLoadEdge_ModelCatalogDuplicateModelIDRejected(t *testing.T) { // serve model membership fail → 다른 error로 먼저 실패해야 하지만 duplicate 체크가 // 먼저 발생하므로 models validation 전에 seen check가 먼저 통과하는 fixture. dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" models: - id: "qwen3.6:35b" providers: vllm-gpu: "model-a" - id: "qwen3.6:35b" providers: ollama: "qwen3.6" nodes: - id: "node-01" providers: - id: "vllm-gpu" type: "vllm" category: "api" models: - "model-a" - id: "ollama" type: "ollama" category: "local_inference" models: - "qwen3.6" ` 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 id") } if !strings.Contains(err.Error(), "duplicate model id") { t.Fatalf("expected error mentioning duplicate model id, got %v", err) } } func TestLoadEdge_ModelCatalogEmptyProvidersRejected(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" models: - id: "qwen3.6:35b" providers: {} nodes: - id: "node-01" providers: - id: "vllm-gpu" type: "vllm" category: "api" ` 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 providers") } if !strings.Contains(err.Error(), "providers must not be empty") { t.Fatalf("expected error mentioning providers must not be empty, got %v", err) } } func TestLoadEdge_ModelCatalogContextWindowRejectsNegative(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" models: - id: "qwen3.6:35b" context_window_tokens: -1 providers: vllm-gpu: "model-a" nodes: - id: "node-01" providers: - id: "vllm-gpu" type: "vllm" category: "api" models: - "model-a" ` 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 negative context_window_tokens") } if !strings.Contains(err.Error(), "context_window_tokens must be non-negative") { t.Fatalf("expected error mentioning context_window_tokens, got %v", err) } } func TestLoadEdge_ModelCatalogServedModelMembershipMissing(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" models: - id: "qwen3.6:35b" providers: vllm-gpu: "typo-model" nodes: - id: "node-gpu-01" providers: - id: "vllm-gpu" type: "vllm" category: "api" models: - "nvidia/Qwen3.6-35B-A3B-NVFP4" ` 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 served model not in provider's models list") } if !strings.Contains(err.Error(), "typo-model") || !strings.Contains(err.Error(), "vllm-gpu") { t.Fatalf("expected error mentioning typo-model and vllm-gpu, got %v", err) } } func TestLoadEdge_ModelCatalogUnresolvedProviderRejected(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" models: - id: "qwen3.6:35b" providers: non-existent: "model-a" nodes: - id: "node-01" providers: - id: "vllm-gpu" type: "vllm" category: "api" ` 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 unresolved provider id") } if !strings.Contains(err.Error(), "does not resolve") || !strings.Contains(err.Error(), "non-existent") { t.Fatalf("expected error mentioning does not resolve and non-existent, got %v", err) } } // ---------- PROVIDER_POLICY_CONFIG: root provider_pool queue policy tests ---------- // TestLoadEdge_ProviderPoolPolicyCanonical verifies S06: root provider_pool // is the canonical queue policy owner and its explicit values are honored. func TestLoadEdge_ProviderPoolPolicyCanonical(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" provider_pool: max_queue: 32 queue_timeout_ms: 60000 nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 ` 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.ProviderPool.MaxQueue != 32 { t.Errorf("expected canonical max_queue=32, got %d", cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 60000 { t.Errorf("expected canonical queue_timeout_ms=60000, got %d", cfg.ProviderPool.QueueTimeoutMS) } } // TestLoadEdge_ProviderPoolPolicyCanonicalMaxZeroDefaults verifies that // canonical max_queue=0 normalizes to the default 16, preserving other values. func TestLoadEdge_ProviderPoolPolicyCanonicalMaxZeroDefaults(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" provider_pool: max_queue: 0 queue_timeout_ms: 45000 nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 ` 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.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue { t.Errorf("expected normalized max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 45000 { t.Errorf("expected queue_timeout_ms=45000, got %d", cfg.ProviderPool.QueueTimeoutMS) } } // TestLoadEdge_ProviderPoolPolicyCanonicalExplicitZeroTimeout verifies that // explicit queue_timeout_ms=0 is preserved as no-timeout. func TestLoadEdge_ProviderPoolPolicyCanonicalExplicitZeroTimeout(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" provider_pool: max_queue: 20 queue_timeout_ms: 0 nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 ` 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.ProviderPool.MaxQueue != 20 { t.Errorf("expected max_queue=20, got %d", cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 0 { t.Errorf("expected explicit zero timeout preserved, got %d", cfg.ProviderPool.QueueTimeoutMS) } } // TestLoadEdge_ProviderPoolPolicyLegacyIdenticalPromotion verifies that when // no canonical is set, identical legacy pairs across providers are promoted. func TestLoadEdge_ProviderPoolPolicyLegacyIdenticalPromotion(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 max_queue: 8 queue_timeout_ms: 15000 - id: "prov-b" type: "ollama" category: "local_inference" models: ["model-b"] capacity: 3 max_queue: 8 queue_timeout_ms: 15000 ` 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.ProviderPool.MaxQueue != 8 { t.Errorf("expected promoted max_queue=8, got %d", cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 15000 { t.Errorf("expected promoted queue_timeout_ms=15000, got %d", cfg.ProviderPool.QueueTimeoutMS) } } // TestLoadEdge_ProviderPoolPolicyLegacyConflict verifies that differing // legacy pairs across providers produce a deterministic error naming both // providers. func TestLoadEdge_ProviderPoolPolicyLegacyConflict(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 max_queue: 8 queue_timeout_ms: 15000 - id: "prov-b" type: "ollama" category: "local_inference" models: ["model-b"] capacity: 3 max_queue: 16 queue_timeout_ms: 15000 ` 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 conflict error for differing legacy queue policy") } if !strings.Contains(err.Error(), "conflicting provider queue policy") { t.Fatalf("expected conflict error, got: %v", err) } if !strings.Contains(err.Error(), "prov-a") || !strings.Contains(err.Error(), "prov-b") { t.Fatalf("expected error to name both conflicting providers, got: %v", err) } } // TestLoadEdge_ProviderPoolPolicyOrderIndependentConflict verifies that the // conflict detection is order-independent (swapping prov-a and prov-b order). func TestLoadEdge_ProviderPoolPolicyOrderIndependentConflict(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-01" providers: - id: "prov-b" type: "ollama" category: "local_inference" models: ["model-b"] capacity: 3 max_queue: 16 queue_timeout_ms: 15000 - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 max_queue: 8 queue_timeout_ms: 15000 ` 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 conflict error for reversed legacy queue policy order") } if !strings.Contains(err.Error(), "conflicting provider queue policy") { t.Fatalf("expected conflict error regardless of provider order, got: %v", err) } } // TestLoadEdge_ProviderPoolPolicyDefaults verifies that with no canonical and // no usable legacy values, defaults are used. func TestLoadEdge_ProviderPoolPolicyDefaults(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 ` 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.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue { t.Errorf("expected default max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != config.DefaultProviderPoolQueueTimeoutMS { t.Errorf("expected default queue_timeout_ms=%d, got %d", config.DefaultProviderPoolQueueTimeoutMS, cfg.ProviderPool.QueueTimeoutMS) } } // TestLoadEdge_ProviderPoolPolicyCanonicalOverridesLegacy verifies that when // canonical is set, legacy per-provider values do not affect the effective policy. func TestLoadEdge_ProviderPoolPolicyCanonicalOverridesLegacy(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" provider_pool: max_queue: 10 queue_timeout_ms: 5000 nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 max_queue: 99 queue_timeout_ms: 99000 ` 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.ProviderPool.MaxQueue != 10 { t.Errorf("expected canonical max_queue=10 to override legacy, got %d", cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 5000 { t.Errorf("expected canonical queue_timeout_ms=5000 to override legacy, got %d", cfg.ProviderPool.QueueTimeoutMS) } } // ---------- PROVIDER_POLICY_CONFIG: canonical negative rejection (G04 followup) ---------- // TestLoadEdge_ProviderPoolPolicyCanonicalNegativeRejected verifies S06 that // negative max_queue and queue_timeout_ms are rejected before default // normalization can mask them. func TestLoadEdge_ProviderPoolPolicyCanonicalNegativeRejected(t *testing.T) { t.Run("max_queue negative", func(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" provider_pool: max_queue: -1 queue_timeout_ms: 30000 nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 ` 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 negative max_queue") } if !strings.Contains(err.Error(), "provider_pool.max_queue") || !strings.Contains(err.Error(), "non-negative") { t.Fatalf("expected error to mention provider_pool.max_queue non-negative, got: %v", err) } }) t.Run("queue_timeout_ms negative", func(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" provider_pool: max_queue: 10 queue_timeout_ms: -500 nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 ` 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 negative queue_timeout_ms") } if !strings.Contains(err.Error(), "provider_pool.queue_timeout_ms") || !strings.Contains(err.Error(), "non-negative") { t.Fatalf("expected error to mention provider_pool.queue_timeout_ms non-negative, got: %v", err) } }) } // ---------- PROVIDER_POLICY_CONFIG: legacy partial fields and effective equality (G04 followup) ---------- // TestLoadEdge_ProviderPoolPolicyLegacyPartialFields verifies that legacy // timeout-only and max-only pairs are normalized to effective (DefaultProviderPoolMaxQueue, // timeout) and (max, 0) respectively before promotion. func TestLoadEdge_ProviderPoolPolicyLegacyPartialFields(t *testing.T) { t.Run("timeout-only promotes to default-max", func(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 queue_timeout_ms: 20000 ` 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.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue { t.Errorf("expected normalized max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 20000 { t.Errorf("expected queue_timeout_ms=20000, got %d", cfg.ProviderPool.QueueTimeoutMS) } }) t.Run("max-only preserves no-timeout", func(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 max_queue: 24 ` 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.ProviderPool.MaxQueue != 24 { t.Errorf("expected max_queue=24, got %d", cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 0 { t.Errorf("expected no-timeout preserved (0), got %d", cfg.ProviderPool.QueueTimeoutMS) } }) } // TestLoadEdge_ProviderPoolPolicyLegacyEffectiveEquality verifies that a // timeout-only provider (max_queue=0) and a provider with max_queue=Default // are treated as equal effective pairs and promoted without conflict. func TestLoadEdge_ProviderPoolPolicyLegacyEffectiveEquality(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-01" providers: - id: "prov-a" type: "ollama" category: "local_inference" models: ["model-a"] capacity: 2 queue_timeout_ms: 20000 - id: "prov-b" type: "ollama" category: "local_inference" models: ["model-b"] capacity: 3 max_queue: 16 queue_timeout_ms: 20000 ` 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) } // Both normalize to (DefaultProviderPoolMaxQueue, 20000). if cfg.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue { t.Errorf("expected normalized max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue) } if cfg.ProviderPool.QueueTimeoutMS != 20000 { t.Errorf("expected queue_timeout_ms=20000, got %d", cfg.ProviderPool.QueueTimeoutMS) } }