Compare commits

...
Author SHA1 Message Date
simandClaude Sonnet 5.5 0e625c5381 ai-app: Test an unkritische Stelle (Konflikt mit Nachbar-PRs vermeiden)
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Waiting to run
DB-Integrationstests / db-tests (pull_request) Waiting to run
Supply-Chain-Scan (Go) / govulncheck (pull_request) Waiting to run
Supply-Chain-Scan (npm) / npm-audit (pull_request) Waiting to run
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Waiting to run
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Waiting to run
CI / Core-Modul (Build/Vet/Test) (push) Waiting to run
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Waiting to run
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Waiting to run
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Waiting to run
CI / Web (dms-browser) (push) Waiting to run
CI / Web (pdf-viewer) (push) Waiting to run
CI / Web (shl) (push) Waiting to run
Supply-Chain-Scan (Go) / govulncheck (push) Waiting to run
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Waiting to run
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Waiting to run
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Waiting to run
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Waiting to run
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Waiting to run
CI / Web (dms-browser) (pull_request) Waiting to run
CI / Web (pdf-viewer) (pull_request) Waiting to run
CI / Web (shl) (pull_request) Waiting to run
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Blocked by required conditions
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 23:28:41 +01:00
simandClaude Sonnet 5.5 0ef867daba ai-app: serverWriteTimeout vor main() (vermeidet Konflikt mit Nachbar-PRs)
CI / Core-Modul (Build/Vet/Test) (push) Waiting to run
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Waiting to run
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Waiting to run
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Waiting to run
CI / Web (dms-browser) (push) Waiting to run
CI / Web (pdf-viewer) (push) Waiting to run
CI / Web (shl) (push) Waiting to run
Supply-Chain-Scan (Go) / govulncheck (push) Waiting to run
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 23:25:40 +01:00
simandClaude Sonnet 5.5 e659fdaa59 ai-app: WriteTimeout ueber dem Provider-Timeout
CI / Core-Modul (Build/Vet/Test) (push) Waiting to run
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Waiting to run
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Waiting to run
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Waiting to run
CI / Web (dms-browser) (push) Waiting to run
CI / Web (pdf-viewer) (push) Waiting to run
CI / Web (shl) (push) Waiting to run
Supply-Chain-Scan (Go) / govulncheck (push) Waiting to run
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Der Server kappte Antworten nach 15s, Inferenzaufrufe (Ollama bis 120s)
liefen dadurch ins Leere. Real gefunden bei /suggest/title und
/suggest/correspondent mit gemma4:26b (leere Antwort, curl-Code 000).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 23:22:53 +01:00
patrick 998aaf964c Merge pull request 'connect: DATEV-Export in connect-app registrieren' (#429) from feat/connect-datev-registrieren into main
CI / Web (pdf-viewer) (push) Failing after 40s
CI / Web (shl) (push) Failing after 8s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 43s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 29s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 54s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
2026-10-30 00:24:20 +01:00
patrickandClaude Sonnet 5.5 976878136f Merge main in feat/connect-datev-registrieren
CI / Web (shl) (pull_request) Failing after 8s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 46s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 19m25s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 18m53s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 44s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m20s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 19m26s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 44s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 1m1s
CI / Web (pdf-viewer) (push) Failing after 37s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (shl) (push) Failing after 9s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 44s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
CI / Web (dms-browser) (pull_request) Failing after 55s
CI / Web (pdf-viewer) (pull_request) Failing after 41s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 00:24:14 +01:00
patrick dc8f6476ab Merge pull request 'connect/statusapi: Rolle aus dem Token an die Policy' (#428) from fix/statusapi-rolle into main
CI / Core-Modul (Build/Vet/Test) (push) Successful in 49s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 1m1s
CI / Web (pdf-viewer) (push) Failing after 50s
CI / Web (shl) (push) Failing after 9s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 50s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
2026-10-30 00:23:50 +01:00
patrickandClaude Sonnet 5.5 753af35888 connect: DATEV-Export in connect-app registriert, mit Rechtepruefung
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 42s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 36s
CI / Web (shl) (push) Failing after 7s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 43s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 46s
CI / Web (dms-browser) (pull_request) Failing after 58s
CI / Web (pdf-viewer) (pull_request) Failing after 43s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 18m38s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 15s
CI / Web (dms-browser) (push) Failing after 51s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
CI / Web (shl) (pull_request) Failing after 9s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m16s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 18m46s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 18m59s
/datev/exports braucht connect.datev.write (POST) bzw. connect.datev.read
(GET) mit der Rolle aus X-User-Role. Registriert nur mit
NEXARCH_CONNECT_APP_ARCHIVE_URL; die DATEV-Uebertragung bleibt aus.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 00:23:17 +01:00
patrickandClaude Sonnet 5.5 422e96cc20 connect/statusapi: Rolle aus X-User-Role an die Policy
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 5s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 41s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 42s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
CI / Web (dms-browser) (push) Failing after 57s
CI / Web (pdf-viewer) (push) Failing after 40s
CI / Web (shl) (push) Failing after 8s
CI / Web (shl) (pull_request) Failing after 9s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 18m52s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
CI / Web (dms-browser) (pull_request) Failing after 50s
CI / Web (pdf-viewer) (pull_request) Failing after 38s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 36s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m17s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 18m56s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Failing after 29s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 18m24s
Bisher fragte statusapi mit fester Rolle user. bearerauth setzt X-User-Role
aus dem Token; ohne Header bleibt es bei user.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 23:42:16 +01:00
7 changed files with 152 additions and 6 deletions

No files matched your search

+4 -1
View File
@@ -202,6 +202,9 @@ func build(pool *pgxpool.Pool, cfg config, prov provider.Provider, workerID stri
return wiring{mux: mux, queue: q, worker: worker, quotaStore: quotaStore}
}
// Muss ueber dem Provider-Timeout (120s) liegen, sonst kappt der Server langsame Inferenz-Antworten.
const serverWriteTimeout = 130 * time.Second
func main() {
cfg, err := loadConfig(os.Getenv)
if err != nil {
@@ -226,7 +229,7 @@ func main() {
Handler: w.mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
WriteTimeout: serverWriteTimeout,
IdleTimeout: 60 * time.Second,
}
+6
View File
@@ -87,6 +87,12 @@ func TestLoadConfig_Defaults(t *testing.T) {
}
}
func TestServerWriteTimeoutCoversProviderTimeout(t *testing.T) {
if serverWriteTimeout <= 120*time.Second {
t.Fatalf("serverWriteTimeout = %v, muss ueber dem Provider-Timeout von 120s liegen", serverWriteTimeout)
}
}
func newTestPool(t *testing.T) *pgxpool.Pool {
t.Helper()
dsn := os.Getenv("TEST_TENANT_DSN")
+25
View File
@@ -18,9 +18,12 @@ import (
"gitea.perlbach24.de/scripte/nexarch/common/bearerauth"
"gitea.perlbach24.de/scripte/nexarch/common/moduleauth"
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/archiveclient"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/connectorsetupapi"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/credential"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/crypto"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/datevapi"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/datevexport"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/dms2import"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/dms2importapi"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/fieldmapping"
@@ -56,6 +59,25 @@ func buildPolicyClient() *policyclient.Client {
return policyclient.New(policyURL, token)
}
// buildDatevExports liefert nil ohne NEXARCH_CONNECT_APP_ARCHIVE_URL; dann ist
// /datev/exports nicht registriert. Die DATEV-Uebertragung ist hier nicht verdrahtet.
func buildDatevExports(pool *pgxpool.Pool) *datevexport.Store {
archiveURL := os.Getenv("NEXARCH_CONNECT_APP_ARCHIVE_URL")
if archiveURL == "" {
log.Print("connect-app: NEXARCH_CONNECT_APP_ARCHIVE_URL fehlt, /datev/exports nicht registriert")
return nil
}
token := os.Getenv("NEXARCH_CONNECT_APP_ARCHIVE_SERVICE_TOKEN")
if token == "" {
log.Fatal("connect-app: NEXARCH_CONNECT_APP_ARCHIVE_SERVICE_TOKEN ist erforderlich, wenn NEXARCH_CONNECT_APP_ARCHIVE_URL gesetzt ist")
}
return &datevexport.Store{
Pool: pool,
Archive: archiveclient.New(archiveURL, token),
CallbackURL: os.Getenv("NEXARCH_CONNECT_APP_DATEV_CALLBACK_URL"),
}
}
func main() {
if !killswitch.Enabled() {
log.Printf("connect-app: deaktiviert (%s=false), kein Start", killswitch.EnvEnabled)
@@ -98,6 +120,9 @@ func main() {
statusapi.NewServer(syncjob.NewStore(pool)).WithPolicy(policy).RegisterRoutes(mux)
dms2importapi.NewServer(mappings, dms2import.NewStore(pool)).RegisterRoutes(mux)
connectorsetupapi.NewServer(credentials, mappings).WithPolicy(policy).RegisterRoutes(mux)
if exports := buildDatevExports(pool); exports != nil {
datevapi.NewServer(exports, nil).WithPolicy(policy).RegisterRoutes(mux)
}
srv := &http.Server{
Addr: addr,
+33 -2
View File
@@ -9,6 +9,7 @@ import (
"net/http"
"time"
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/datevexport"
"gitea.perlbach24.de/scripte/nexarch/connect/internal/datevtransfer"
)
@@ -16,6 +17,12 @@ import (
type Server struct {
Exports *datevexport.Store
Transfers *datevtransfer.Store
policy *policyclient.Client
}
func (s *Server) WithPolicy(client *policyclient.Client) *Server {
s.policy = client
return s
}
func NewServer(exports *datevexport.Store, transfers *datevtransfer.Store) *Server {
@@ -23,8 +30,32 @@ func NewServer(exports *datevexport.Store, transfers *datevtransfer.Store) *Serv
}
func (s *Server) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /datev/exports", s.requireIdentity(s.handleTrigger))
mux.HandleFunc("GET /datev/exports", s.requireIdentity(s.handleList))
mux.HandleFunc("POST /datev/exports", s.requireIdentity(s.requirePermission("connect.datev.write", s.handleTrigger)))
mux.HandleFunc("GET /datev/exports", s.requireIdentity(s.requirePermission("connect.datev.read", s.handleList)))
}
func (s *Server) requirePermission(permission string, next func(w http.ResponseWriter, r *http.Request, tenantSlug string)) func(w http.ResponseWriter, r *http.Request, tenantSlug string) {
return func(w http.ResponseWriter, r *http.Request, tenantSlug string) {
if s.policy != nil {
allowed, err := s.policy.Allowed(r.Context(), tenantSlug, roleOf(r), permission)
if err != nil {
writeError(w, http.StatusServiceUnavailable, "berechtigungsdienst nicht erreichbar")
return
}
if !allowed {
writeError(w, http.StatusForbidden, "keine berechtigung")
return
}
}
next(w, r, tenantSlug)
}
}
func roleOf(r *http.Request) string {
if role := r.Header.Get("X-User-Role"); role != "" {
return role
}
return "user"
}
func (s *Server) requireIdentity(next func(w http.ResponseWriter, r *http.Request, tenantSlug string)) http.HandlerFunc {
@@ -0,0 +1,46 @@
package datevapi
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
)
func TestPolicy_PermissionAndRoleReachCore(t *testing.T) {
type ask struct{ role, permission string }
var got []ask
policyAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body struct {
Role string `json:"role"`
Permission string `json:"permission"`
}
_ = json.NewDecoder(r.Body).Decode(&body)
got = append(got, ask{body.Role, body.Permission})
_ = json.NewEncoder(w).Encode(map[string]bool{"allowed": false})
}))
defer policyAPI.Close()
mux := http.NewServeMux()
(&Server{}).WithPolicy(policyclient.New(policyAPI.URL, "tok")).RegisterRoutes(mux)
for _, c := range []struct{ method, role string }{{"GET", "tenant_admin"}, {"POST", ""}} {
req := httptest.NewRequest(c.method, "/datev/exports", strings.NewReader("{}"))
req.Header.Set("X-User-Id", "u1")
req.Header.Set("X-Tenant-Slug", "acme")
if c.role != "" {
req.Header.Set("X-User-Role", c.role)
}
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("%s: erwartet 403, bekam %d", c.method, rec.Code)
}
}
want := []ask{{"tenant_admin", "connect.datev.read"}, {"user", "connect.datev.write"}}
if len(got) != 2 || got[0] != want[0] || got[1] != want[1] {
t.Fatalf("erwartet %v, bekam %v", want, got)
}
}
+9 -3
View File
@@ -22,8 +22,7 @@ type Server struct {
// WithPolicy aktiviert RBAC-12: echte Rechtepruefung gegen Core (POST
// /authorize), zusaetzlich zur bestehenden X-User-Id/X-Tenant-Slug-Pruefung.
// Rolle ist mangels eigenem Rollenkonzept in Connect fest "user" (Uebergang,
// wie beim restlichen Header-basierten Identitaetsmodell dieses Pakets).
// Die Rolle kommt aus X-User-Role (setzt bearerauth aus dem Token), sonst "user".
func (s *Server) WithPolicy(client *policyclient.Client) *Server {
s.policy = client
return s
@@ -40,7 +39,7 @@ func (s *Server) RegisterRoutes(mux *http.ServeMux) {
func (s *Server) requirePermission(permission string, next func(w http.ResponseWriter, r *http.Request, tenantSlug string)) func(w http.ResponseWriter, r *http.Request, tenantSlug string) {
return func(w http.ResponseWriter, r *http.Request, tenantSlug string) {
if s.policy != nil {
allowed, err := s.policy.Allowed(r.Context(), tenantSlug, "user", permission)
allowed, err := s.policy.Allowed(r.Context(), tenantSlug, roleOf(r), permission)
if err != nil {
writeError(w, http.StatusServiceUnavailable, "berechtigungsdienst nicht erreichbar")
return
@@ -54,6 +53,13 @@ func (s *Server) requirePermission(permission string, next func(w http.ResponseW
}
}
func roleOf(r *http.Request) string {
if role := r.Header.Get("X-User-Role"); role != "" {
return role
}
return "user"
}
func (s *Server) requireIdentity(next func(w http.ResponseWriter, r *http.Request, tenantSlug string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-User-Id") == "" {
@@ -60,3 +60,32 @@ func TestHandleList_PolicyAllows_Passes(t *testing.T) {
t.Fatalf("erwartet 200 mit berechtigung, bekam %d: %s", rec.Code, rec.Body.String())
}
}
func TestHandleList_RoleFromHeaderReachesPolicy(t *testing.T) {
var got []string
policyAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body struct {
Role string `json:"role"`
}
_ = json.NewDecoder(r.Body).Decode(&body)
got = append(got, body.Role)
_ = json.NewEncoder(w).Encode(map[string]bool{"allowed": true})
}))
defer policyAPI.Close()
s := newTestServer(t).WithPolicy(policyclient.New(policyAPI.URL, "test-token"))
mux := http.NewServeMux()
s.RegisterRoutes(mux)
for _, role := range []string{"tenant_admin", ""} {
req := httptest.NewRequest("GET", "/connectors/status", nil)
req.Header.Set("X-User-Id", "u1")
req.Header.Set("X-Tenant-Slug", "acme")
if role != "" {
req.Header.Set("X-User-Role", role)
}
mux.ServeHTTP(httptest.NewRecorder(), req)
}
if len(got) != 2 || got[0] != "tenant_admin" || got[1] != "user" {
t.Fatalf("erwartet [tenant_admin user], bekam %v", got)
}
}