// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package api import ( "reflect" "testing" "time" ) func TestExtractVersionMetadata(t *testing.T) { t.Parallel() inputCreatedTimeStr := "2022-05-06T23:02:04.865025Z" inputDeletionTimeStr := "2022-06-17T01:15:03.279013Z" expectedCreatedTimeParsed, err := time.Parse(time.RFC3339, inputCreatedTimeStr) if err != nil { t.Fatalf("unable to parse expected created time: %v", err) } expectedDeletionTimeParsed, err := time.Parse(time.RFC3339, inputDeletionTimeStr) if err != nil { t.Fatalf("unable to parse expected created time: %v", err) } testCases := []struct { name string input *Secret expected *KVVersionMetadata }{ { name: "a secret", input: &Secret{ Data: map[string]interface{}{ "data": map[string]interface{}{ "password": "Hashi123", }, "metadata": map[string]interface{}{ "version": 10, "created_time": inputCreatedTimeStr, "deletion_time": "", "destroyed": false, "custom_metadata": nil, }, }, }, expected: &KVVersionMetadata{ Version: 10, CreatedTime: expectedCreatedTimeParsed, DeletionTime: time.Time{}, Destroyed: false, }, }, { name: "a secret that has been deleted", input: &Secret{ Data: map[string]interface{}{ "data": map[string]interface{}{ "password": "Hashi123", }, "metadata": map[string]interface{}{ "version": 10, "created_time": inputCreatedTimeStr, "deletion_time": inputDeletionTimeStr, "destroyed": false, "custom_metadata": nil, }, }, }, expected: &KVVersionMetadata{ Version: 10, CreatedTime: expectedCreatedTimeParsed, DeletionTime: expectedDeletionTimeParsed, Destroyed: false, }, }, { name: "a response from a Write operation", input: &Secret{ Data: map[string]interface{}{ "version": 10, "created_time": inputCreatedTimeStr, "deletion_time": "", "destroyed": false, "custom_metadata": nil, }, }, expected: &KVVersionMetadata{ Version: 10, CreatedTime: expectedCreatedTimeParsed, DeletionTime: time.Time{}, Destroyed: false, }, }, } for _, tc := range testCases { versionMetadata, err := extractVersionMetadata(tc.input) if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(versionMetadata, tc.expected) { t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, versionMetadata, tc.expected) } } } func TestExtractDataAndVersionMetadata(t *testing.T) { t.Parallel() inputCreatedTimeStr := "2022-05-06T23:02:04.865025Z" inputDeletionTimeStr := "2022-06-17T01:15:03.279013Z" expectedCreatedTimeParsed, err := time.Parse(time.RFC3339, inputCreatedTimeStr) if err != nil { t.Fatalf("unable to parse expected created time: %v", err) } expectedDeletionTimeParsed, err := time.Parse(time.RFC3339, inputDeletionTimeStr) if err != nil { t.Fatalf("unable to parse expected created time: %v", err) } readResp := &Secret{ Data: map[string]interface{}{ "data": map[string]interface{}{ "password": "Hashi123", }, "metadata": map[string]interface{}{ "version": 10, "created_time": inputCreatedTimeStr, "deletion_time": "", "destroyed": false, "custom_metadata": nil, }, }, } readRespDeleted := &Secret{ Data: map[string]interface{}{ "data": nil, "metadata": map[string]interface{}{ "version": 10, "created_time": inputCreatedTimeStr, "deletion_time": inputDeletionTimeStr, "destroyed": false, "custom_metadata": nil, }, }, } testCases := []struct { name string input *Secret expected *KVSecret }{ { name: "a response from a Read operation", input: readResp, expected: &KVSecret{ Data: map[string]interface{}{ "password": "Hashi123", }, VersionMetadata: &KVVersionMetadata{ Version: 10, CreatedTime: expectedCreatedTimeParsed, DeletionTime: time.Time{}, Destroyed: false, }, // it's tempting to test some Secrets with custom_metadata but // we can't in this test because it isn't until we call the // extractCustomMetadata function that the custom metadata // gets added onto the struct. See TestExtractCustomMetadata. CustomMetadata: nil, Raw: readResp, }, }, { name: "a secret that has been deleted and thus has nil data", input: readRespDeleted, expected: &KVSecret{ Data: nil, VersionMetadata: &KVVersionMetadata{ Version: 10, CreatedTime: expectedCreatedTimeParsed, DeletionTime: expectedDeletionTimeParsed, Destroyed: false, }, CustomMetadata: nil, Raw: readRespDeleted, }, }, } for _, tc := range testCases { dvm, err := extractDataAndVersionMetadata(tc.input) if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(dvm, tc.expected) { t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, dvm, tc.expected) } } } func TestExtractFullMetadata(t *testing.T) { inputCreatedTimeStr := "2022-05-20T00:51:49.419794Z" expectedCreatedTimeParsed, err := time.Parse(time.RFC3339, inputCreatedTimeStr) if err != nil { t.Fatalf("unable to parse expected created time: %v", err) } inputUpdatedTimeStr := "2022-05-20T20:23:43.284488Z" expectedUpdatedTimeParsed, err := time.Parse(time.RFC3339, inputUpdatedTimeStr) if err != nil { t.Fatalf("unable to parse expected updated time: %v", err) } inputDeletedTimeStr := "2022-05-21T00:05:49.521697Z" expectedDeletedTimeParsed, err := time.Parse(time.RFC3339, inputDeletedTimeStr) if err != nil { t.Fatalf("unable to parse expected deletion time: %v", err) } metadataResp := &Secret{ Data: map[string]interface{}{ "cas_required": true, "created_time": inputCreatedTimeStr, "current_version": 2, "custom_metadata": map[string]interface{}{ "org": "eng", }, "delete_version_after": "200s", "max_versions": 3, "oldest_version": 1, "updated_time": inputUpdatedTimeStr, "versions": map[string]interface{}{ "2": map[string]interface{}{ "created_time": inputUpdatedTimeStr, "deletion_time": "", "destroyed": false, }, "1": map[string]interface{}{ "created_time": inputCreatedTimeStr, "deletion_time": inputDeletedTimeStr, "destroyed": false, }, }, }, } testCases := []struct { name string input *Secret expected *KVMetadata }{ { name: "a metadata response", input: metadataResp, expected: &KVMetadata{ CASRequired: true, CreatedTime: expectedCreatedTimeParsed, CurrentVersion: 2, CustomMetadata: map[string]interface{}{ "org": "eng", }, DeleteVersionAfter: time.Duration(200 * time.Second), MaxVersions: 3, OldestVersion: 1, UpdatedTime: expectedUpdatedTimeParsed, Versions: map[string]KVVersionMetadata{ "2": { Version: 2, CreatedTime: expectedUpdatedTimeParsed, DeletionTime: time.Time{}, }, "1": { Version: 1, CreatedTime: expectedCreatedTimeParsed, DeletionTime: expectedDeletedTimeParsed, }, }, }, }, } for _, tc := range testCases { md, err := extractFullMetadata(tc.input) if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(md, tc.expected) { t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, md, tc.expected) } } } func TestExtractCustomMetadata(t *testing.T) { testCases := []struct { name string inputAPIResp *Secret expected map[string]interface{} }{ { name: "a read response with some custom metadata", inputAPIResp: &Secret{ Data: map[string]interface{}{ "metadata": map[string]interface{}{ "custom_metadata": map[string]interface{}{"org": "eng"}, }, }, }, expected: map[string]interface{}{"org": "eng"}, }, { name: "a write response with some (pre-existing) custom metadata", inputAPIResp: &Secret{ Data: map[string]interface{}{ "custom_metadata": map[string]interface{}{"org": "eng"}, }, }, expected: map[string]interface{}{"org": "eng"}, }, { name: "a read response with no custom metadata from a pre-1.9 Vault server", inputAPIResp: &Secret{ Data: map[string]interface{}{ "metadata": map[string]interface{}{}, }, }, expected: map[string]interface{}(nil), }, { name: "a write response with no custom metadata from a pre-1.9 Vault server", inputAPIResp: &Secret{ Data: map[string]interface{}{}, }, expected: map[string]interface{}(nil), }, { name: "a read response with no custom metadata from a post-1.9 Vault server", inputAPIResp: &Secret{ Data: map[string]interface{}{ "metadata": map[string]interface{}{ "custom_metadata": nil, }, }, }, expected: map[string]interface{}(nil), }, { name: "a write response with no custom metadata from a post-1.9 Vault server", inputAPIResp: &Secret{ Data: map[string]interface{}{ "custom_metadata": nil, }, }, expected: map[string]interface{}(nil), }, { name: "a read response where custom metadata was deleted", inputAPIResp: &Secret{ Data: map[string]interface{}{ "metadata": map[string]interface{}{ "custom_metadata": map[string]interface{}{}, }, }, }, expected: map[string]interface{}{}, }, { name: "a write response where custom metadata was deleted", inputAPIResp: &Secret{ Data: map[string]interface{}{ "custom_metadata": map[string]interface{}{}, }, }, expected: map[string]interface{}{}, }, } for _, tc := range testCases { cm := extractCustomMetadata(tc.inputAPIResp) if !reflect.DeepEqual(cm, tc.expected) { t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, cm, tc.expected) } } }