feat(api): add OpenAPI documentation
This commit is contained in:
119
backend/internal/httpapi/docs_test.go
Normal file
119
backend/internal/httpapi/docs_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
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: "/swagger/",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "text/html; charset=utf-8",
|
||||
wantBody: `url: "/openapi.json"`,
|
||||
},
|
||||
{
|
||||
name: "documentation shortcut",
|
||||
path: "/docs",
|
||||
wantStatus: http.StatusTemporaryRedirect,
|
||||
wantLocation: "/swagger/",
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user