package taskapi import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "gitea.perlbach24.de/scripte/nexarch/common/policyclient" ) // Steht fuer Core's POST /authorize-Vertrag ({"allowed": bool}); prueft die // echte Kette requireAuth -> policyclient -> 403/next, nicht nur einen // Datenbankeintrag. Die Ablehnungsfaelle brauchen keinen Store (nil), weil // die Pruefung vor jedem Zugriff greifen muss. func newFakePolicyAPI(t *testing.T, allow bool, seen *[]map[string]string) *httptest.Server { t.Helper() return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/authorize" || r.Header.Get("X-Service-Token") == "" { t.Fatalf("unerwartete anfrage: %s %s", r.Method, r.URL.Path) } var body map[string]string _ = json.NewDecoder(r.Body).Decode(&body) if seen != nil { *seen = append(*seen, body) } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]bool{"allowed": allow}) })) } func policyMux(t *testing.T, policyURL string) *http.ServeMux { t.Helper() mux := http.NewServeMux() NewServer(nil, "acme").WithPolicy(policyclient.New(policyURL, "test-token")).RegisterRoutes(mux) return mux } func authed(req *http.Request) *http.Request { req.Header.Set("X-User-Id", "u1") req.Header.Set("X-Tenant-Slug", "acme") return req } func TestHandleList_PolicyDenies_Rejected(t *testing.T) { var seen []map[string]string policyAPI := newFakePolicyAPI(t, false, &seen) defer policyAPI.Close() mux := policyMux(t, policyAPI.URL) rec := httptest.NewRecorder() mux.ServeHTTP(rec, authed(httptest.NewRequest(http.MethodGet, "/tasks", nil))) if rec.Code != http.StatusForbidden { t.Fatalf("erwartet 403 ohne berechtigung, bekam %d: %s", rec.Code, rec.Body.String()) } if len(seen) != 1 || seen[0]["permission"] != "workflow.task.read" || seen[0]["tenant"] != "acme" { t.Fatalf("falsche policy-anfrage: %+v", seen) } } func TestHandleSetStatus_PolicyDenies_Rejected(t *testing.T) { var seen []map[string]string policyAPI := newFakePolicyAPI(t, false, &seen) defer policyAPI.Close() mux := policyMux(t, policyAPI.URL) req := authed(httptest.NewRequest(http.MethodPatch, "/tasks/t-1/status", strings.NewReader(`{"status":"done"}`))) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusForbidden { t.Fatalf("erwartet 403 ohne berechtigung, bekam %d: %s", rec.Code, rec.Body.String()) } if len(seen) != 1 || seen[0]["permission"] != "workflow.task.write" { t.Fatalf("falsche policy-anfrage: %+v", seen) } } func TestHandleList_PolicyUnavailable_FailsClosed(t *testing.T) { policyAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer policyAPI.Close() mux := policyMux(t, policyAPI.URL) rec := httptest.NewRecorder() mux.ServeHTTP(rec, authed(httptest.NewRequest(http.MethodGet, "/tasks", nil))) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("erwartet 503 bei ausfall des berechtigungsdienstes, bekam %d", rec.Code) } } func TestHandleList_PolicyNotAskedWithoutIdentity(t *testing.T) { var seen []map[string]string policyAPI := newFakePolicyAPI(t, true, &seen) defer policyAPI.Close() mux := policyMux(t, policyAPI.URL) rec := httptest.NewRecorder() mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/tasks", nil)) if rec.Code != http.StatusUnauthorized { t.Fatalf("erwartet 401 ohne identitaet, bekam %d", rec.Code) } if len(seen) != 0 { t.Fatalf("policy darf ohne identitaet nicht befragt werden: %+v", seen) } } func TestHandleList_PolicyAllows_Passes(t *testing.T) { srv, _ := newTestServer(t) policyAPI := newFakePolicyAPI(t, true, nil) defer policyAPI.Close() mux := http.NewServeMux() srv.WithPolicy(policyclient.New(policyAPI.URL, "test-token")).RegisterRoutes(mux) rec := httptest.NewRecorder() mux.ServeHTTP(rec, authed(httptest.NewRequest(http.MethodGet, "/tasks", nil))) if rec.Code != http.StatusOK { t.Fatalf("erwartet 200 mit berechtigung, bekam %d: %s", rec.Code, rec.Body.String()) } } func TestPolicyAsked_WithRoleFromHeader_DefaultUser(t *testing.T) { var seen []map[string]string policyAPI := newFakePolicyAPI(t, false, &seen) defer policyAPI.Close() mux := policyMux(t, policyAPI.URL) mux.ServeHTTP(httptest.NewRecorder(), authed(httptest.NewRequest(http.MethodGet, "/tasks", nil))) admin := authed(httptest.NewRequest(http.MethodGet, "/tasks", nil)) admin.Header.Set("X-User-Role", "tenant_admin") mux.ServeHTTP(httptest.NewRecorder(), admin) if len(seen) != 2 || seen[0]["role"] != "user" || seen[1]["role"] != "tenant_admin" { t.Fatalf("falsche rollen in policy-anfragen: %+v", seen) } }