package config_test import ( "os" "path/filepath" "testing" "iop/packages/go/config" ) func TestStreamEvidenceGate_DirectStructZeroValueDefaults(t *testing.T) { var cfg config.EdgeOpenAIConf if got := cfg.StreamEvidenceGate.EffectiveMaxRequestFaultRecovery(); got != 3 { t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want 3", got) } if got := cfg.StreamEvidenceGate.EffectiveMaxStrategyFaultRecovery(); got != 3 { t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want 3", got) } if got := cfg.StreamEvidenceGate.EffectiveMaxIngressSnapshotBytes(); got != 16777216 { t.Errorf("EffectiveMaxIngressSnapshotBytes() = %d, want 16777216 (16 MiB)", got) } if err := cfg.StreamEvidenceGate.Validate(); err != nil { t.Errorf("Validate() unexpectedly failed on zero-value struct: %v", err) } } func TestStreamEvidenceGate_YAMLOmittedDefaults(t *testing.T) { yamlContent := ` openai: enabled: true provider_id: "test-provider" ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "edge.yaml") if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil { t.Fatalf("failed to write temp yaml: %v", err) } cfg, err := config.LoadEdge(tmpFile) if err != nil { t.Fatalf("LoadEdge failed: %v", err) } gate := cfg.OpenAI.StreamEvidenceGate if got := gate.EffectiveMaxRequestFaultRecovery(); got != 3 { t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want 3", got) } if got := gate.EffectiveMaxStrategyFaultRecovery(); got != 3 { t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want 3", got) } if got := gate.EffectiveMaxIngressSnapshotBytes(); got != 16777216 { t.Errorf("EffectiveMaxIngressSnapshotBytes() = %d, want 16777216", got) } } func TestStreamEvidenceGate_EnabledField_Load(t *testing.T) { tests := []struct { name string gateYAML string wantEnabled bool }{ { name: "omitted defaults to disabled", gateYAML: "", wantEnabled: false, }, { name: "explicit true enables the request runtime", gateYAML: ` stream_evidence_gate: enabled: true`, wantEnabled: true, }, { name: "explicit false stays disabled", gateYAML: ` stream_evidence_gate: enabled: false`, wantEnabled: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { yamlContent := "openai:\n enabled: true\n provider_id: \"test-provider\"" + tt.gateYAML + "\n" tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "edge.yaml") if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil { t.Fatalf("failed to write temp yaml: %v", err) } cfg, err := config.LoadEdge(tmpFile) if err != nil { t.Fatalf("LoadEdge failed: %v", err) } if got := cfg.OpenAI.StreamEvidenceGate.Enabled; got != tt.wantEnabled { t.Errorf("StreamEvidenceGate.Enabled = %v, want %v", got, tt.wantEnabled) } }) } } func TestStreamEvidenceGate_RequestFaultRecovery_Table(t *testing.T) { tests := []struct { name string totalYAML string expectErr bool wantTotal int wantStrategy int }{ { name: "explicit 0 disables fault recovery", totalYAML: "max_request_fault_recovery: 0", expectErr: false, wantTotal: 0, wantStrategy: 0, }, { name: "explicit 1", totalYAML: "max_request_fault_recovery: 1", expectErr: false, wantTotal: 1, wantStrategy: 1, }, { name: "explicit 3", totalYAML: "max_request_fault_recovery: 3", expectErr: false, wantTotal: 3, wantStrategy: 3, }, { name: "explicit 4 rejection", totalYAML: "max_request_fault_recovery: 4", expectErr: true, }, { name: "negative total rejection", totalYAML: "max_request_fault_recovery: -1", expectErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { yamlContent := ` openai: enabled: true provider_id: "test-provider" stream_evidence_gate: ` + tt.totalYAML + ` ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "edge.yaml") if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil { t.Fatalf("failed to write temp yaml: %v", err) } cfg, err := config.LoadEdge(tmpFile) if tt.expectErr { if err == nil { t.Errorf("LoadEdge succeeded unexpectedly, wanted error for %s", tt.totalYAML) } return } if err != nil { t.Fatalf("LoadEdge failed: %v", err) } gate := cfg.OpenAI.StreamEvidenceGate if got := gate.EffectiveMaxRequestFaultRecovery(); got != tt.wantTotal { t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want %d", got, tt.wantTotal) } if got := gate.EffectiveMaxStrategyFaultRecovery(); got != tt.wantStrategy { t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want %d", got, tt.wantStrategy) } }) } } func TestStreamEvidenceGate_StrategyFaultRecovery_Table(t *testing.T) { tests := []struct { name string gateYAML string expectErr bool wantTotal int wantStrategy int }{ { name: "total 3, strategy 0", gateYAML: ` max_request_fault_recovery: 3 max_strategy_fault_recovery: 0`, expectErr: false, wantTotal: 3, wantStrategy: 0, }, { name: "total 3, strategy 2", gateYAML: ` max_request_fault_recovery: 3 max_strategy_fault_recovery: 2`, expectErr: false, wantTotal: 3, wantStrategy: 2, }, { name: "total 3, strategy 3", gateYAML: ` max_request_fault_recovery: 3 max_strategy_fault_recovery: 3`, expectErr: false, wantTotal: 3, wantStrategy: 3, }, { name: "total 3, strategy 4 rejection (strategy > total)", gateYAML: ` max_request_fault_recovery: 3 max_strategy_fault_recovery: 4`, expectErr: true, }, { name: "total 1, strategy 2 rejection (strategy > total)", gateYAML: ` max_request_fault_recovery: 1 max_strategy_fault_recovery: 2`, expectErr: true, }, { name: "total 0, strategy 1 rejection (strategy > total)", gateYAML: ` max_request_fault_recovery: 0 max_strategy_fault_recovery: 1`, expectErr: true, }, { name: "strategy negative rejection", gateYAML: ` max_request_fault_recovery: 3 max_strategy_fault_recovery: -1`, expectErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { yamlContent := ` openai: enabled: true provider_id: "test-provider" stream_evidence_gate:` + tt.gateYAML + ` ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "edge.yaml") if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil { t.Fatalf("failed to write temp yaml: %v", err) } cfg, err := config.LoadEdge(tmpFile) if tt.expectErr { if err == nil { t.Errorf("LoadEdge succeeded unexpectedly, wanted error for %s", tt.gateYAML) } return } if err != nil { t.Fatalf("LoadEdge failed: %v", err) } gate := cfg.OpenAI.StreamEvidenceGate if got := gate.EffectiveMaxRequestFaultRecovery(); got != tt.wantTotal { t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want %d", got, tt.wantTotal) } if got := gate.EffectiveMaxStrategyFaultRecovery(); got != tt.wantStrategy { t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want %d", got, tt.wantStrategy) } }) } } // TestStreamGateFilterPolicy_Defaults verifies the per-filter effective // defaults (enabled=true, enforcement=blocking, capability=output., // hold=500, timeout=5000) that the runtime relies on when fields are omitted. func TestStreamGateFilterPolicy_Defaults(t *testing.T) { f := config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard} if !f.EffectiveEnabled() { t.Errorf("EffectiveEnabled() = false, want true by default") } if got := f.EffectiveEnforcement(); got != config.StreamGateFilterEnforcementBlocking { t.Errorf("EffectiveEnforcement() = %q, want blocking", got) } if got := f.EffectiveCapability(); got != "output.repeat_guard" { t.Errorf("EffectiveCapability() = %q, want output.repeat_guard", got) } if got := f.EffectiveHoldEvidenceRunes(); got != config.DefaultStreamGateFilterHoldEvidenceRunes { t.Errorf("EffectiveHoldEvidenceRunes() = %d, want %d", got, config.DefaultStreamGateFilterHoldEvidenceRunes) } if got := f.EffectiveTimeoutMS(); got != config.DefaultStreamGateFilterTimeoutMS { t.Errorf("EffectiveTimeoutMS() = %d, want %d", got, config.DefaultStreamGateFilterTimeoutMS) } if err := f.Validate(); err != nil { t.Errorf("Validate() unexpectedly failed on defaulted filter: %v", err) } } // TestStreamGateFilterPolicy_ValidateTable covers the config validation contract // for the semantic output filters: known kinds, enforcement values, selector // types/keys, and numeric bounds (S01/S02 config). func TestStreamGateFilterPolicy_ValidateTable(t *testing.T) { boolPtr := func(b bool) *bool { return &b } tests := []struct { name string filter config.StreamGateFilterPolicyConf expectErr bool }{ {name: "valid repeat guard", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard}}, {name: "valid schema gate observe_only", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterSchemaGate, Enforcement: config.StreamGateFilterEnforcementObserveOnly}}, {name: "valid provider error with selectors", filter: config.StreamGateFilterPolicyConf{ Filter: config.StreamGateFilterProviderError, Priority: 20, Selectors: []config.StreamGateFilterSelectorConf{ {Type: config.StreamGateFilterSelectorProvider, Key: "prov-a", Enabled: boolPtr(false)}, {Type: config.StreamGateFilterSelectorModel, Key: "ornith:35b", Enforcement: config.StreamGateFilterEnforcementObserveOnly}, }, }}, {name: "unknown kind", filter: config.StreamGateFilterPolicyConf{Filter: "malformed_guard"}, expectErr: true}, {name: "unknown enforcement", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Enforcement: "warn"}, expectErr: true}, {name: "hold runes over max", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, HoldEvidenceRunes: config.MaxStreamGateFilterHoldEvidenceRunes + 1}, expectErr: true}, {name: "negative hold runes", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, HoldEvidenceRunes: -1}, expectErr: true}, {name: "timeout over max", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, TimeoutMS: config.MaxStreamGateFilterTimeoutMS + 1}, expectErr: true}, {name: "negative priority", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Priority: -1}, expectErr: true}, {name: "unknown selector type", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Selectors: []config.StreamGateFilterSelectorConf{{Type: "region", Key: "kr"}}}, expectErr: true}, {name: "empty selector key", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Selectors: []config.StreamGateFilterSelectorConf{{Type: config.StreamGateFilterSelectorModel, Key: " "}}}, expectErr: true}, {name: "unknown selector enforcement", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Selectors: []config.StreamGateFilterSelectorConf{{Type: config.StreamGateFilterSelectorModel, Key: "m", Enforcement: "warn"}}}, expectErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.filter.Validate() if tt.expectErr && err == nil { t.Fatalf("Validate() succeeded, want error") } if !tt.expectErr && err != nil { t.Fatalf("Validate() failed: %v", err) } }) } } // TestStreamGateFilterPolicy_LoadAndDuplicateRejection loads a full filter policy // from YAML and confirms a duplicate filter kind is rejected at load time. func TestStreamGateFilterPolicy_LoadAndDuplicateRejection(t *testing.T) { valid := ` openai: enabled: true provider_id: "test-provider" stream_evidence_gate: enabled: true filters: - filter: repeat_guard enforcement: observe_only capability: output.repeat_guard - filter: provider_error priority: 20 selectors: - type: provider key: prov-a enabled: false ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "edge.yaml") if err := os.WriteFile(tmpFile, []byte(valid), 0644); err != nil { t.Fatalf("write yaml: %v", err) } cfg, err := config.LoadEdge(tmpFile) if err != nil { t.Fatalf("LoadEdge valid filters failed: %v", err) } filters := cfg.OpenAI.StreamEvidenceGate.Filters if len(filters) != 2 { t.Fatalf("loaded %d filters, want 2", len(filters)) } if filters[0].Filter != config.StreamGateFilterRepeatGuard || filters[0].EffectiveEnforcement() != config.StreamGateFilterEnforcementObserveOnly { t.Errorf("filter[0] = %+v", filters[0]) } if len(filters[1].Selectors) != 1 || filters[1].Selectors[0].Type != config.StreamGateFilterSelectorProvider { t.Errorf("filter[1] selectors = %+v", filters[1].Selectors) } dup := ` openai: enabled: true provider_id: "test-provider" stream_evidence_gate: enabled: true filters: - filter: repeat_guard - filter: repeat_guard ` dupFile := filepath.Join(tmpDir, "dup.yaml") if err := os.WriteFile(dupFile, []byte(dup), 0644); err != nil { t.Fatalf("write dup yaml: %v", err) } if _, err := config.LoadEdge(dupFile); err == nil { t.Fatalf("LoadEdge with duplicate filter kind succeeded, want error") } } func TestStreamEvidenceGate_IngressSnapshotBytes_TableFixture(t *testing.T) { maxAllowed := 16 * 1024 * 1024 // 16777216 tests := []struct { name string ingressYAML string expectErr bool wantBytes int }{ { name: "ingress limit - 1 (16777215)", ingressYAML: "max_ingress_snapshot_bytes: 16777215", expectErr: false, wantBytes: 16777215, }, { name: "ingress limit (16777216)", ingressYAML: "max_ingress_snapshot_bytes: 16777216", expectErr: false, wantBytes: maxAllowed, }, { name: "ingress limit + 1 (16777217) rejection", ingressYAML: "max_ingress_snapshot_bytes: 16777217", expectErr: true, }, { name: "ingress 1 byte boundary", ingressYAML: "max_ingress_snapshot_bytes: 1", expectErr: false, wantBytes: 1, }, { name: "ingress negative rejection", ingressYAML: "max_ingress_snapshot_bytes: -1", expectErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { yamlContent := ` openai: enabled: true provider_id: "test-provider" stream_evidence_gate: ` + tt.ingressYAML + ` ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "edge.yaml") if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil { t.Fatalf("failed to write temp yaml: %v", err) } cfg, err := config.LoadEdge(tmpFile) if tt.expectErr { if err == nil { t.Errorf("LoadEdge succeeded unexpectedly, wanted error for %s", tt.ingressYAML) } return } if err != nil { t.Fatalf("LoadEdge failed: %v", err) } gate := cfg.OpenAI.StreamEvidenceGate if got := gate.EffectiveMaxIngressSnapshotBytes(); got != tt.wantBytes { t.Errorf("EffectiveMaxIngressSnapshotBytes() = %d, want %d", got, tt.wantBytes) } }) } } func TestStreamGateFilterPolicy_Environment(t *testing.T) { var defaults config.StreamEvidenceGateConf if got := defaults.EffectiveEnvironment(); got != config.StreamGateEnvironmentDev { t.Fatalf("default environment=%q, want dev", got) } for _, environment := range []string{config.StreamGateEnvironmentDev, config.StreamGateEnvironmentDevCorp} { cfg := config.StreamEvidenceGateConf{Environment: environment} if err := cfg.Validate(); err != nil { t.Errorf("Validate(%q): %v", environment, err) } } if err := (config.StreamEvidenceGateConf{Environment: "production"}).Validate(); err == nil { t.Fatal("Validate(production) succeeded, want closed environment error") } }