Validate response schema for integration tests (#19043)
* add RequestResponseCallback to core/options Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * pass in router and apply function on requests Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * add callback Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * cleanup Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com> * Update vault/core.go * bad typo... Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * use pvt interface, can't downcast to child struct Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * finer grained errors Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * trim path for backend Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * remove entire mount point instead of just the first part of url Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * Update vault/testing.go Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com> * add doc string Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * update docstring Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * reformat Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> * added changelog --------- Signed-off-by: Daniel Huckins <dhuckins@users.noreply.github.com> Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com>
This commit is contained in:
parent
020a3903ff
commit
7fde5ecb83
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
openapi: added ability to validate response structures against openapi schema for test clusters
|
||||
```
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
|
@ -126,3 +127,43 @@ func GetResponseSchema(t *testing.T, path *framework.Path, operation logical.Ope
|
|||
|
||||
return &schemaResponses[0]
|
||||
}
|
||||
|
||||
// ResponseValidatingCallback can be used in setting up a [vault.TestCluster] that validates every response against the
|
||||
// openapi specifications
|
||||
//
|
||||
// [vault.TestCluster]: https://pkg.go.dev/github.com/hashicorp/vault/vault#TestCluster
|
||||
func ResponseValidatingCallback(t *testing.T) func(logical.Backend, *logical.Request, *logical.Response) {
|
||||
type PathRouter interface {
|
||||
Route(string) *framework.Path
|
||||
}
|
||||
|
||||
return func(b logical.Backend, req *logical.Request, resp *logical.Response) {
|
||||
t.Helper()
|
||||
|
||||
if b == nil {
|
||||
t.Fatalf("non-nil backend required")
|
||||
}
|
||||
backend, ok := b.(PathRouter)
|
||||
if !ok {
|
||||
t.Fatalf("could not cast %T to have `Route(string) *framework.Path`", b)
|
||||
}
|
||||
|
||||
// the full request path includes the backend
|
||||
// but when passing to the backend, we have to trim the mount point
|
||||
// `sys/mounts/secret` -> `mounts/secret`
|
||||
// `auth/token/create` -> `create`
|
||||
requestPath := strings.TrimPrefix(req.Path, req.MountPoint)
|
||||
|
||||
route := backend.Route(requestPath)
|
||||
if route == nil {
|
||||
t.Fatalf("backend %T could not find a route for %s", b, req.Path)
|
||||
}
|
||||
|
||||
ValidateResponse(
|
||||
t,
|
||||
GetResponseSchema(t, route, req.Operation),
|
||||
resp,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -688,6 +688,10 @@ type Core struct {
|
|||
// contains absolute paths that we intend to forward (and template) when
|
||||
// we're on a secondary cluster.
|
||||
writeForwardedPaths *pathmanager.PathManager
|
||||
|
||||
// if populated, the callback is called for every request
|
||||
// for testing purposes
|
||||
requestResponseCallback func(logical.Backend, *logical.Request, *logical.Response)
|
||||
}
|
||||
|
||||
// c.stateLock needs to be held in read mode before calling this function.
|
||||
|
|
|
@ -675,6 +675,10 @@ func (c *Core) handleCancelableRequest(ctx context.Context, req *logical.Request
|
|||
resp, auth, err = c.handleRequest(ctx, req)
|
||||
}
|
||||
|
||||
if err == nil && c.requestResponseCallback != nil {
|
||||
c.requestResponseCallback(c.router.MatchingBackend(ctx, req.Path), req, resp)
|
||||
}
|
||||
|
||||
// If we saved the token in the request, we should return it in the response
|
||||
// data.
|
||||
if resp != nil && resp.Data != nil {
|
||||
|
|
|
@ -1186,6 +1186,9 @@ type TestClusterOptions struct {
|
|||
NoDefaultQuotas bool
|
||||
|
||||
Plugins *TestPluginConfig
|
||||
|
||||
// if populated, the callback is called for every request
|
||||
RequestResponseCallback func(logical.Backend, *logical.Request, *logical.Response)
|
||||
}
|
||||
|
||||
type TestPluginConfig struct {
|
||||
|
@ -1936,6 +1939,10 @@ func (testCluster *TestCluster) newCore(t testing.T, idx int, coreConfig *CoreCo
|
|||
handler = opts.HandlerFunc.Handler(&props)
|
||||
}
|
||||
|
||||
if opts != nil && opts.RequestResponseCallback != nil {
|
||||
c.requestResponseCallback = opts.RequestResponseCallback
|
||||
}
|
||||
|
||||
// Set this in case the Seal was manually set before the core was
|
||||
// created
|
||||
if localConfig.Seal != nil {
|
||||
|
|
Loading…
Reference in New Issue