Skip to content

Instantly share code, notes, and snippets.

@bojanz
Last active March 6, 2023 09:58
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bojanz/00187fd502b75001953d200c11dbbd97 to your computer and use it in GitHub Desktop.
Save bojanz/00187fd502b75001953d200c11dbbd97 to your computer and use it in GitHub Desktop.
OpenAPI assert helper
package responsetest
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
oarouters "github.com/getkin/kin-openapi/routers"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// oaRouter is the OpenAPI router used to validate requests and responses.
var oaRouter oarouters.Router
func init() {
openapi3.DefineStringFormat("uuid", openapi3.FormatOfStringForUUIDOfRFC4122)
openapi3.DefineStringFormat("ulid", `^[0-7]{1}[0-9A-HJKMNP-TV-Z]{25}$`)
loader := &openapi3.Loader{Context: context.Background()}
doc, err := loader.LoadFromFile("../../openapi.combined.yaml")
if err != nil {
panic(err)
}
// Our test requests are relative, so the server URL doesn't get found.
doc.Servers = nil
oaRouter, err = legacyrouter.NewRouter(doc)
if err != nil {
panic(err)
}
}
// AssertOpenAPI verifies that the given request and response match their OpenAPI schema.
func AssertOpenAPI(t *testing.T, rr *httptest.ResponseRecorder, req *http.Request) {
t.Helper()
// The request body exists and was already read once when the request
// was sent. Replay it to allow ValidateRequest() to read it again.
if req.Body != nil && req.Body != http.NoBody {
req.Body, _ = req.GetBody()
}
// Validate request.
route, pathParams, err := oaRouter.FindRoute(req)
require.NoError(t, err, "could not find route")
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
Options: &openapi3filter.Options{
MultiError: true,
},
}
// Don't validate the request body if the response indicates an error,
// to allow testing server-side validation using known bad values.
if rr.Result().StatusCode >= http.StatusBadRequest {
requestValidationInput.Options.ExcludeRequestBody = true
}
err = openapi3filter.ValidateRequest(context.Background(), requestValidationInput)
assert.NoError(t, err, "http request is not valid")
// Validate response.
responseValidationInput := &openapi3filter.ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: rr.Result().StatusCode,
Header: http.Header{"Content-Type": []string{rr.Header().Get("Content-Type")}},
}
responseValidationInput.SetBodyBytes(rr.Body.Bytes())
err = openapi3filter.ValidateResponse(context.Background(), responseValidationInput)
assert.NoError(t, err, "http response is not valid")
}
@bojanz
Copy link
Author

bojanz commented Apr 9, 2021

Usage:

req, _ = http.NewRequest("GET", "/my-endpoint", nil)
w = httptest.NewRecorder()
r.ServeHTTP(w, req)
// Do response assertions here.
responsetest.AssertOpenAPI(t, w, req)

@bojanz
Copy link
Author

bojanz commented May 21, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment