150 lines
5.1 KiB
Go
150 lines
5.1 KiB
Go
package apiserver
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
|
|
)
|
|
|
|
func newTestServer() (*Server, *auth.TokenIssuer) {
|
|
issuer := auth.NewTokenIssuer("test-secret-nur-fuer-tests")
|
|
return NewServer(issuer), issuer
|
|
}
|
|
|
|
func withAuthCookie(req *http.Request, token string) *http.Request {
|
|
req.AddCookie(&http.Cookie{Name: auth.CookieName, Value: token})
|
|
return req
|
|
}
|
|
|
|
// Akzeptanzkriterium 1: API unter versioniertem Pfad erreichbar.
|
|
func TestHandleV1_RegistersUnderVersionedPath(t *testing.T) {
|
|
srv, issuer := newTestServer()
|
|
srv.HandleV1("/things", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
token, err := issuer.Issue("user-1", "acme")
|
|
if err != nil {
|
|
t.Fatalf("issue: %v", err)
|
|
}
|
|
|
|
req := withAuthCookie(httptest.NewRequest(http.MethodGet, "/api/v1/things", nil), token)
|
|
rec := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", rec.Code)
|
|
}
|
|
}
|
|
|
|
// Akzeptanzkriterium 2 + Pruefung 1 (Stichprobe): mehrere Endpunkte liefern
|
|
// bei fehlerhafter Anfrage dasselbe Fehlerschema.
|
|
func TestErrorFormat_ConsistentAcrossEndpoints(t *testing.T) {
|
|
srv, _ := newTestServer()
|
|
srv.HandleV1("/endpunkt-a", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
|
srv.HandleV1("/endpunkt-b", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
|
|
|
for _, path := range []string{"/api/v1/endpunkt-a", "/api/v1/endpunkt-b"} {
|
|
req := httptest.NewRequest(http.MethodGet, path, nil) // ohne cookie -> 401
|
|
rec := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("%s: status = %d, want 401", path, rec.Code)
|
|
}
|
|
var body errorBody
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("%s: fehlerantwort nicht im erwarteten json-schema: %v (body: %s)", path, err, rec.Body.String())
|
|
}
|
|
if body.Error.Code == "" || body.Error.Message == "" {
|
|
t.Fatalf("%s: erwartet nicht-leeren code/message, habe %+v", path, body)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Akzeptanzkriterium 3 + Pruefung 2: Middleware-Kette setzt Tenant-/
|
|
// Benutzerkontext zuverlaessig, nachweislich fuer mehrere Endpunkte.
|
|
func TestMiddleware_SetsRequestContextForEveryEndpoint(t *testing.T) {
|
|
srv, issuer := newTestServer()
|
|
|
|
var gotA, gotB RequestContext
|
|
srv.HandleV1("/kontext-a", func(w http.ResponseWriter, r *http.Request) {
|
|
gotA, _ = FromContext(r.Context())
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
srv.HandleV1("/kontext-b", func(w http.ResponseWriter, r *http.Request) {
|
|
gotB, _ = FromContext(r.Context())
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
token, err := issuer.Issue("user-42", "tenant-x")
|
|
if err != nil {
|
|
t.Fatalf("issue: %v", err)
|
|
}
|
|
|
|
for path, got := range map[string]*RequestContext{"/api/v1/kontext-a": &gotA, "/api/v1/kontext-b": &gotB} {
|
|
req := withAuthCookie(httptest.NewRequest(http.MethodGet, path, nil), token)
|
|
rec := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("%s: status = %d, want 200", path, rec.Code)
|
|
}
|
|
if got.UserID != "user-42" || got.TenantSlug != "tenant-x" {
|
|
t.Fatalf("%s: request-context unerwartet: %+v", path, *got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Akzeptanzkriterium 1 + Pruefung 3: eine neue v2-Route laesst sich anlegen,
|
|
// ohne v1 zu beeintraechtigen.
|
|
func TestVersioning_V2DoesNotAffectV1(t *testing.T) {
|
|
srv, issuer := newTestServer()
|
|
srv.HandleV1("/things", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("v1-antwort"))
|
|
})
|
|
srv.Handle("v2", "/things", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("v2-antwort"))
|
|
})
|
|
|
|
token, err := issuer.Issue("user-1", "acme")
|
|
if err != nil {
|
|
t.Fatalf("issue: %v", err)
|
|
}
|
|
|
|
reqV1 := withAuthCookie(httptest.NewRequest(http.MethodGet, "/api/v1/things", nil), token)
|
|
recV1 := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(recV1, reqV1)
|
|
if recV1.Body.String() != "v1-antwort" {
|
|
t.Fatalf("v1 antwort = %q, want v1-antwort", recV1.Body.String())
|
|
}
|
|
|
|
reqV2 := withAuthCookie(httptest.NewRequest(http.MethodGet, "/api/v2/things", nil), token)
|
|
recV2 := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(recV2, reqV2)
|
|
if recV2.Body.String() != "v2-antwort" {
|
|
t.Fatalf("v2 antwort = %q, want v2-antwort", recV2.Body.String())
|
|
}
|
|
|
|
// v1 nach dem Anlegen von v2 erneut pruefen — unveraendert.
|
|
reqV1Again := withAuthCookie(httptest.NewRequest(http.MethodGet, "/api/v1/things", nil), token)
|
|
recV1Again := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(recV1Again, reqV1Again)
|
|
if recV1Again.Body.String() != "v1-antwort" {
|
|
t.Fatalf("v1 antwort nach v2-anlage = %q, want weiterhin v1-antwort", recV1Again.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestAuthAndTenantContext_RejectsInvalidToken(t *testing.T) {
|
|
srv, _ := newTestServer()
|
|
srv.HandleV1("/geschuetzt", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
|
|
|
req := withAuthCookie(httptest.NewRequest(http.MethodGet, "/api/v1/geschuetzt", nil), "kaputtes.token.hier")
|
|
rec := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("status = %d, want 401", rec.Code)
|
|
}
|
|
}
|