package task import ( "context" "os" "path/filepath" "testing" "gitea.perlbach24.de/scripte/nexarch/common/testdb" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/migrate" ) func findModuleRoot(t *testing.T) string { t.Helper() dir, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } for { if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { return dir } parent := filepath.Dir(dir) if parent == dir { t.Fatal("go.mod nicht gefunden") } dir = parent } } func newTestStore(t *testing.T) *Store { t.Helper() dsn := os.Getenv("TEST_TENANT_DSN") if dsn == "" { t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest uebersprungen") } ctx := context.Background() pool := testdb.Pool(t, dsn) migrations, err := migrate.Load(filepath.Join(findModuleRoot(t), "migrations", "tenant")) if err != nil { t.Fatalf("migrationen laden: %v", err) } if _, err := migrate.Up(ctx, pool, migrations); err != nil { t.Fatalf("migrationen anwenden: %v", err) } return NewStore(pool) } // TestCreate_AssignPerson prueft Akzeptanzkriterium 1: Zuweisung an eine // Person. func TestCreate_AssignPerson(t *testing.T) { s := newTestStore(t) ctx := context.Background() id, err := s.Create(ctx, Task{Title: "Rechnung pruefen", AssigneeType: AssigneePerson, AssigneeID: "user-42", Priority: PriorityHigh}) if err != nil { t.Fatalf("create: %v", err) } got, err := s.Get(ctx, id) if err != nil { t.Fatalf("get: %v", err) } if got.AssigneeType != AssigneePerson || got.AssigneeID != "user-42" { t.Fatalf("zuweisung falsch: %+v", got) } if got.Priority != PriorityHigh { t.Fatalf("prioritaet falsch: %v", got.Priority) } } // TestCreate_AssignGroup_ExactlyOneTask prueft Akzeptanzkriterium 1 // (Zuweisung an Gruppe) und Pruefung 1: die Zuweisung an eine Gruppe // erzeugt genau eine Aufgabe, nicht eine je fiktivem Mitglied. func TestCreate_AssignGroup_ExactlyOneTask(t *testing.T) { s := newTestStore(t) ctx := context.Background() id, err := s.Create(ctx, Task{Title: "Postkorb sichten", AssigneeType: AssigneeGroup, AssigneeID: "team-buchhaltung"}) if err != nil { t.Fatalf("create: %v", err) } tasks, err := s.ByAssignee(ctx, AssigneeGroup, "team-buchhaltung") if err != nil { t.Fatalf("by assignee: %v", err) } if len(tasks) != 1 { t.Fatalf("erwartet genau 1 Aufgabe fuer die Gruppe, habe %d", len(tasks)) } if tasks[0].ID != id { t.Fatalf("falsche aufgabe zurueckgegeben") } if tasks[0].Priority != PriorityNormal { t.Fatalf("standard-prioritaet nicht normal: %v", tasks[0].Priority) } } // TestCreate_LinkedToForeignModuleObject prueft Akzeptanzkriterium 3 und // Pruefung 2: Verknuepfung mit einem Testobjekt aus einem fiktiven Modul. func TestCreate_LinkedToForeignModuleObject(t *testing.T) { s := newTestStore(t) ctx := context.Background() id, err := s.Create(ctx, Task{ Title: "Dokument freigeben", AssigneeType: AssigneePerson, AssigneeID: "user-7", References: []Reference{ {ModuleName: "dms-fake", ObjectType: "document", ObjectID: "doc-123"}, }, }) if err != nil { t.Fatalf("create: %v", err) } got, err := s.Get(ctx, id) if err != nil { t.Fatalf("get: %v", err) } if len(got.References) != 1 || got.References[0].ModuleName != "dms-fake" || got.References[0].ObjectID != "doc-123" { t.Fatalf("referenz nicht wie erwartet: %+v", got.References) } byRef, err := s.ByReference(ctx, "dms-fake", "document", "doc-123") if err != nil { t.Fatalf("by reference: %v", err) } if len(byRef) != 1 || byRef[0].ID != id { t.Fatalf("rueckkanal ueber referenz liefert falsches ergebnis: %+v", byRef) } } // TestCreate_RejectsInvalidInput prueft Randfaelle: unzulaessiger // assignee_type/priority, leere Pflichtfelder. func TestCreate_RejectsInvalidInput(t *testing.T) { s := newTestStore(t) ctx := context.Background() cases := []struct { name string task Task }{ {"ungueltiger assignee_type", Task{Title: "x", AssigneeType: "team", AssigneeID: "a"}}, {"ungueltige prioritaet", Task{Title: "x", AssigneeType: AssigneePerson, AssigneeID: "a", Priority: "urgent"}}, {"leere assignee_id", Task{Title: "x", AssigneeType: AssigneePerson, AssigneeID: ""}}, {"leerer title", Task{Title: "", AssigneeType: AssigneePerson, AssigneeID: "a"}}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if _, err := s.Create(ctx, c.task); err == nil { t.Fatal("erwartet fehler, habe nil") } }) } } // TestTenantIsolation prueft Pruefung 3: zwei getrennte Mandanten-Schemas // sehen gegenseitig keine Aufgaben (testdb.Pool legt je Aufruf ein // eigenes Schema an, wie bei den anderen workflow-Paketen). func TestTenantIsolation(t *testing.T) { s1 := newTestStore(t) s2 := newTestStore(t) ctx := context.Background() id, err := s1.Create(ctx, Task{Title: "Mandant A", AssigneeType: AssigneePerson, AssigneeID: "user-1"}) if err != nil { t.Fatalf("create in tenant 1: %v", err) } if _, err := s2.Get(ctx, id); err == nil { t.Fatal("aufgabe aus fremdem mandanten war sichtbar") } else if err != ErrNotFound { t.Fatalf("erwartet ErrNotFound, habe %v", err) } tasks, err := s2.ByAssignee(ctx, AssigneePerson, "user-1") if err != nil { t.Fatalf("by assignee in tenant 2: %v", err) } if len(tasks) != 0 { t.Fatalf("mandant 2 sieht aufgaben von mandant 1: %+v", tasks) } }