Ebene 2 ("Antrag stellen") als einseitiges Formular statt mehrseitigem
Assistenten — jede zusätzliche Seite kostet Zeit, und laut Spezifikation
wird ein Antrag umgangen, wenn er länger als fünf Minuten dauert.
- GET /antraege/neu: Fragebogen-Formular (Abschnitte A-D exakt nach
Spezifikation). Adaptive Folgefragen (C2: welche Art von Entscheidung,
C3: welche Art von Erkennung) werden rein per CSS :has() ein-/
ausgeblendet, kein JavaScript nötig — visuell mit Chromium-Screenshots
verifiziert (unchecked vs. checked).
- POST /antraege: legt an und reicht direkt ein (entwurf->eingereicht in
einem Schritt, kein Zwischenspeichern als Entwurf für v1). Die
antworten-JSON nutzt exakt dieselben Fakten-Schlüssel wie
rules/*.yaml (b1-b7, c1-c5, c2_folge, c3_art) — Schritt 3 kann sie
direkt auswerten, ohne Felder umzubenennen. "Unsicher" wird bewusst
NICHT zu "ja" normalisiert (das ist eine Auswertungsregel für Schritt
3, keine Speicherregel) — der Antrag hält fest, was der Mitarbeiter
tatsächlich geantwortet hat.
- GET /antraege: eigene Anträge (Ebene 2 sieht nur eigene, nicht die
des ganzen Mandanten — dafür neue Store-Methode
ListAntraegeForUser, getrennt von ListAntraegeForAccount für den
späteren Ebene-3-Posteingang).
- GET /antraege/{id}: Detail, fremder Antrag liefert 404 (nicht 403,
gleiches Muster wie überall sonst im Projekt).
- betreiber-Rolle wird von allen Antrags-Routen weggeleitet (Ebene 5
ist technisch getrennt vom Mandantenbereich).
rules/OPEN.md Punkt 3 (fehlende Fragebogen-Unterscheidung für die
"verboten"-Varianten) damit geklärt: c3_art existiert jetzt mit den
bereits in rules/kivo_einstufung.yaml erwarteten Werten.
Volle Testsuite inkl. echter Postgres-Tests grün; End-to-End gegen
einen laufenden Server verifiziert (Antrag anlegen, antworten-JSON in
der DB korrekt, Detail-/Listenansicht, Mandantentrennung).
134 lines
4.0 KiB
Go
134 lines
4.0 KiB
Go
package store_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/netcell-it/deklarix/internal/store"
|
|
)
|
|
|
|
func testUserID(t *testing.T, s *store.Store, accountID string) string {
|
|
t.Helper()
|
|
u, err := s.CreateUser(context.Background(), accountID, "mitarbeiter-"+accountID+"@example.com", "hash", "mitarbeiter")
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
return u.ID
|
|
}
|
|
|
|
func TestAntragCRUD(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
accID := testAccountID(t, s)
|
|
userID := testUserID(t, s, accID)
|
|
abt, err := s.CreateAbteilung(ctx, accID, "Vertrieb")
|
|
if err != nil {
|
|
t.Fatalf("CreateAbteilung: %v", err)
|
|
}
|
|
|
|
a, err := s.CreateAntrag(ctx, accID, userID, &abt.ID, "Angebotstexte generieren")
|
|
if err != nil {
|
|
t.Fatalf("CreateAntrag: %v", err)
|
|
}
|
|
if a.Status != "entwurf" {
|
|
t.Fatalf("Status = %q, want entwurf", a.Status)
|
|
}
|
|
if a.AbteilungID == nil || *a.AbteilungID != abt.ID {
|
|
t.Fatalf("AbteilungID = %v, want %q", a.AbteilungID, abt.ID)
|
|
}
|
|
|
|
got, err := s.GetAntrag(ctx, a.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetAntrag: %v", err)
|
|
}
|
|
if got.Titel != "Angebotstexte generieren" {
|
|
t.Fatalf("Titel = %q, unerwartet", got.Titel)
|
|
}
|
|
|
|
updated, err := s.UpdateAntragFelder(ctx, a.ID, "Angebotstexte generieren", "KI schreibt Angebotstexte", "Fertiger Text zur Freigabe", "gelegentlich", []byte(`{"b1":"nein"}`))
|
|
if err != nil {
|
|
t.Fatalf("UpdateAntragFelder: %v", err)
|
|
}
|
|
if updated.Beschreibung != "KI schreibt Angebotstexte" || updated.Haeufigkeit != "gelegentlich" {
|
|
t.Fatalf("UpdateAntragFelder = %+v, unerwartete Werte", updated)
|
|
}
|
|
|
|
if err := s.SetAntragStatus(ctx, a.ID, "eingereicht"); err != nil {
|
|
t.Fatalf("SetAntragStatus: %v", err)
|
|
}
|
|
got, err = s.GetAntrag(ctx, a.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetAntrag nach Statuswechsel: %v", err)
|
|
}
|
|
if got.Status != "eingereicht" {
|
|
t.Fatalf("Status nach SetAntragStatus = %q, want eingereicht", got.Status)
|
|
}
|
|
}
|
|
|
|
func TestGetAntragNotFound(t *testing.T) {
|
|
s := openTestStore(t)
|
|
_, err := s.GetAntrag(context.Background(), "00000000-0000-0000-0000-000000000000")
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestSetAntragStatusNotFound(t *testing.T) {
|
|
s := openTestStore(t)
|
|
err := s.SetAntragStatus(context.Background(), "00000000-0000-0000-0000-000000000000", "eingereicht")
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestListAntraegeForUserOnlyReturnsOwnAntraege(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
accID := testAccountID(t, s)
|
|
userA := testUserID(t, s, accID)
|
|
userB, err := s.CreateUser(ctx, accID, "zweiter-mitarbeiter-"+accID+"@example.com", "hash", "mitarbeiter")
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
|
|
if _, err := s.CreateAntrag(ctx, accID, userA, nil, "Antrag von A"); err != nil {
|
|
t.Fatalf("CreateAntrag (A): %v", err)
|
|
}
|
|
if _, err := s.CreateAntrag(ctx, accID, userB.ID, nil, "Antrag von B"); err != nil {
|
|
t.Fatalf("CreateAntrag (B): %v", err)
|
|
}
|
|
|
|
listA, err := s.ListAntraegeForUser(ctx, userA)
|
|
if err != nil {
|
|
t.Fatalf("ListAntraegeForUser: %v", err)
|
|
}
|
|
if len(listA) != 1 || listA[0].Titel != "Antrag von A" {
|
|
t.Fatalf("ListAntraegeForUser(A) = %+v, want exactly Antrag von A", listA)
|
|
}
|
|
}
|
|
|
|
func TestListAntraegeForAccountIsolatesTenants(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
accA := testAccountID(t, s)
|
|
accB := testAccountID(t, s)
|
|
userA := testUserID(t, s, accA)
|
|
userB := testUserID(t, s, accB)
|
|
|
|
if _, err := s.CreateAntrag(ctx, accA, userA, nil, "Antrag A"); err != nil {
|
|
t.Fatalf("CreateAntrag (A): %v", err)
|
|
}
|
|
if _, err := s.CreateAntrag(ctx, accB, userB, nil, "Antrag B"); err != nil {
|
|
t.Fatalf("CreateAntrag (B): %v", err)
|
|
}
|
|
|
|
listA, err := s.ListAntraegeForAccount(ctx, accA)
|
|
if err != nil {
|
|
t.Fatalf("ListAntraegeForAccount (A): %v", err)
|
|
}
|
|
if len(listA) != 1 || listA[0].Titel != "Antrag A" {
|
|
t.Fatalf("ListAntraegeForAccount (A) = %+v, want exactly Antrag A", listA)
|
|
}
|
|
}
|