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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/sdk/framework"
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
@ -126,3 +127,43 @@ func GetResponseSchema(t *testing.T, path *framework.Path, operation logical.Ope
|
||||||
|
|
||||||
return &schemaResponses[0]
|
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
|
// contains absolute paths that we intend to forward (and template) when
|
||||||
// we're on a secondary cluster.
|
// we're on a secondary cluster.
|
||||||
writeForwardedPaths *pathmanager.PathManager
|
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.
|
// 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)
|
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
|
// If we saved the token in the request, we should return it in the response
|
||||||
// data.
|
// data.
|
||||||
if resp != nil && resp.Data != nil {
|
if resp != nil && resp.Data != nil {
|
||||||
|
|
|
@ -1186,6 +1186,9 @@ type TestClusterOptions struct {
|
||||||
NoDefaultQuotas bool
|
NoDefaultQuotas bool
|
||||||
|
|
||||||
Plugins *TestPluginConfig
|
Plugins *TestPluginConfig
|
||||||
|
|
||||||
|
// if populated, the callback is called for every request
|
||||||
|
RequestResponseCallback func(logical.Backend, *logical.Request, *logical.Response)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestPluginConfig struct {
|
type TestPluginConfig struct {
|
||||||
|
@ -1936,6 +1939,10 @@ func (testCluster *TestCluster) newCore(t testing.T, idx int, coreConfig *CoreCo
|
||||||
handler = opts.HandlerFunc.Handler(&props)
|
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
|
// Set this in case the Seal was manually set before the core was
|
||||||
// created
|
// created
|
||||||
if localConfig.Seal != nil {
|
if localConfig.Seal != nil {
|
||||||
|
|
Loading…
Reference in New Issue