Files
vault/command/kv_metadata_patch_test.go
Chris Capurso e014065c64 Add HTTP PATCH support for KV key metadata (#13215)
* go get vault-plugin-secrets-kv@vault-4290-patch-metadata

* add kv metadata patch command

* add changelog entry

* success tests for kv metadata patch flags

* add more kv metadata patch flags tests

* add kv metadata patch cas warning test

* add kv-v2 key metadata patch API docs

* add kv metadata patch to docs

* prevent unintentional field overwriting in kv metadata put cmd

* like create/update ops, prevent patch to paths ending in /

* fix kv metadata patch cmd in docs

* fix flag defaults for kv metadata put

* go get vault-plugin-secrets-kv@vault-4290-patch-metadata

* fix TestKvMetadataPatchCommand_Flags test

* doc fixes

* go get vault-plugin-secrets-kv@master; go mod tidy
2022-01-12 12:05:27 -05:00

273 lines
6.4 KiB
Go

package command
import (
"encoding/json"
"io"
"strings"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/vault/api"
"github.com/mitchellh/cli"
)
func testKVMetadataPatchCommand(tb testing.TB) (*cli.MockUi, *KVMetadataPatchCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &KVMetadataPatchCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func kvMetadataPatchWithRetry(t *testing.T, client *api.Client, args []string, stdin *io.PipeReader) (int, string) {
t.Helper()
return retryKVCommand(t, func() (int, string) {
ui, cmd := testKVMetadataPatchCommand(t)
cmd.client = client
if stdin != nil {
cmd.testStdin = stdin
}
code := cmd.Run(args)
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
return code, combined
})
}
func kvMetadataPutWithRetry(t *testing.T, client *api.Client, args []string, stdin *io.PipeReader) (int, string) {
t.Helper()
return retryKVCommand(t, func() (int, string) {
ui, cmd := testKVMetadataPutCommand(t)
cmd.client = client
if stdin != nil {
cmd.testStdin = stdin
}
code := cmd.Run(args)
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
return code, combined
})
}
func TestKvMetadataPatchCommand_EmptyArgs(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
if err := client.Sys().Mount("kv/", &api.MountInput{
Type: "kv-v2",
}); err != nil {
t.Fatalf("kv-v2 mount error: %#v", err)
}
args := make([]string, 0)
code, combined := kvMetadataPatchWithRetry(t, client, args, nil)
expectedCode := 1
expectedOutput := "Not enough arguments"
if code != expectedCode {
t.Fatalf("expected code to be %d but was %d for patch cmd with args %#v", expectedCode, code, args)
}
if !strings.Contains(combined, expectedOutput) {
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", expectedOutput, combined, args)
}
}
func TestKvMetadataPatchCommand_Flags(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
out string
code int
expectedUpdates map[string]interface{}
}{
{
"cas_required_success",
[]string{"-cas-required=true"},
"Success!",
0,
map[string]interface{}{
"cas_required": true,
},
},
{
"cas_required_invalid",
[]string{"-cas-required=12345"},
"invalid boolean value",
1,
map[string]interface{}{},
},
{
"custom_metadata_success",
[]string{"-custom-metadata=baz=ghi"},
"Success!",
0,
map[string]interface{}{
"custom_metadata": map[string]interface{}{
"foo": "abc",
"bar": "def",
"baz": "ghi",
},
},
},
{
"delete_version_after_success",
[]string{"-delete-version-after=5s"},
"Success!",
0,
map[string]interface{}{
"delete_version_after": "5s",
},
},
{
"delete_version_after_invalid",
[]string{"-delete-version-after=false"},
"invalid duration",
1,
map[string]interface{}{},
},
{
"max_versions_success",
[]string{"-max-versions=10"},
"Success!",
0,
map[string]interface{}{
"max_versions": json.Number("10"),
},
},
{
"max_versions_invalid",
[]string{"-max-versions=false"},
"invalid syntax",
1,
map[string]interface{}{},
},
{
"multiple_flags_success",
[]string{"-max-versions=20", "-custom-metadata=baz=123"},
"Success!",
0,
map[string]interface{}{
"max_versions": json.Number("20"),
"custom_metadata": map[string]interface{}{
"foo": "abc",
"bar": "def",
"baz": "123",
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
basePath := t.Name() + "/"
secretPath := basePath + "my-secret"
metadataPath := basePath + "metadata/" + "my-secret"
if err := client.Sys().Mount(basePath, &api.MountInput{
Type: "kv-v2",
}); err != nil {
t.Fatalf("kv-v2 mount error: %#v", err)
}
putArgs := []string{"-cas-required=true", "-custom-metadata=foo=abc", "-custom-metadata=bar=def", secretPath}
code, combined := kvMetadataPutWithRetry(t, client, putArgs, nil)
if code != 0 {
t.Fatalf("initial metadata put failed, code: %d, output: %s", code, combined)
}
initialMetadata, err := client.Logical().Read(metadataPath)
if err != nil {
t.Fatalf("metadata read failed, err: %#v", err)
}
patchArgs := append(tc.args, secretPath)
code, combined = kvMetadataPatchWithRetry(t, client, patchArgs, nil)
if !strings.Contains(combined, tc.out) {
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", tc.out, combined, patchArgs)
}
if code != tc.code {
t.Fatalf("expected code to be %d but was %d for patch cmd with args %#v", tc.code, code, patchArgs)
}
patchedMetadata, err := client.Logical().Read(metadataPath)
if err != nil {
t.Fatalf("metadata read failed, err: %#v", err)
}
for k, v := range patchedMetadata.Data {
var expectedVal interface{}
if inputVal, ok := tc.expectedUpdates[k]; ok {
expectedVal = inputVal
} else {
expectedVal = initialMetadata.Data[k]
}
if diff := deep.Equal(expectedVal, v); len(diff) > 0 {
t.Fatalf("patched %q mismatch, diff: %#v", k, diff)
}
}
})
}
}
func TestKvMetadataPatchCommand_CasWarning(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
basePath := "kv/"
if err := client.Sys().Mount(basePath, &api.MountInput{
Type: "kv-v2",
}); err != nil {
t.Fatalf("kv-v2 mount error: %#v", err)
}
secretPath := basePath + "my-secret"
args := []string{"-cas-required=true", secretPath}
code, combined := kvMetadataPutWithRetry(t, client, args, nil)
if code != 0 {
t.Fatalf("metadata put failed, code: %d, output: %s", code, combined)
}
casConfig := map[string]interface{}{
"cas_required": true,
}
_, err := client.Logical().Write(basePath + "config", casConfig)
if err != nil {
t.Fatalf("config write failed, err: #%v", err)
}
args = []string{"-cas-required=false", secretPath}
code, combined = kvMetadataPatchWithRetry(t, client, args, nil)
if code != 0 {
t.Fatalf("expected code to be 0 but was %d for patch cmd with args %#v", code, args)
}
expectedOutput := "\"cas_required\" set to false, but is mandated by backend config"
if !strings.Contains(combined, expectedOutput) {
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", expectedOutput, combined, args)
}
}