blob: 58f4f9152332122d495234ef0aba9c0535b06b02 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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",
},
},
},
{
"remove-custom_metadata",
[]string{"-custom-metadata=baz=ghi", "-remove-custom-metadata=foo"},
"Success!",
0,
map[string]interface{}{
"custom_metadata": map[string]interface{}{
"bar": "def",
"baz": "ghi",
},
},
},
{
"remove-custom_metadata-multiple",
[]string{"-custom-metadata=baz=ghi", "-remove-custom-metadata=foo", "-remove-custom-metadata=bar"},
"Success!",
0,
map[string]interface{}{
"custom_metadata": map[string]interface{}{
"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)
}
}