package config_test import ( "os" "path/filepath" "strings" "testing" "iop/packages/go/config" ) func TestLoadEdge_NodeAgentKindGeneric(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := `server: listen: "0.0.0.0:9090" nodes: - alias: "node-local" token: "token-node" agent_kind: "generic-node" ` 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") } if cfg.Nodes[0].AgentKind != config.AgentKindGenericNode { t.Fatalf("expected agent_kind=%q, got %q", config.AgentKindGenericNode, cfg.Nodes[0].AgentKind) } } func TestLoadEdge_NodeAgentKindDefault(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := `server: listen: "0.0.0.0:9090" nodes: - alias: "node-local" token: "token-node" ` 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") } if cfg.Nodes[0].AgentKind != config.AgentKindGenericNode { t.Fatalf("expected default agent_kind=%q, got %q", config.AgentKindGenericNode, cfg.Nodes[0].AgentKind) } } func TestLoadEdge_NodeAgentKindRejectsInvalid(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - alias: "bad-local" token: "token-bad" agent_kind: "not-a-kind" ` 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 invalid agent_kind error") } if !strings.Contains(err.Error(), "agent_kind") { t.Fatalf("expected error to mention agent_kind, got %v", err) } } func TestLoadEdge_NodeProviderConfEmptyIDRejected(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: "" 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 node provider id") } if !strings.Contains(err.Error(), "providers[].id must not be empty") { t.Fatalf("expected error mentioning providers[].id must not be empty, got %v", err) } } func TestLoadEdge_NodeProviderConfEmptyTypeRejected(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: "p1" type: "" 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 provider type") } if !strings.Contains(err.Error(), "type must not be empty") { t.Fatalf("expected error mentioning type must not be empty, got %v", err) } } func TestLoadEdge_NodeProviderConfInvalidCategoryRejected(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: "p1" type: "vllm" category: "invalid_category" ` 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 invalid provider category") } if !strings.Contains(err.Error(), "invalid provider category") { t.Fatalf("expected error mentioning invalid provider category, got %v", err) } } func TestLoadEdge_NodeProviderConfDuplicateIDRejected(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: "p1" type: "vllm" category: "api" - id: "p1" type: "ollama" category: "local_inference" ` 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 provider id within a node") } if !strings.Contains(err.Error(), "duplicate provider id") { t.Fatalf("expected error mentioning duplicate provider id, got %v", err) } } func TestLoadEdge_NodeProviderConfNegativeCapacityRejected(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: "p1" type: "vllm" category: "api" 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 error for negative provider capacity") } if !strings.Contains(err.Error(), "capacity must be non-negative") { t.Fatalf("expected error mentioning capacity must be non-negative, got %v", err) } } func TestLoadEdge_NodeProviderConfEmptyServedModelRejected(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: "p1" type: "vllm" category: "api" models: - "" - "real-model" ` 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 served model in provider models list") } if !strings.Contains(err.Error(), "served model name must not be empty") { t.Fatalf("expected error mentioning served model name must not be empty, got %v", err) } } func TestLoad_NodeReconnectDefaults(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "node.yaml") if err := os.WriteFile(f, []byte("transport:\n edge_addr: \"localhost:9090\"\n token: \"tok\"\n"), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } cfg, err := config.Load(f) if err != nil { t.Fatalf("load: %v", err) } if cfg.Reconnect.IntervalSec != 10 { t.Fatalf("expected reconnect.interval_sec=10, got %d", cfg.Reconnect.IntervalSec) } if cfg.Reconnect.MaxAttempts != 10 { t.Fatalf("expected reconnect.max_attempts=10, got %d", cfg.Reconnect.MaxAttempts) } } func TestLoad_NodeReconnectOverride(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "node.yaml") yaml := "transport:\n edge_addr: \"localhost:9090\"\n token: \"tok\"\nreconnect:\n interval_sec: 30\n max_attempts: 5\n" if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } cfg, err := config.Load(f) if err != nil { t.Fatalf("load: %v", err) } if cfg.Reconnect.IntervalSec != 30 { t.Fatalf("expected reconnect.interval_sec=30, got %d", cfg.Reconnect.IntervalSec) } if cfg.Reconnect.MaxAttempts != 5 { t.Fatalf("expected reconnect.max_attempts=5, got %d", cfg.Reconnect.MaxAttempts) } } // TestLoadReconnectPolicy verifies that config load distinguishes an omitted // max_attempts (default 10), an explicit 0 (unlimited), and a positive finite // limit, and preserves an explicit interval alongside each. SDD S16/S17. func TestLoadReconnectPolicy(t *testing.T) { cases := []struct { name string reconnectYAML string wantInterval int wantMax int }{ { name: "omitted keeps defaults", reconnectYAML: "", wantInterval: 10, wantMax: 10, }, { name: "explicit zero max is unlimited with default interval", reconnectYAML: "reconnect:\n max_attempts: 0\n", wantInterval: 10, wantMax: 0, }, { name: "explicit zero max with positive interval stays unlimited", reconnectYAML: "reconnect:\n max_attempts: 0\n interval_sec: 5\n", wantInterval: 5, wantMax: 0, }, { name: "positive finite limit is preserved", reconnectYAML: "reconnect:\n max_attempts: 3\n interval_sec: 30\n", wantInterval: 30, wantMax: 3, }, { name: "positive finite limit allows zero interval", reconnectYAML: "reconnect:\n max_attempts: 3\n interval_sec: 0\n", wantInterval: 0, wantMax: 3, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "node.yaml") yaml := "transport:\n edge_addr: \"localhost:9090\"\n token: \"tok\"\n" + tc.reconnectYAML if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } cfg, err := config.Load(f) if err != nil { t.Fatalf("load: %v", err) } if cfg.Reconnect.IntervalSec != tc.wantInterval { t.Errorf("interval_sec: got %d want %d", cfg.Reconnect.IntervalSec, tc.wantInterval) } if cfg.Reconnect.MaxAttempts != tc.wantMax { t.Errorf("max_attempts: got %d want %d", cfg.Reconnect.MaxAttempts, tc.wantMax) } }) } } // TestLoadRejectsInvalidReconnectPolicy verifies that negative attempts/interval // and an unlimited policy with a non-positive interval are rejected at load with // a clear config error. SDD S17. func TestLoadRejectsInvalidReconnectPolicy(t *testing.T) { cases := []struct { name string reconnectYAML string wantSubstr string }{ { name: "negative max_attempts", reconnectYAML: "reconnect:\n max_attempts: -1\n", wantSubstr: "reconnect.max_attempts must not be negative", }, { name: "negative interval_sec", reconnectYAML: "reconnect:\n max_attempts: 3\n interval_sec: -1\n", wantSubstr: "reconnect.interval_sec must not be negative", }, { name: "unlimited with zero interval", reconnectYAML: "reconnect:\n max_attempts: 0\n interval_sec: 0\n", wantSubstr: "reconnect.interval_sec must be positive", }, { name: "unlimited with negative interval", reconnectYAML: "reconnect:\n max_attempts: 0\n interval_sec: -5\n", wantSubstr: "reconnect.interval_sec must not be negative", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "node.yaml") yaml := "transport:\n edge_addr: \"localhost:9090\"\n token: \"tok\"\n" + tc.reconnectYAML if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } _, err := config.Load(f) if err == nil { t.Fatalf("expected error for %s", tc.name) } if !strings.Contains(err.Error(), tc.wantSubstr) { t.Fatalf("expected error containing %q, got %v", tc.wantSubstr, err) } }) } } // TestLoadEdge_NodeProviderPriorityOmittedDefaultsZero verifies that when // priority is omitted from a provider entry, it defaults to zero. func TestLoadEdge_NodeProviderPriorityOmittedDefaultsZero(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-p" alias: "n-p" token: "tok-p" providers: - id: "prov-p" type: "vllm" category: "api" endpoint: "http://127.0.0.1:8000/v1" 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 len(cfg.Nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(cfg.Nodes)) } if len(cfg.Nodes[0].Providers) != 1 { t.Fatalf("expected 1 provider, got %d", len(cfg.Nodes[0].Providers)) } if cfg.Nodes[0].Providers[0].Priority != 0 { t.Fatalf("expected default priority=0, got %d", cfg.Nodes[0].Providers[0].Priority) } } // TestLoadEdge_NodeProviderPriorityExplicitLoads verifies that an explicit // priority value is loaded correctly. func TestLoadEdge_NodeProviderPriorityExplicitLoads(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-p2" alias: "n-p2" token: "tok-p2" providers: - id: "prov-p2" type: "vllm" category: "api" endpoint: "http://127.0.0.1:8000/v1" models: - "model-a" capacity: 2 priority: 5 ` 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.Nodes[0].Providers[0].Priority != 5 { t.Fatalf("expected priority=5, got %d", cfg.Nodes[0].Providers[0].Priority) } } // TestLoadEdge_NodeProviderNegativePriorityRejected verifies that a negative // priority value is rejected during config load. func TestLoadEdge_NodeProviderNegativePriorityRejected(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml") yaml := ` server: listen: "0.0.0.0:9090" nodes: - id: "node-p3" alias: "n-p3" token: "tok-p3" providers: - id: "prov-p3" type: "vllm" category: "api" endpoint: "http://127.0.0.1:8000/v1" models: - "model-a" capacity: 2 priority: -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 error for negative priority") } if !strings.Contains(err.Error(), "priority must be non-negative") { t.Fatalf("expected error mentioning priority non-negative, got %v", err) } }