package httpapi import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func TestRegisterDocumentation(t *testing.T) { tests := []struct { name string path string wantStatus int wantContentType string wantBody string wantLocation string }{ { name: "OpenAPI document", path: "/openapi.json", wantStatus: http.StatusOK, wantContentType: "application/json", wantBody: `"openapi": "3.0.3"`, }, { name: "Swagger UI", path: "/docs/", wantStatus: http.StatusOK, wantContentType: "text/html; charset=utf-8", wantBody: `url: "/openapi.json"`, }, { name: "documentation trailing slash", path: "/docs", wantStatus: http.StatusTemporaryRedirect, wantLocation: "/docs/", }, { name: "removed Swagger route", path: "/swagger/", wantStatus: http.StatusNotFound, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() mux := http.NewServeMux() RegisterDocumentation(mux) request := httptest.NewRequest(http.MethodGet, tt.path, nil) response := httptest.NewRecorder() mux.ServeHTTP(response, request) if response.Code != tt.wantStatus { t.Fatalf("status = %d, want %d", response.Code, tt.wantStatus) } if tt.wantContentType != "" { if got := response.Header().Get("Content-Type"); got != tt.wantContentType { t.Errorf("Content-Type = %q, want %q", got, tt.wantContentType) } } if tt.wantBody != "" && !strings.Contains(response.Body.String(), tt.wantBody) { t.Errorf("body does not contain %q", tt.wantBody) } if got := response.Header().Get("Location"); got != tt.wantLocation { t.Errorf("Location = %q, want %q", got, tt.wantLocation) } }) } } func TestOpenAPIDocumentCoversPublicAPI(t *testing.T) { t.Parallel() mux := http.NewServeMux() RegisterDocumentation(mux) request := httptest.NewRequest(http.MethodGet, "/openapi.json", nil) response := httptest.NewRecorder() mux.ServeHTTP(response, request) var document struct { OpenAPI string `json:"openapi"` Paths map[string]map[string]struct { Responses map[string]json.RawMessage `json:"responses"` } `json:"paths"` Components struct { Schemas map[string]json.RawMessage `json:"schemas"` } `json:"components"` } if err := json.Unmarshal(response.Body.Bytes(), &document); err != nil { t.Fatalf("decode OpenAPI document: %v", err) } if document.OpenAPI != "3.0.3" { t.Errorf("OpenAPI version = %q, want 3.0.3", document.OpenAPI) } if _, ok := document.Paths["/health"]["get"]; !ok { t.Error("OpenAPI document does not define GET /health") } conversion, ok := document.Paths["/api/v1/card-to-iban"]["post"] if !ok { t.Fatal("OpenAPI document does not define POST /api/v1/card-to-iban") } for _, status := range []string{"200", "400", "405", "422", "500", "502", "503", "504"} { if _, ok := conversion.Responses[status]; !ok { t.Errorf("POST /api/v1/card-to-iban does not document response %s", status) } } for _, schema := range []string{ "ConvertRequest", "ConvertResponse", "ErrorResponse", "HealthResponse", } { if _, ok := document.Components.Schemas[schema]; !ok { t.Errorf("OpenAPI document does not define schema %s", schema) } } }