|
|
@@ -95,11 +95,12 @@ func TestSecretGetProp(t *testing.T) {
|
|
|
g.RegisterTestingT(t)
|
|
|
|
|
|
secret := Secret{
|
|
|
- Name: "test-name",
|
|
|
- Username: "test-user",
|
|
|
- Password: "test-pass",
|
|
|
- URI: "https://test.com",
|
|
|
- Description: "test-desc",
|
|
|
+ Name: "test-name",
|
|
|
+ Username: "test-user",
|
|
|
+ Password: "test-pass",
|
|
|
+ URI: "https://test.com",
|
|
|
+ Description: "test-desc",
|
|
|
+ CustomFields: map[string]string{"my-field": "my-value"},
|
|
|
}
|
|
|
|
|
|
// Test valid properties
|
|
|
@@ -123,11 +124,264 @@ func TestSecretGetProp(t *testing.T) {
|
|
|
g.Expect(err).To(g.BeNil())
|
|
|
g.Expect(string(val)).To(g.Equal("test-desc"))
|
|
|
|
|
|
+ // Test custom field
|
|
|
+ val, err = secret.GetProp("custom_fields.my-field")
|
|
|
+ g.Expect(err).To(g.BeNil())
|
|
|
+ g.Expect(string(val)).To(g.Equal("my-value"))
|
|
|
+
|
|
|
// Test invalid property
|
|
|
_, err = secret.GetProp("invalid")
|
|
|
g.Expect(err).To(g.MatchError(errPassboltSecretPropertyInvalid))
|
|
|
}
|
|
|
|
|
|
+func TestSecretGetPropCustomFieldNotFound(t *testing.T) {
|
|
|
+ g.RegisterTestingT(t)
|
|
|
+
|
|
|
+ // No custom fields set at all.
|
|
|
+ secret := Secret{Name: "test-name"}
|
|
|
+ _, err := secret.GetProp("custom_fields.missing")
|
|
|
+ g.Expect(err).To(g.MatchError(errPassboltCustomFieldNotFound))
|
|
|
+ g.Expect(err).To(g.MatchError(g.ContainSubstring("missing")))
|
|
|
+
|
|
|
+ // Custom fields present but the requested key does not exist.
|
|
|
+ secret.CustomFields = map[string]string{"other-key": "v"}
|
|
|
+ _, err = secret.GetProp("custom_fields.nonexistent")
|
|
|
+ g.Expect(err).To(g.MatchError(errPassboltCustomFieldNotFound))
|
|
|
+ g.Expect(err).To(g.MatchError(g.ContainSubstring("nonexistent")))
|
|
|
+}
|
|
|
+
|
|
|
+func TestBuildCustomFields(t *testing.T) {
|
|
|
+ g.RegisterTestingT(t)
|
|
|
+
|
|
|
+ const idA = "11111111-1111-1111-1111-111111111111"
|
|
|
+ const idB = "22222222-2222-2222-2222-222222222222"
|
|
|
+
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ metaFields map[string]any
|
|
|
+ secretFields map[string]any
|
|
|
+ want map[string]string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "no custom_fields in metadata returns nil",
|
|
|
+ metaFields: map[string]any{"name": "x"},
|
|
|
+ secretFields: map[string]any{"password": "p"},
|
|
|
+ want: nil,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "empty custom_fields array returns nil",
|
|
|
+ metaFields: map[string]any{"custom_fields": []any{}},
|
|
|
+ secretFields: map[string]any{},
|
|
|
+ want: nil,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "standard case: metadata_key with secret_value",
|
|
|
+ metaFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "metadata_key": "api-key"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secretFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "secret_value": "secret-123"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: map[string]string{"api-key": "secret-123"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "non-secret field: metadata_key with metadata_value and secret-side stub",
|
|
|
+ metaFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "metadata_key": "env", "metadata_value": "production"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secretFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "type": "text"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: map[string]string{"env": "production"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "secret_key field (no metadata_key) is silently skipped",
|
|
|
+ metaFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA /* no metadata_key */},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secretFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "secret_key": "hidden-name", "secret_value": "hidden-val"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: nil,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "multiple fields: encrypted value and non-secret value",
|
|
|
+ metaFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "metadata_key": "token"},
|
|
|
+ map[string]any{"id": idB, "metadata_key": "region", "metadata_value": "us-east-1"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secretFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "secret_value": "tok-abc123"},
|
|
|
+ map[string]any{"id": idB, "type": "text"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: map[string]string{
|
|
|
+ "token": "tok-abc123",
|
|
|
+ "region": "us-east-1",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "secret_value takes precedence over metadata_value",
|
|
|
+ metaFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "metadata_key": "field", "metadata_value": "meta-val"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secretFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "secret_value": "secret-val"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: map[string]string{"field": "secret-val"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "secret_value of empty string is preserved",
|
|
|
+ metaFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "metadata_key": "empty-field"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secretFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "secret_value": ""},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: map[string]string{"empty-field": ""},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "numeric and boolean values are stringified",
|
|
|
+ metaFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "metadata_key": "port"},
|
|
|
+ map[string]any{"id": idB, "metadata_key": "enabled", "metadata_value": true},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secretFields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "secret_value": float64(8080)},
|
|
|
+ map[string]any{"id": idB, "type": "boolean"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: map[string]string{
|
|
|
+ "port": "8080",
|
|
|
+ "enabled": "true",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ got := buildCustomFields(tt.metaFields, tt.secretFields)
|
|
|
+ g.Expect(got).To(g.Equal(tt.want))
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestExtractCustomFields(t *testing.T) {
|
|
|
+ g.RegisterTestingT(t)
|
|
|
+
|
|
|
+ const idA = "11111111-1111-1111-1111-111111111111"
|
|
|
+ const idB = "22222222-2222-2222-2222-222222222222"
|
|
|
+
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ fields map[string]any
|
|
|
+ want []map[string]any
|
|
|
+ wantOK bool
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "no custom_fields key",
|
|
|
+ fields: map[string]any{"password": "p"},
|
|
|
+ want: nil,
|
|
|
+ wantOK: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "custom_fields not a slice",
|
|
|
+ fields: map[string]any{"custom_fields": "not-a-slice"},
|
|
|
+ want: nil,
|
|
|
+ wantOK: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "already typed as []map[string]any",
|
|
|
+ fields: map[string]any{
|
|
|
+ "custom_fields": []map[string]any{
|
|
|
+ {"id": idA, "secret_value": "tok-abc"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: []map[string]any{{"id": idA, "secret_value": "tok-abc"}},
|
|
|
+ wantOK: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "entries are extracted, non-map items skipped",
|
|
|
+ fields: map[string]any{
|
|
|
+ "custom_fields": []any{
|
|
|
+ map[string]any{"id": idA, "secret_value": "tok-abc"},
|
|
|
+ "not-a-map",
|
|
|
+ map[string]any{"id": idB, "type": "text"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ want: []map[string]any{
|
|
|
+ {"id": idA, "secret_value": "tok-abc"},
|
|
|
+ {"id": idB, "type": "text"},
|
|
|
+ },
|
|
|
+ wantOK: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "empty custom_fields slice returns false",
|
|
|
+ fields: map[string]any{
|
|
|
+ "custom_fields": []any{},
|
|
|
+ },
|
|
|
+ want: []map[string]any{},
|
|
|
+ wantOK: false,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ got, ok := extractCustomFields(tt.fields)
|
|
|
+ g.Expect(ok).To(g.Equal(tt.wantOK))
|
|
|
+ g.Expect(got).To(g.Equal(tt.want))
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestHasNonEmptyString(t *testing.T) {
|
|
|
+ g.RegisterTestingT(t)
|
|
|
+
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ m map[string]any
|
|
|
+ key string
|
|
|
+ want bool
|
|
|
+ }{
|
|
|
+ {name: "missing key", m: map[string]any{}, key: "k", want: false},
|
|
|
+ {name: "non-empty string", m: map[string]any{"k": "v"}, key: "k", want: true},
|
|
|
+ {name: "empty string", m: map[string]any{"k": ""}, key: "k", want: false},
|
|
|
+ {name: "non-string value", m: map[string]any{"k": 42}, key: "k", want: false},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ g.Expect(hasNonEmptyString(tt.m, tt.key)).To(g.Equal(tt.want))
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestCapabilities(t *testing.T) {
|
|
|
g.RegisterTestingT(t)
|
|
|
p := &ProviderPassbolt{}
|