File size: 4,515 Bytes
1f10f31 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | package oasmiddleware_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/getkin/kin-openapi/routers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
api "github.com/openmeterio/openmeter/api/v3"
"github.com/openmeterio/openmeter/api/v3/oasmiddleware"
)
// TestValidateResponse_Violation proves that the ValidateResponse middleware fires its
// error hook when a handler returns a response body that violates the OpenAPI spec.
//
// GET /openmeter/addons requires a 200 body with both "data" and "meta" fields.
// Returning {} omits both required fields and must trigger a violation.
func TestValidateResponse_Violation(t *testing.T) {
swagger, err := api.GetSwagger()
require.NoError(t, err)
swagger.Servers = nil
router, err := oasmiddleware.NewValidationRouter(t.Context(), swagger, &oasmiddleware.ValidationRouterOpts{
DeleteServers: true,
})
require.NoError(t, err)
var gotErr error
mw := oasmiddleware.ValidateResponse(router, oasmiddleware.ValidateResponseOption{
ResponseValidationErrorHook: func(err error, r *http.Request) {
gotErr = err
},
})
badHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{}`))
})
req := httptest.NewRequest(http.MethodGet, "/openmeter/addons", nil)
rec := httptest.NewRecorder()
mw(badHandler).ServeHTTP(rec, req)
assert.Error(t, gotErr, "expected a validation error for missing required fields")
t.Logf("validation error (expected): %v", gotErr)
}
// TestValidateResponse_Clean proves that a well-formed response does not trigger the error hook.
func TestValidateResponse_Clean(t *testing.T) {
swagger, err := api.GetSwagger()
require.NoError(t, err)
swagger.Servers = nil
router, err := oasmiddleware.NewValidationRouter(t.Context(), swagger, &oasmiddleware.ValidationRouterOpts{
DeleteServers: true,
})
require.NoError(t, err)
var gotErr error
mw := oasmiddleware.ValidateResponse(router, oasmiddleware.ValidateResponseOption{
ResponseValidationErrorHook: func(err error, r *http.Request) {
gotErr = err
},
})
goodHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"data":[],"meta":{"page":{"number":0,"size":100,"total":0}}}`))
})
req := httptest.NewRequest(http.MethodGet, "/openmeter/addons", nil)
rec := httptest.NewRecorder()
mw(goodHandler).ServeHTTP(rec, req)
assert.NoError(t, gotErr, "expected no validation error for a well-formed response")
}
// TestValidateResponse_RouteFilterSkipsValidation proves that when RouteFilterHook returns false,
// the response body is neither buffered nor validated — even if it would otherwise violate the spec.
// The filter is the per-route gate that lets callers (e.g. unstable-only mode) avoid the
// buffering overhead on routes they don't care about.
func TestValidateResponse_RouteFilterSkipsValidation(t *testing.T) {
swagger, err := api.GetSwagger()
require.NoError(t, err)
swagger.Servers = nil
router, err := oasmiddleware.NewValidationRouter(t.Context(), swagger, &oasmiddleware.ValidationRouterOpts{
DeleteServers: true,
})
require.NoError(t, err)
var (
gotErr error
filteredRoute *routers.Route
)
mw := oasmiddleware.ValidateResponse(router, oasmiddleware.ValidateResponseOption{
RouteFilterHook: func(route *routers.Route) bool {
filteredRoute = route
return false
},
ResponseValidationErrorHook: func(err error, r *http.Request) {
gotErr = err
},
})
// Body that would fail validation if the filter let it through.
badHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{}`))
})
req := httptest.NewRequest(http.MethodGet, "/openmeter/addons", nil)
rec := httptest.NewRecorder()
mw(badHandler).ServeHTTP(rec, req)
require.NotNil(t, filteredRoute, "RouteFilterHook should have been invoked with the matched route")
assert.Equal(t, "/openmeter/addons", filteredRoute.Path)
assert.NoError(t, gotErr, "validation error hook must not fire when the filter returns false")
assert.Equal(t, http.StatusOK, rec.Code, "client response should still be served")
assert.Equal(t, `{}`, rec.Body.String(), "client response body should still be served")
}
|