package rules_test import ( "testing" "testing/fstest" "github.com/netcell-it/deklarix/internal/rules" ) func TestLoadRejectsMissingID(t *testing.T) { fsys := fstest.MapFS{ "bad.yaml": &fstest.MapFile{Data: []byte("version: 1\ntitel: x\nschwere: hoch\n")}, } if _, err := rules.Load(fsys); err == nil { t.Fatal("expected error for rule without id, got nil") } } func TestLoadRejectsMissingVersion(t *testing.T) { fsys := fstest.MapFS{ "bad.yaml": &fstest.MapFile{Data: []byte("id: WK-999\ntitel: x\nschwere: hoch\n")}, } if _, err := rules.Load(fsys); err == nil { t.Fatal("expected error for rule without version, got nil") } } func TestLoadRejectsMissingJurisdiction(t *testing.T) { fsys := fstest.MapFS{ "bad.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\ntitel: x\nschwere: hoch\n")}, } if _, err := rules.Load(fsys); err == nil { t.Fatal("expected error for rule without land (jurisdiction), got nil") } } func TestLoadRejectsDuplicateID(t *testing.T) { fsys := fstest.MapFS{ "a.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\nland: DE\ntitel: x\nschwere: hoch\n")}, "b.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 2\nland: DE\ntitel: y\nschwere: hoch\n")}, } if _, err := rules.Load(fsys); err == nil { t.Fatal("expected error for duplicate rule id, got nil") } } func TestLoadIgnoresNonYAMLFiles(t *testing.T) { fsys := fstest.MapFS{ "a.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\nland: DE\ntitel: x\nschwere: hoch\n")}, "README.md": &fstest.MapFile{Data: []byte("not a rule")}, } got, err := rules.Load(fsys) if err != nil { t.Fatalf("Load: %v", err) } if len(got) != 1 { t.Fatalf("expected 1 rule, got %d", len(got)) } } func TestConditionMatches(t *testing.T) { yes := true no := false cases := []struct { name string cond rules.Condition fact rules.Facts want bool }{ { name: "consideration list matches", cond: rules.Condition{Consideration: []rules.Consideration{rules.ConsiderationPaid}}, fact: rules.Facts{Consideration: rules.ConsiderationPaid}, want: true, }, { name: "consideration list does not match", cond: rules.Condition{Consideration: []rules.Consideration{rules.ConsiderationPaid}}, fact: rules.Facts{Consideration: rules.ConsiderationNone}, want: false, }, { name: "disclosure present must match", cond: rules.Condition{DisclosurePresent: &no}, fact: rules.Facts{DisclosurePresent: true}, want: false, }, { name: "unset fields impose no constraint", cond: rules.Condition{}, fact: rules.Facts{Consideration: rules.ConsiderationUnclear}, want: true, }, { name: "all constraints must hold (AND)", cond: rules.Condition{ Consideration: []rules.Consideration{rules.ConsiderationPaid}, DisclosurePresent: &yes, DisclosureBeforeCut: &no, }, fact: rules.Facts{Consideration: rules.ConsiderationPaid, DisclosurePresent: true, DisclosureBeforeCut: false}, want: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := tc.cond.Matches(tc.fact); got != tc.want { t.Fatalf("Matches() = %v, want %v", got, tc.want) } }) } } func TestEvaluateFiltersByJurisdiction(t *testing.T) { rule := rules.Rule{ ID: "WK-TEST", Version: 1, Jurisdiction: "AT", Condition: rules.Condition{Consideration: []rules.Consideration{rules.ConsiderationPaid}}, Severity: rules.SeverityHigh, } fact := rules.Facts{Jurisdiction: "DE", Consideration: rules.ConsiderationPaid} findings, needsClarification := rules.Evaluate([]rules.Rule{rule}, fact) if needsClarification { t.Fatal("needsClarification = true, want false") } if len(findings) != 0 { t.Fatalf("expected no findings for a rule from a different jurisdiction, got %v", findings) } }