package caseoverviewapi import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "gitea.perlbach24.de/scripte/nexarch/common/testdb" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/caseobj" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/caseoverview" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/migrate" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/reminder" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/task" ) 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 newTestServer(t *testing.T) (*http.ServeMux, *caseobj.Service, *reminder.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) } eng, err := caseobj.NewEngineWithCase(pool) if err != nil { t.Fatalf("engine anlegen: %v", err) } caseSvc := caseobj.NewService(pool, eng) tasks := task.NewStore(pool) reminders := reminder.NewStore(pool, tasks) overview := caseoverview.NewService(caseSvc, reminders) mux := http.NewServeMux() NewServer(overview).RegisterRoutes(mux) return mux, caseSvc, reminders } // X-User-Id fehlt: alle Routen antworten 401, wie bei caseapi/backchannelapi // etabliert. func TestRoutes_RequireUserID(t *testing.T) { mux, _, _ := newTestServer(t) req := httptest.NewRequest(http.MethodGet, "/cases", nil) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("erwartet 401 ohne X-User-Id, bekommen %d", rec.Code) } } // AK1: GET /cases liefert die per Query-Parameter gefilterte Liste. func TestHandleList_FiltersByQueryParams(t *testing.T) { mux, cases, _ := newTestServer(t) ctx := context.Background() if _, err := cases.Create(ctx, "", "alice", "creator", nil); err != nil { t.Fatalf("akte anlegen: %v", err) } if _, err := cases.Create(ctx, "", "bob", "creator", nil); err != nil { t.Fatalf("akte anlegen: %v", err) } req := httptest.NewRequest(http.MethodGet, "/cases?responsible=alice", nil) req.Header.Set("X-User-Id", "tester") rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("erwartet 200, bekommen %d: %s", rec.Code, rec.Body.String()) } if !contains(rec.Body.String(), `"Responsible":"alice"`) { t.Fatalf("antwort enthaelt nicht die gefilterte akte: %s", rec.Body.String()) } if contains(rec.Body.String(), `"Responsible":"bob"`) { t.Fatalf("antwort enthaelt ungefilterte akte: %s", rec.Body.String()) } } // AK4: POST /reminders/{id}/move mit target=done ruft VOR-03s Complete auf. func TestHandleMove_ToDone_CompletesReminder(t *testing.T) { mux, _, reminders := newTestServer(t) ctx := context.Background() r, err := reminders.Create(ctx, reminder.Target{ModuleName: "dms", ObjectType: "document", ObjectID: "doc-http"}, time.Now().Add(time.Hour), "test", "alice") if err != nil { t.Fatalf("wiedervorlage anlegen: %v", err) } req := httptest.NewRequest(http.MethodPost, "/reminders/"+r.ID+"/move", strings.NewReader(`{"target":"done"}`)) req.Header.Set("X-User-Id", "tester") rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Fatalf("erwartet 204, bekommen %d: %s", rec.Code, rec.Body.String()) } got, err := reminders.Get(ctx, r.ID) if err != nil { t.Fatalf("wiedervorlage erneut lesen: %v", err) } if got.Status != reminder.StatusDone { t.Fatalf("erwartet status erledigt, bekommen %q", got.Status) } } func contains(haystack, needle string) bool { return len(haystack) >= len(needle) && (func() bool { for i := 0; i+len(needle) <= len(haystack); i++ { if haystack[i:i+len(needle)] == needle { return true } } return false })() }