Skip to content

Commit

Permalink
Fix response structure validation tests for non-2xx responses (hashic…
Browse files Browse the repository at this point in the history
  • Loading branch information
averche authored Mar 23, 2023
1 parent 0f1ac87 commit f674f0e
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 23 deletions.
8 changes: 2 additions & 6 deletions builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5254,12 +5254,8 @@ func TestBackend_IfModifiedSinceHeaders(t *testing.T) {
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
// XXX: Schema response validation does not take into account
// response codes, preventing non-200 responses from working
// properly.
//
// RequestResponseCallback: schema.ResponseValidatingCallback(t),
HandlerFunc: vaulthttp.Handler,
RequestResponseCallback: schema.ResponseValidatingCallback(t),
})
cluster.Start()
defer cluster.Cleanup()
Expand Down
46 changes: 29 additions & 17 deletions sdk/helper/testhelpers/schema/response_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
"github.com/hashicorp/vault/sdk/logical"
)

// ValidateResponseData is a test helper that validates whether the given
// response data map conforms to the response schema (schema.Fields). It cycles
// through the data map and validates conversions in the schema. In "strict"
// mode, this function will also ensure that the data map has all schema's
// requred fields and does not have any fields outside of the schema.
// ValidateResponse is a test helper that validates whether the given response
// object conforms to the response schema (schema.Fields). It cycles through
// the data map and validates conversions in the schema. In "strict" mode, this
// function will also ensure that the data map has all schema-required fields
// and does not have any fields outside of the schema.
func ValidateResponse(t *testing.T, schema *framework.Response, response *logical.Response, strict bool) {
t.Helper()

Expand All @@ -29,11 +29,11 @@ func ValidateResponse(t *testing.T, schema *framework.Response, response *logica
}
}

// ValidateResponse is a test helper that validates whether the given response
// object conforms to the response schema (schema.Fields). It cycles through
// the data map and validates conversions in the schema. In "strict" mode, this
// function will also ensure that the data map has all schema-required fields
// and does not have any fields outside of the schema.
// ValidateResponseData is a test helper that validates whether the given
// response data map conforms to the response schema (schema.Fields). It cycles
// through the data map and validates conversions in the schema. In "strict"
// mode, this function will also ensure that the data map has all schema's
// requred fields and does not have any fields outside of the schema.
func ValidateResponseData(t *testing.T, schema *framework.Response, data map[string]interface{}, strict bool) {
t.Helper()

Expand All @@ -53,6 +53,16 @@ func validateResponseDataImpl(schema *framework.Response, data map[string]interf
return nil
}

// Certain responses may come through with non-2xx status codes. While
// these are not always errors (e.g. 3xx redirection codes), we don't
// consider them for the purposes of schema validation
if status, exists := data[logical.HTTPStatusCode]; exists {
s, ok := status.(int)
if ok && (s < 200 || s > 299) {
return nil
}
}

// Marshal the data to JSON and back to convert the map's values into
// JSON strings expected by Validate() and ValidateStrict(). This is
// not efficient and is done for testing purposes only.
Expand Down Expand Up @@ -100,7 +110,8 @@ func validateResponseDataImpl(schema *framework.Response, data map[string]interf
return fd.Validate()
}

// FindResponseSchema is a test helper to extract response schema from the given framework path / operation
// FindResponseSchema is a test helper to extract response schema from the
// given framework path / operation.
func FindResponseSchema(t *testing.T, paths []*framework.Path, pathIdx int, operation logical.Operation) *framework.Response {
t.Helper()

Expand Down Expand Up @@ -149,8 +160,8 @@ 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
// 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) {
Expand All @@ -164,15 +175,16 @@ func ResponseValidatingCallback(t *testing.T) func(logical.Backend, *logical.Req
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`
// 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)
Expand Down
30 changes: 30 additions & 0 deletions sdk/helper/testhelpers/schema/response_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,36 @@ func TestValidateResponse(t *testing.T) {
errorExpected: false,
},

"string schema field, response has non-200 http_status_code, strict": {
schema: &framework.Response{
Fields: map[string]*framework.FieldSchema{
"foo": {
Type: framework.TypeString,
},
},
},
response: map[string]interface{}{
"http_status_code": 304,
},
strict: true,
errorExpected: false,
},

"string schema field, response has non-200 http_status_code, not strict": {
schema: &framework.Response{
Fields: map[string]*framework.FieldSchema{
"foo": {
Type: framework.TypeString,
},
},
},
response: map[string]interface{}{
"http_status_code": 304,
},
strict: false,
errorExpected: false,
},

"schema has http_raw_body, strict": {
schema: &framework.Response{
Fields: map[string]*framework.FieldSchema{
Expand Down

0 comments on commit f674f0e

Please sign in to comment.