package rules_test import ( "testing" "github.com/netcell-it/deklarix/internal/rules" ) func loadRealRegelwerke(t *testing.T) (rules.DatenklasseRegelwerk, rules.EinstufungRegelwerk, rules.AnforderungsRegelwerk) { t.Helper() fsys := realRulesFS(t) dk, err := rules.LoadDatenklasse(fsys, "datenklasse.yaml") if err != nil { t.Fatalf("LoadDatenklasse: %v", err) } ei, err := rules.LoadEinstufung(fsys, "kivo_einstufung.yaml") if err != nil { t.Fatalf("LoadEinstufung: %v", err) } an, err := rules.LoadAnforderungen(fsys, "anforderungen.yaml") if err != nil { t.Fatalf("LoadAnforderungen: %v", err) } return dk, ei, an } func TestEvaluateDatenklasseOnlyOeffentlich(t *testing.T) { dk, _, _ := loadRealRegelwerke(t) antworten := rules.Antworten{"b1": "nein", "b2": "nein", "b3": "nein", "b4": "nein", "b5": "nein", "b6": "nein", "b7": "ja"} got := rules.EvaluateDatenklasse(dk, antworten) if got != "oeffentlich" { t.Fatalf("EvaluateDatenklasse = %q, want oeffentlich", got) } } func TestEvaluateDatenklasseHighestWins(t *testing.T) { dk, _, _ := loadRealRegelwerke(t) // b1 (personenbezogen) UND b2 (besondere_kategorie) beide ja -> // besondere_kategorie hat den hoeheren Rang und muss gewinnen. antworten := rules.Antworten{"b1": "ja", "b2": "ja", "b7": "nein"} got := rules.EvaluateDatenklasse(dk, antworten) if got != "besondere_kategorie" { t.Fatalf("EvaluateDatenklasse = %q, want besondere_kategorie", got) } } func TestEvaluateDatenklasseUnsicherZaehltWieJa(t *testing.T) { dk, _, _ := loadRealRegelwerke(t) antworten := rules.Antworten{"b3": "unsicher"} got := rules.EvaluateDatenklasse(dk, antworten) if got != "berufsgeheimnis" { t.Fatalf("EvaluateDatenklasse = %q, want berufsgeheimnis (unsicher zaehlt wie ja)", got) } } func TestEvaluateDatenklasseFallsBackToInternWhenNothingMatches(t *testing.T) { dk, _, _ := loadRealRegelwerke(t) antworten := rules.Antworten{"b1": "nein", "b2": "nein", "b3": "nein", "b4": "nein", "b5": "nein", "b6": "nein", "b7": "nein"} got := rules.EvaluateDatenklasse(dk, antworten) if got != "intern" { t.Fatalf("EvaluateDatenklasse = %q, want intern (konservativer Fallback)", got) } } func TestEvaluateEinstufungVerboten(t *testing.T) { _, ei, _ := loadRealRegelwerke(t) antworten := rules.Antworten{"c3": true, "c3_art": "social_scoring"} got, err := rules.EvaluateEinstufung(ei, antworten) if err != nil { t.Fatalf("EvaluateEinstufung: %v", err) } if got != "verboten" { t.Fatalf("EvaluateEinstufung = %q, want verboten", got) } if !rules.IstVerboten(got) { t.Error("IstVerboten sollte true liefern") } } func TestEvaluateEinstufungHochrisiko(t *testing.T) { _, ei, _ := loadRealRegelwerke(t) antworten := rules.Antworten{"c2": true, "c2_folge": "kreditwuerdigkeit", "c3": false} got, err := rules.EvaluateEinstufung(ei, antworten) if err != nil { t.Fatalf("EvaluateEinstufung: %v", err) } if got != "hochrisiko" { t.Fatalf("EvaluateEinstufung = %q, want hochrisiko", got) } } func TestEvaluateEinstufungTransparenzpflicht(t *testing.T) { _, ei, _ := loadRealRegelwerke(t) antworten := rules.Antworten{"c1": true, "c2": false, "c3": false, "c5": false} got, err := rules.EvaluateEinstufung(ei, antworten) if err != nil { t.Fatalf("EvaluateEinstufung: %v", err) } if got != "transparenzpflicht" { t.Fatalf("EvaluateEinstufung = %q, want transparenzpflicht", got) } } func TestEvaluateEinstufungMinimalFallback(t *testing.T) { _, ei, _ := loadRealRegelwerke(t) antworten := rules.Antworten{"c1": false, "c2": false, "c3": false, "c4": false, "c5": true} got, err := rules.EvaluateEinstufung(ei, antworten) if err != nil { t.Fatalf("EvaluateEinstufung: %v", err) } if got != "minimal" { t.Fatalf("EvaluateEinstufung = %q, want minimal", got) } if rules.IstVerboten(got) { t.Error("IstVerboten sollte fuer minimal false liefern") } } func TestDeriveAnforderungenPersonenbezogen(t *testing.T) { _, _, an := loadRealRegelwerke(t) got := rules.DeriveAnforderungen(an, "personenbezogen", "minimal") want := map[string]bool{"avv_erforderlich": true, "kein_training_auf_eingabe": true, "loeschfrist_max_tage": true} gotSet := map[string]bool{} for _, id := range got { gotSet[id] = true } for id := range want { if !gotSet[id] { t.Errorf("expected Anforderung %q for personenbezogen/minimal, got %v", id, got) } } if gotSet["eu_verarbeitung"] { t.Error("eu_verarbeitung sollte fuer personenbezogen (ohne berufsgeheimnis/besondere_kategorie) nicht ausgeloest werden") } } func TestDeriveAnforderungenHochrisikoAddsAufsichtUndDsfa(t *testing.T) { _, _, an := loadRealRegelwerke(t) got := rules.DeriveAnforderungen(an, "oeffentlich", "hochrisiko") gotSet := map[string]bool{} for _, id := range got { gotSet[id] = true } if !gotSet["menschliche_aufsicht"] || !gotSet["dsfa_erforderlich"] { t.Errorf("expected menschliche_aufsicht und dsfa_erforderlich fuer hochrisiko, got %v", got) } } func TestErfuelltAnforderungAVV(t *testing.T) { if rules.ErfuelltAnforderung("avv_erforderlich", rules.WerkzeugEigenschaften{AVVVerfuegbar: false}) { t.Error("erwartet: nicht erfuellt ohne AVV") } if !rules.ErfuelltAnforderung("avv_erforderlich", rules.WerkzeugEigenschaften{AVVVerfuegbar: true}) { t.Error("erwartet: erfuellt mit AVV") } } func TestErfuelltAnforderungUnbekannteAnforderungIstUnkritisch(t *testing.T) { if !rules.ErfuelltAnforderung("menschliche_aufsicht", rules.WerkzeugEigenschaften{}) { t.Error("Prozess-Anforderungen duerfen ein Werkzeug nicht hart aussortieren") } } func TestFilterWerkzeugeSortsOutMissingAVV(t *testing.T) { kandidaten := []rules.WerkzeugKandidat{ {ID: "mit-avv", Eigenschaften: rules.WerkzeugEigenschaften{AVVVerfuegbar: true, Verarbeitungsort: "EU", TrainingStandard: true}}, {ID: "ohne-avv", Eigenschaften: rules.WerkzeugEigenschaften{AVVVerfuegbar: false, Verarbeitungsort: "EU", TrainingStandard: true}}, } zulaessig, ausgeschlossen := rules.FilterWerkzeuge(kandidaten, []string{"avv_erforderlich"}) if len(zulaessig) != 1 || zulaessig[0] != "mit-avv" { t.Fatalf("zulaessig = %v, want [mit-avv]", zulaessig) } if len(ausgeschlossen) != 1 || ausgeschlossen[0].WerkzeugID != "ohne-avv" { t.Fatalf("ausgeschlossen = %v, want genau ohne-avv", ausgeschlossen) } if len(ausgeschlossen[0].NichtErfuellt) != 1 || ausgeschlossen[0].NichtErfuellt[0] != "avv_erforderlich" { t.Fatalf("NichtErfuellt = %v, want [avv_erforderlich]", ausgeschlossen[0].NichtErfuellt) } } func TestFilterWerkzeugeNoRequirementsAllowsEverything(t *testing.T) { kandidaten := []rules.WerkzeugKandidat{{ID: "x"}, {ID: "y"}} zulaessig, ausgeschlossen := rules.FilterWerkzeuge(kandidaten, nil) if len(zulaessig) != 2 || len(ausgeschlossen) != 0 { t.Fatalf("zulaessig=%v ausgeschlossen=%v, want beide zulaessig", zulaessig, ausgeschlossen) } } func TestParseAntwortenRoundTrip(t *testing.T) { raw := []byte(`{"b1":"ja","c2":true,"c2_folge":"bildung"}`) a, err := rules.ParseAntworten(raw) if err != nil { t.Fatalf("ParseAntworten: %v", err) } if a["b1"] != "ja" || a["c2_folge"] != "bildung" { t.Fatalf("ParseAntworten = %+v, unerwartete Werte", a) } } func TestParseAntwortenEmpty(t *testing.T) { a, err := rules.ParseAntworten(nil) if err != nil { t.Fatalf("ParseAntworten: %v", err) } if len(a) != 0 { t.Fatalf("ParseAntworten(nil) = %+v, want empty", a) } }