Files
nexarch/internal/openapi/openapi_test.go
T

143 lines
4.8 KiB
Go

package openapi
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/internal/apiserver"
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
)
func newTestServerWithRoutes() (*apiserver.Server, *auth.TokenIssuer) {
issuer := auth.NewTokenIssuer("test-secret-nur-fuer-tests")
srv := apiserver.NewServer(issuer)
srv.HandleV1("/things", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv.HandleV1("/other", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
return srv, issuer
}
func testEntries() []Entry {
return []Entry{
{Path: "/api/v1/things", Method: "GET", Summary: "Dinge auflisten", ExpectStatus: http.StatusOK},
{Path: "/api/v1/other", Method: "GET", Summary: "Anderes abrufen", ExpectStatus: http.StatusOK},
}
}
// Akzeptanzkriterium 1: das Dokument deckt alle tatsaechlich registrierten
// Endpunkte ab.
func TestCheckNoDrift_PassesWhenDocumentMatchesRoutes(t *testing.T) {
srv, _ := newTestServerWithRoutes()
if err := CheckNoDrift(srv, testEntries()); err != nil {
t.Fatalf("erwartet keinen drift, habe: %v", err)
}
}
// Akzeptanzkriterium 2 + Pruefung 1: automatisierter Abgleich schlaegt bei
// Drift fehl — hier absichtlich eine Route aus dem Dokument entfernt, waehrend
// der Server sie weiterhin registriert hat.
func TestCheckNoDrift_FailsWhenRouteMissingFromDocument(t *testing.T) {
srv, _ := newTestServerWithRoutes()
entries := []Entry{testEntries()[0]} // "/api/v1/other" absichtlich entfernt
err := CheckNoDrift(srv, entries)
if err == nil {
t.Fatal("erwartet drift-fehler, da eine registrierte route nicht dokumentiert ist")
}
driftErr, ok := err.(*ErrDrift)
if !ok {
t.Fatalf("erwartet *ErrDrift, habe %T", err)
}
if len(driftErr.MissingInDocument) != 1 || driftErr.MissingInDocument[0] != "/api/v1/other" {
t.Fatalf("erwartet '/api/v1/other' als nicht dokumentiert, habe: %v", driftErr.MissingInDocument)
}
}
// Symmetrischer Fall: Dokument nennt eine Route, die es beim Server gar
// nicht (mehr) gibt (z.B. nach Entfernen eines Endpunkts im Code).
func TestCheckNoDrift_FailsWhenDocumentedRouteNoLongerExists(t *testing.T) {
srv, _ := newTestServerWithRoutes()
entries := append(testEntries(), Entry{Path: "/api/v1/entfernt", Method: "GET", ExpectStatus: http.StatusOK})
err := CheckNoDrift(srv, entries)
if err == nil {
t.Fatal("erwartet drift-fehler fuer dokumentierte, aber nicht registrierte route")
}
driftErr := err.(*ErrDrift)
if len(driftErr.MissingAsRoute) != 1 || driftErr.MissingAsRoute[0] != "/api/v1/entfernt" {
t.Fatalf("erwartet '/api/v1/entfernt' als nicht (mehr) registriert, habe: %v", driftErr.MissingAsRoute)
}
}
// Akzeptanzkriterium 2 + Pruefung 2: Beispielaufrufe aus dem Dokument
// werden GEGEN DIE ECHTE API (httptest, echte Middleware-Kette) ausgefuehrt.
func TestExamples_ExecuteSuccessfullyAgainstRealServer(t *testing.T) {
srv, issuer := newTestServerWithRoutes()
token, err := issuer.Issue("user-1", "acme")
if err != nil {
t.Fatalf("issue: %v", err)
}
for _, e := range testEntries() {
req := httptest.NewRequest(e.Method, e.Path, nil)
req.AddCookie(&http.Cookie{Name: auth.CookieName, Value: token})
rec := httptest.NewRecorder()
srv.Handler().ServeHTTP(rec, req)
if rec.Code != e.ExpectStatus {
t.Fatalf("beispielaufruf %s %s: status = %d, want %d", e.Method, e.Path, rec.Code, e.ExpectStatus)
}
}
}
// Akzeptanzkriterium 3: Dokument ist ueber einen Endpunkt abrufbar und
// valides JSON, das Tools wie Swagger UI importieren koennen.
func TestDocumentHandler_ServesValidJSONWithAllPaths(t *testing.T) {
doc := BuildDocument("NEXARCH Core API", "v1", testEntries())
req := httptest.NewRequest(http.MethodGet, "/api/v1/openapi.json", nil)
rec := httptest.NewRecorder()
DocumentHandler(doc)(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
var decoded Document
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("dokument nicht als json lesbar: %v", err)
}
if decoded.OpenAPI == "" {
t.Fatal("erwartet gesetztes openapi-versionsfeld")
}
for _, e := range testEntries() {
if _, ok := decoded.Paths[e.Path]; !ok {
t.Fatalf("pfad %s fehlt im dekodierten dokument", e.Path)
}
}
}
func TestSwaggerUIHandler_ServesHTMLReferencingDocumentURL(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/docs", nil)
rec := httptest.NewRecorder()
SwaggerUIHandler("/api/v1/openapi.json")(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
ct := rec.Header().Get("Content-Type")
if ct == "" {
t.Fatal("erwartet gesetzten Content-Type-Header")
}
body := rec.Body.String()
if !strings.Contains(body, "/api/v1/openapi.json") {
t.Fatal("erwartet referenz auf das openapi-dokument in der swagger-ui-seite")
}
}