Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate response schema for integration tests #19043

Merged
merged 17 commits into from
Feb 15, 2023
Merged
3 changes: 3 additions & 0 deletions changelog/19043.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
openapi: added ability to validate response structures against openapi schema for test clusters
```
41 changes: 41 additions & 0 deletions sdk/helper/testhelpers/schema/response_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"

"github.com/hashicorp/vault/sdk/framework"
Expand Down Expand Up @@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting workaround here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sigh yeah... couldn't think of a better way to do it 🤷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this implementation. It reminds of something similar I saw in the implementation of Go's context. 👍

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,
)
}
}
4 changes: 4 additions & 0 deletions vault/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions vault/request_handling.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions vault/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down