package taskapi import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "gitea.perlbach24.de/scripte/nexarch/common/testdb" "gitea.perlbach24.de/scripte/nexarch/workflow/internal/migrate" "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) (*Server, *task.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) } store := task.NewStore(pool) return NewServer(store, "acme"), store } func TestHandleList_RequiresAuth(t *testing.T) { srv, _ := newTestServer(t) mux := http.NewServeMux() srv.RegisterRoutes(mux) req := httptest.NewRequest(http.MethodGet, "/tasks", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Fatalf("erwartet 401 ohne X-User-Id/X-Tenant-Slug, bekam %d", w.Code) } } func TestHandleList_RejectsForeignTenant(t *testing.T) { srv, _ := newTestServer(t) mux := http.NewServeMux() srv.RegisterRoutes(mux) req := httptest.NewRequest(http.MethodGet, "/tasks", nil) req.Header.Set("X-User-Id", "u1") req.Header.Set("X-Tenant-Slug", "fremd") w := httptest.NewRecorder() mux.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Fatalf("erwartet 403 bei fremdem tenant, bekam %d", w.Code) } } func TestHandleList_ReturnsTasksForOwnTenant(t *testing.T) { srv, store := newTestServer(t) ctx := context.Background() id, err := store.Create(ctx, task.Task{Title: "API-Liste-Test", AssigneeType: task.AssigneePerson, AssigneeID: "u1"}) if err != nil { t.Fatalf("create: %v", err) } mux := http.NewServeMux() srv.RegisterRoutes(mux) req := httptest.NewRequest(http.MethodGet, "/tasks", nil) req.Header.Set("X-User-Id", "u1") req.Header.Set("X-Tenant-Slug", "acme") w := httptest.NewRecorder() mux.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("erwartet 200, bekam %d: %s", w.Code, w.Body.String()) } var tasks []task.Task if err := json.NewDecoder(w.Body).Decode(&tasks); err != nil { t.Fatalf("decode: %v", err) } found := false for _, tk := range tasks { if tk.ID == id { found = true } } if !found { t.Fatal("erstellte aufgabe fehlt in der antwort") } } func TestHandleSetStatus_DragAndDropUpdatesViaTSK01(t *testing.T) { srv, store := newTestServer(t) ctx := context.Background() id, err := store.Create(ctx, task.Task{Title: "Drag-Test", AssigneeType: task.AssigneePerson, AssigneeID: "u1"}) if err != nil { t.Fatalf("create: %v", err) } mux := http.NewServeMux() srv.RegisterRoutes(mux) body := strings.NewReader(`{"status":"in_progress"}`) req := httptest.NewRequest(http.MethodPatch, "/tasks/"+id+"/status", body) req.Header.Set("X-User-Id", "u1") req.Header.Set("X-Tenant-Slug", "acme") w := httptest.NewRecorder() mux.ServeHTTP(w, req) if w.Code != http.StatusNoContent { t.Fatalf("erwartet 204, bekam %d: %s", w.Code, w.Body.String()) } // Nachweis: der Status wurde tatsaechlich ueber TSK-01 (task.Store) // zurueckgeschrieben, nicht nur im Handler simuliert. got, err := store.Get(ctx, id) if err != nil { t.Fatalf("get: %v", err) } if got.Status != task.StatusInProgress { t.Fatalf("erwartet in_progress nach drag&drop, war %q", got.Status) } } func TestHandleSetStatus_UnknownTask(t *testing.T) { srv, _ := newTestServer(t) mux := http.NewServeMux() srv.RegisterRoutes(mux) body := strings.NewReader(`{"status":"done"}`) req := httptest.NewRequest(http.MethodPatch, "/tasks/00000000-0000-0000-0000-000000000000/status", body) req.Header.Set("X-User-Id", "u1") req.Header.Set("X-Tenant-Slug", "acme") w := httptest.NewRecorder() mux.ServeHTTP(w, req) if w.Code != http.StatusNotFound { t.Fatalf("erwartet 404, bekam %d", w.Code) } } func TestHandleSetStatus_InvalidStatus(t *testing.T) { srv, store := newTestServer(t) ctx := context.Background() id, err := store.Create(ctx, task.Task{Title: "Ungueltig-API-Test", AssigneeType: task.AssigneePerson, AssigneeID: "u1"}) if err != nil { t.Fatalf("create: %v", err) } mux := http.NewServeMux() srv.RegisterRoutes(mux) body := strings.NewReader(`{"status":"erfunden"}`) req := httptest.NewRequest(http.MethodPatch, "/tasks/"+id+"/status", body) req.Header.Set("X-User-Id", "u1") req.Header.Set("X-Tenant-Slug", "acme") w := httptest.NewRecorder() mux.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("erwartet 400, bekam %d", w.Code) } }