feat: Ergebnisdarstellung mit Herleitung (Schritt 4 der Baureihenfolge)
POST /antraege berechnet und speichert direkt beim Einreichen eine
append-only bewertung (Datenklasse, KI-VO-Einstufung, Anforderungen,
zulässige/ausgeschlossene Werkzeuge), GET /antraege/{id} zeigt sie
inklusive Herleitung und dem Pflicht-Hinweis, dass das System nicht
entscheidet, sondern nur vorbereitet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/store"
|
||||
)
|
||||
|
||||
func fullAntragForm() url.Values {
|
||||
@@ -122,6 +124,123 @@ func TestAntragDetailRejectsForeignUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// fullAntragForm liefert b1=ja (Datenklasse personenbezogen) und
|
||||
// c1..c3=nein (keine der verboten/hochrisiko/transparenzpflicht-
|
||||
// Varianten trifft zu) — die Einstufung fällt damit auf die
|
||||
// Auffangregel "minimal" zurück (siehe rules/kivo_einstufung.yaml).
|
||||
func TestAntragCreateComputesBewertung(t *testing.T) {
|
||||
fs := newFakeStore()
|
||||
s := newServer(t, fs)
|
||||
cookie := seedAccount(t, fs, "Test-Mandant", "test@example.com")
|
||||
|
||||
resp := postForm(t, s, cookie, "/antraege", fullAntragForm())
|
||||
if resp.Code != http.StatusSeeOther {
|
||||
t.Fatalf("status = %d, body: %s", resp.Code, resp.Body.String())
|
||||
}
|
||||
var antragID string
|
||||
for id := range fs.antraege {
|
||||
antragID = id
|
||||
}
|
||||
b, err := fs.GetLatestBewertungForAntrag(context.Background(), antragID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLatestBewertungForAntrag: %v", err)
|
||||
}
|
||||
if b.Datenklasse != "personenbezogen" {
|
||||
t.Errorf("Datenklasse = %q, want personenbezogen", b.Datenklasse)
|
||||
}
|
||||
if b.Einstufung != "minimal" {
|
||||
t.Errorf("Einstufung = %q, want minimal", b.Einstufung)
|
||||
}
|
||||
if b.Verboten {
|
||||
t.Error("Verboten = true, want false")
|
||||
}
|
||||
|
||||
detail := getWithCookie(t, s, cookie, resp.Header().Get("Location"))
|
||||
if !strings.Contains(detail.Body.String(), "Das System entscheidet nicht") {
|
||||
t.Errorf("expected the mandatory disclaimer on the detail page, got: %s", detail.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestAntragCreateVerbotenSkipsWerkzeugSearch prüft die K.-o.-Prüfung:
|
||||
// c3=ja mit c3_art=social_scoring löst "verboten" aus (Art. 5 KI-VO) —
|
||||
// dann darf keine Werkzeugsuche stattfinden (siehe CLAUDE.md,
|
||||
// Bewertungslogik).
|
||||
func TestAntragCreateVerbotenSkipsWerkzeugSearch(t *testing.T) {
|
||||
fs := newFakeStore()
|
||||
s := newServer(t, fs)
|
||||
cookie := seedAccount(t, fs, "Test-Mandant", "test@example.com")
|
||||
|
||||
form := fullAntragForm()
|
||||
form.Set("c3", "ja")
|
||||
form.Set("c3_art", "social_scoring")
|
||||
|
||||
resp := postForm(t, s, cookie, "/antraege", form)
|
||||
if resp.Code != http.StatusSeeOther {
|
||||
t.Fatalf("status = %d, body: %s", resp.Code, resp.Body.String())
|
||||
}
|
||||
var antragID string
|
||||
for id := range fs.antraege {
|
||||
antragID = id
|
||||
}
|
||||
b, err := fs.GetLatestBewertungForAntrag(context.Background(), antragID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLatestBewertungForAntrag: %v", err)
|
||||
}
|
||||
if !b.Verboten || b.Einstufung != "verboten" {
|
||||
t.Fatalf("Bewertung = %+v, want Verboten=true, Einstufung=verboten", b)
|
||||
}
|
||||
if len(b.ZulaessigeWerkzeuge) != 0 || len(b.AusgeschlosseneWerkzeuge) != 0 {
|
||||
t.Errorf("expected no werkzeug search for a verboten Bewertung, got %+v", b)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAntragCreateFiltersWerkzeugeByAnforderungen prüft den harten
|
||||
// Filter: personenbezogen verlangt avv_erforderlich — ein Werkzeug ohne
|
||||
// AVV wird ausgeschlossen und im Ergebnis mit Begründung genannt statt
|
||||
// stillschweigend wegzufallen, ein Werkzeug mit AVV bleibt zulässig.
|
||||
func TestAntragCreateFiltersWerkzeugeByAnforderungen(t *testing.T) {
|
||||
fs := newFakeStore()
|
||||
s := newServer(t, fs)
|
||||
cookie := seedAccount(t, fs, "Test-Mandant", "test@example.com")
|
||||
|
||||
fs.werkzeuge["werkzeug-ok"] = store.Werkzeug{
|
||||
ID: "werkzeug-ok", Name: "ChatGPT Enterprise",
|
||||
AVVVerfuegbar: true, TrainingStandard: true,
|
||||
}
|
||||
fs.werkzeuge["werkzeug-ohne-avv"] = store.Werkzeug{
|
||||
ID: "werkzeug-ohne-avv", Name: "Freies KI-Tool",
|
||||
AVVVerfuegbar: false, TrainingStandard: true,
|
||||
}
|
||||
|
||||
resp := postForm(t, s, cookie, "/antraege", fullAntragForm())
|
||||
if resp.Code != http.StatusSeeOther {
|
||||
t.Fatalf("status = %d, body: %s", resp.Code, resp.Body.String())
|
||||
}
|
||||
var antragID string
|
||||
for id := range fs.antraege {
|
||||
antragID = id
|
||||
}
|
||||
b, err := fs.GetLatestBewertungForAntrag(context.Background(), antragID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLatestBewertungForAntrag: %v", err)
|
||||
}
|
||||
if len(b.ZulaessigeWerkzeuge) != 1 || b.ZulaessigeWerkzeuge[0] != "werkzeug-ok" {
|
||||
t.Errorf("ZulaessigeWerkzeuge = %v, want [werkzeug-ok]", b.ZulaessigeWerkzeuge)
|
||||
}
|
||||
if len(b.AusgeschlosseneWerkzeuge) != 1 || b.AusgeschlosseneWerkzeuge[0].WerkzeugID != "werkzeug-ohne-avv" {
|
||||
t.Fatalf("AusgeschlosseneWerkzeuge = %+v, want [werkzeug-ohne-avv]", b.AusgeschlosseneWerkzeuge)
|
||||
}
|
||||
if got := b.AusgeschlosseneWerkzeuge[0].NichtErfuellt; len(got) != 1 || got[0] != "avv_erforderlich" {
|
||||
t.Errorf("NichtErfuellt = %v, want [avv_erforderlich]", got)
|
||||
}
|
||||
|
||||
detail := getWithCookie(t, s, cookie, resp.Header().Get("Location"))
|
||||
body := detail.Body.String()
|
||||
if !strings.Contains(body, "ChatGPT Enterprise") || !strings.Contains(body, "Freies KI-Tool") {
|
||||
t.Errorf("expected both werkzeuge (by name) on the detail page, got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBetreiberRedirectedAwayFromAntragPages(t *testing.T) {
|
||||
fs := newFakeStore()
|
||||
s := newServer(t, fs)
|
||||
|
||||
Reference in New Issue
Block a user