feat: Posteingang und Entscheiden für die Fachebene (Schritt 5 der Baureihenfolge)
Ebene 3 (Rollen verantwortlicher/pruefer) bekommt GET /faelle
(Posteingang aller offenen Anträge des Mandanten) und GET/POST
/faelle/{id} zum Entscheiden. Eine Entscheidung friert Regelwerk-,
Katalog- und den vollständigen Werkzeugdatensatz ein, verlangt eine
Begründung bei Abweichung vom abgeleiteten Vorschlag, erlaubt bei
Genehmigung nur ein durch die Bewertung zulässiges Werkzeug und
protokolliert die Entscheidung im Audit-Log. entscheidung ist
append-only wie bewertung.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
115
internal/store/entscheidung_test.go
Normal file
115
internal/store/entscheidung_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/store"
|
||||
)
|
||||
|
||||
func testAntragMitBewertung(t *testing.T, s *store.Store, accountID, userID string) (store.Antrag, store.Bewertung) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
antrag, err := s.CreateAntrag(ctx, accountID, userID, nil, "Testantrag")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateAntrag: %v", err)
|
||||
}
|
||||
b, err := s.CreateBewertung(ctx, testBewertungInput(antrag.ID))
|
||||
if err != nil {
|
||||
t.Fatalf("CreateBewertung: %v", err)
|
||||
}
|
||||
return antrag, b
|
||||
}
|
||||
|
||||
func TestEntscheidungCreateAndGetLatest(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
userID := testUserID(t, s, accID)
|
||||
antrag, b := testAntragMitBewertung(t, s, accID, userID)
|
||||
w, err := s.CreateWerkzeug(ctx, testWerkzeugInput(nil, "ChatGPT Enterprise"))
|
||||
if err != nil {
|
||||
t.Fatalf("CreateWerkzeug: %v", err)
|
||||
}
|
||||
gueltigBis := time.Now().Add(365 * 24 * time.Hour).Truncate(time.Millisecond)
|
||||
|
||||
e, err := s.CreateEntscheidung(ctx, store.EntscheidungInput{
|
||||
AntragID: antrag.ID, BewertungID: b.ID, EntscheiderUserID: userID,
|
||||
Entscheidung: "genehmigt", WerkzeugID: &w.ID, WerkzeugSnapshot: &w,
|
||||
Begruendung: "", GueltigBis: &gueltigBis,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateEntscheidung: %v", err)
|
||||
}
|
||||
if e.Entscheidung != "genehmigt" || e.WerkzeugSnapshot == nil || e.WerkzeugSnapshot.Name != "ChatGPT Enterprise" {
|
||||
t.Fatalf("CreateEntscheidung = %+v, unerwartete Werte", e)
|
||||
}
|
||||
|
||||
got, err := s.GetLatestEntscheidungForAntrag(ctx, antrag.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLatestEntscheidungForAntrag: %v", err)
|
||||
}
|
||||
if got.ID != e.ID {
|
||||
t.Fatalf("GetLatestEntscheidungForAntrag returned a different row than CreateEntscheidung")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntscheidungOhneWerkzeugHatKeinenSnapshot(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
userID := testUserID(t, s, accID)
|
||||
antrag, b := testAntragMitBewertung(t, s, accID, userID)
|
||||
|
||||
e, err := s.CreateEntscheidung(ctx, store.EntscheidungInput{
|
||||
AntragID: antrag.ID, BewertungID: b.ID, EntscheiderUserID: userID,
|
||||
Entscheidung: "abgelehnt", Begruendung: "Kein geeignetes Werkzeug im Katalog",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateEntscheidung: %v", err)
|
||||
}
|
||||
if e.WerkzeugID != nil || e.WerkzeugSnapshot != nil {
|
||||
t.Fatalf("expected no werkzeug/snapshot for an abgelehnt Entscheidung, got %+v", e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestEntscheidungForAntragNotFound(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
userID := testUserID(t, s, accID)
|
||||
antrag, err := s.CreateAntrag(ctx, accID, userID, nil, "Ohne Entscheidung")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateAntrag: %v", err)
|
||||
}
|
||||
|
||||
_, err = s.GetLatestEntscheidungForAntrag(ctx, antrag.ID)
|
||||
if !errors.Is(err, store.ErrNotFound) {
|
||||
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntscheidungIsAppendOnly(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
userID := testUserID(t, s, accID)
|
||||
antrag, b := testAntragMitBewertung(t, s, accID, userID)
|
||||
e, err := s.CreateEntscheidung(ctx, store.EntscheidungInput{
|
||||
AntragID: antrag.ID, BewertungID: b.ID, EntscheiderUserID: userID, Entscheidung: "rueckfrage",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateEntscheidung: %v", err)
|
||||
}
|
||||
|
||||
_, err = s.Pool.Exec(ctx, `UPDATE entscheidung SET entscheidung = 'genehmigt' WHERE id = $1`, e.ID)
|
||||
if err == nil {
|
||||
t.Fatal("expected UPDATE on entscheidung to be rejected by the append-only trigger")
|
||||
}
|
||||
_, err = s.Pool.Exec(ctx, `DELETE FROM entscheidung WHERE id = $1`, e.ID)
|
||||
if err == nil {
|
||||
t.Fatal("expected DELETE on entscheidung to be rejected by the append-only trigger")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user