client_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package volcengine
  14. import (
  15. "context"
  16. "errors"
  17. "testing"
  18. "github.com/stretchr/testify/assert"
  19. "github.com/volcengine/volcengine-go-sdk/service/kms"
  20. "github.com/volcengine/volcengine-go-sdk/volcengine/request"
  21. corev1 "k8s.io/api/core/v1"
  22. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  23. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  24. )
  25. type fakePushSecretData struct {
  26. Metadata *apiextensionsv1.JSON
  27. SecretKey string
  28. RemoteKey string
  29. Property string
  30. }
  31. func (f fakePushSecretData) GetMetadata() *apiextensionsv1.JSON {
  32. return f.Metadata
  33. }
  34. func (f fakePushSecretData) GetSecretKey() string {
  35. return f.SecretKey
  36. }
  37. func (f fakePushSecretData) GetRemoteKey() string {
  38. return f.RemoteKey
  39. }
  40. func (f fakePushSecretData) GetProperty() string {
  41. return f.Property
  42. }
  43. type fakePushScretRemoteRef struct {
  44. RemoteKey string
  45. Property string
  46. }
  47. func (f fakePushScretRemoteRef) GetRemoteKey() string {
  48. return f.RemoteKey
  49. }
  50. func (f fakePushScretRemoteRef) GetProperty() string {
  51. return f.Property
  52. }
  53. // MockKMSClient is a mock of KMSAPI interface.
  54. type MockKMSClient struct {
  55. kms.KMSAPI
  56. DescribeRegionsFunc func(*kms.DescribeRegionsInput) (*kms.DescribeRegionsOutput, error)
  57. DescribeSecretWithContextFunc func(context.Context, *kms.DescribeSecretInput, ...request.Option) (*kms.DescribeSecretOutput, error)
  58. GetSecretValueWithContextFunc func(context.Context, *kms.GetSecretValueInput, ...request.Option) (*kms.GetSecretValueOutput, error)
  59. }
  60. // DescribeRegions mocks the DescribeRegions method.
  61. func (m *MockKMSClient) DescribeRegions(input *kms.DescribeRegionsInput) (*kms.DescribeRegionsOutput, error) {
  62. if m.DescribeRegionsFunc != nil {
  63. return m.DescribeRegionsFunc(input)
  64. }
  65. return nil, errors.New("DescribeRegions is not implemented")
  66. }
  67. // DescribeSecretWithContext mocks the DescribeSecretWithContext method.
  68. func (m *MockKMSClient) DescribeSecretWithContext(ctx context.Context, input *kms.DescribeSecretInput, opts ...request.Option) (*kms.DescribeSecretOutput, error) {
  69. if m.DescribeSecretWithContextFunc != nil {
  70. return m.DescribeSecretWithContextFunc(ctx, input, opts...)
  71. }
  72. return nil, errors.New("DescribeSecretWithContext is not implemented")
  73. }
  74. // GetSecretValueWithContext mocks the GetSecretValueWithContext method.
  75. func (m *MockKMSClient) GetSecretValueWithContext(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  76. if m.GetSecretValueWithContextFunc != nil {
  77. return m.GetSecretValueWithContextFunc(ctx, input, opts...)
  78. }
  79. return nil, errors.New("GetSecretValueWithContext is not implemented")
  80. }
  81. func TestNew_should_return_a_new_client(t *testing.T) {
  82. mockKMS := &MockKMSClient{}
  83. client := NewClient(mockKMS)
  84. assert.NotNil(t, client)
  85. assert.Equal(t, mockKMS, client.kms)
  86. }
  87. func TestClient_PushSecret_should_return_not_implemented_error(t *testing.T) {
  88. client := &Client{}
  89. err := client.PushSecret(context.Background(), &corev1.Secret{}, &fakePushSecretData{})
  90. assert.Error(t, err)
  91. assert.Equal(t, notImplemented, err.Error())
  92. }
  93. func TestClient_DeleteSecret_should_return_not_implemented_error(t *testing.T) {
  94. client := &Client{}
  95. err := client.DeleteSecret(context.Background(), &fakePushSecretData{})
  96. assert.Error(t, err)
  97. assert.Equal(t, notImplemented, err.Error())
  98. }
  99. func TestClient_GetAllSecrets_should_return_not_implemented_error(t *testing.T) {
  100. client := &Client{}
  101. _, err := client.GetAllSecrets(context.Background(), esapi.ExternalSecretFind{})
  102. assert.Error(t, err)
  103. assert.Equal(t, notImplemented, err.Error())
  104. }
  105. func TestClient_Close_should_return_nil(t *testing.T) {
  106. client := &Client{}
  107. err := client.Close(context.Background())
  108. assert.NoError(t, err)
  109. }
  110. func TestClient_Validate_should_return_ready_when_kms_client_is_initialized(t *testing.T) {
  111. mockKMS := &MockKMSClient{
  112. DescribeRegionsFunc: func(*kms.DescribeRegionsInput) (*kms.DescribeRegionsOutput, error) {
  113. return &kms.DescribeRegionsOutput{}, nil
  114. },
  115. }
  116. client := NewClient(mockKMS)
  117. result, err := client.Validate()
  118. assert.NoError(t, err)
  119. assert.Equal(t, esapi.ValidationResultReady, result)
  120. }
  121. func TestClient_Validate_should_return_error_when_kms_client_is_not_initialized(t *testing.T) {
  122. client := NewClient(nil)
  123. result, err := client.Validate()
  124. assert.Error(t, err)
  125. assert.Equal(t, "kms client is not initialized", err.Error())
  126. assert.Equal(t, esapi.ValidationResultError, result)
  127. }
  128. func TestClient_GetSecret_should_return_secret_value_when_property_is_empty(t *testing.T) {
  129. secretValue := "my-secret-value"
  130. mockKMS := &MockKMSClient{
  131. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  132. return &kms.GetSecretValueOutput{
  133. SecretValue: &secretValue,
  134. }, nil
  135. },
  136. }
  137. client := NewClient(mockKMS)
  138. value, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
  139. Key: "my-secret",
  140. })
  141. assert.NoError(t, err)
  142. assert.Equal(t, []byte(secretValue), value)
  143. }
  144. func TestClient_GetSecret_should_return_property_value_when_secret_is_json_and_property_exists(t *testing.T) {
  145. secretValue := `{"user":"admin","pass":"1234"}`
  146. mockKMS := &MockKMSClient{
  147. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  148. return &kms.GetSecretValueOutput{
  149. SecretValue: &secretValue,
  150. }, nil
  151. },
  152. }
  153. client := NewClient(mockKMS)
  154. value, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
  155. Key: "my-secret",
  156. Property: "pass",
  157. })
  158. assert.NoError(t, err)
  159. assert.Equal(t, []byte("1234"), value)
  160. }
  161. func TestClient_GetSecret_should_return_raw_json_value_when_property_is_json_object(t *testing.T) {
  162. secretValue := `{"config":{"foo":"bar"},"pass":"1234"}`
  163. mockKMS := &MockKMSClient{
  164. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  165. return &kms.GetSecretValueOutput{
  166. SecretValue: &secretValue,
  167. }, nil
  168. },
  169. }
  170. client := NewClient(mockKMS)
  171. value, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
  172. Key: "my-secret",
  173. Property: "config",
  174. })
  175. assert.NoError(t, err)
  176. assert.Equal(t, []byte(`{"foo":"bar"}`), value)
  177. }
  178. func TestClient_GetSecret_should_return_error_when_property_does_not_exist(t *testing.T) {
  179. secretValue := `{"user":"admin","pass":"1234"}`
  180. mockKMS := &MockKMSClient{
  181. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  182. return &kms.GetSecretValueOutput{
  183. SecretValue: &secretValue,
  184. }, nil
  185. },
  186. }
  187. client := NewClient(mockKMS)
  188. _, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
  189. Key: "my-secret",
  190. Property: "non-existent",
  191. })
  192. assert.Error(t, err)
  193. assert.Equal(t, `property "non-existent" not found in secret`, err.Error())
  194. }
  195. func TestClient_GetSecret_should_return_error_when_secret_is_not_valid_json(t *testing.T) {
  196. secretValue := `not-a-json`
  197. mockKMS := &MockKMSClient{
  198. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  199. return &kms.GetSecretValueOutput{
  200. SecretValue: &secretValue,
  201. }, nil
  202. },
  203. }
  204. client := NewClient(mockKMS)
  205. _, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
  206. Key: "my-secret",
  207. Property: "prop",
  208. })
  209. assert.Error(t, err)
  210. assert.Contains(t, err.Error(), "failed to unmarshal secret")
  211. }
  212. func TestClient_GetSecret_should_return_error_when_api_call_fails(t *testing.T) {
  213. mockKMS := &MockKMSClient{
  214. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  215. return nil, errors.New("api error")
  216. },
  217. }
  218. client := NewClient(mockKMS)
  219. _, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
  220. Key: "my-secret",
  221. })
  222. assert.Error(t, err)
  223. assert.Equal(t, "api error", err.Error())
  224. }
  225. func TestClient_GetSecret_should_return_error_when_secret_value_is_nil(t *testing.T) {
  226. mockKMS := &MockKMSClient{
  227. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  228. return &kms.GetSecretValueOutput{
  229. SecretValue: nil,
  230. }, nil
  231. },
  232. }
  233. client := NewClient(mockKMS)
  234. _, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
  235. Key: "my-secret",
  236. })
  237. assert.Error(t, err)
  238. assert.Equal(t, "secret my-secret has no value", err.Error())
  239. }
  240. func TestClient_GetSecretMap_should_return_map_when_secret_is_valid_json(t *testing.T) {
  241. secretValue := `{"user":"admin"}`
  242. mockKMS := &MockKMSClient{
  243. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  244. return &kms.GetSecretValueOutput{
  245. SecretValue: &secretValue,
  246. }, nil
  247. },
  248. }
  249. client := NewClient(mockKMS)
  250. secretMap, err := client.GetSecretMap(context.Background(), esapi.ExternalSecretDataRemoteRef{
  251. Key: "my-secret",
  252. })
  253. assert.NoError(t, err)
  254. expectedMap := map[string][]byte{
  255. "user": []byte(`"admin"`),
  256. }
  257. assert.Equal(t, expectedMap, secretMap)
  258. }
  259. func TestClient_GetSecretMap_should_return_error_when_secret_is_not_valid_json(t *testing.T) {
  260. secretValue := `not-a-json`
  261. mockKMS := &MockKMSClient{
  262. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  263. return &kms.GetSecretValueOutput{
  264. SecretValue: &secretValue,
  265. }, nil
  266. },
  267. }
  268. client := NewClient(mockKMS)
  269. _, err := client.GetSecretMap(context.Background(), esapi.ExternalSecretDataRemoteRef{
  270. Key: "my-secret",
  271. })
  272. assert.Error(t, err)
  273. assert.Contains(t, err.Error(), "failed to unmarshal secret")
  274. }
  275. func TestClient_GetSecretMap_should_return_error_when_api_call_fails(t *testing.T) {
  276. mockKMS := &MockKMSClient{
  277. GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
  278. return nil, errors.New("api error")
  279. },
  280. }
  281. client := NewClient(mockKMS)
  282. _, err := client.GetSecretMap(context.Background(), esapi.ExternalSecretDataRemoteRef{
  283. Key: "my-secret",
  284. })
  285. assert.Error(t, err)
  286. assert.Equal(t, "api error", err.Error())
  287. }
  288. func TestClient_SecretExists_should_return_error_when_secret_name_is_empty(t *testing.T) {
  289. mockKMS := &MockKMSClient{}
  290. c := NewClient(mockKMS)
  291. exists, err := c.SecretExists(context.Background(), fakePushScretRemoteRef{
  292. RemoteKey: "",
  293. })
  294. assert.False(t, exists)
  295. assert.Error(t, err)
  296. assert.Equal(t, "secret name is empty", err.Error())
  297. }
  298. func TestClient_SecretExists_should_return_error_when_describe_secret_fails(t *testing.T) {
  299. expectedErr := errors.New("failed to describe secret")
  300. mockKMS := &MockKMSClient{
  301. DescribeSecretWithContextFunc: func(ctx context.Context, input *kms.DescribeSecretInput, opts ...request.Option) (*kms.DescribeSecretOutput, error) {
  302. return nil, expectedErr
  303. },
  304. }
  305. c := NewClient(mockKMS)
  306. exists, err := c.SecretExists(context.Background(), fakePushScretRemoteRef{
  307. RemoteKey: "test-secret",
  308. })
  309. assert.False(t, exists)
  310. assert.Error(t, err)
  311. assert.Equal(t, expectedErr, err)
  312. }
  313. func TestClient_SecretExists_should_return_true_when_secret_exists(t *testing.T) {
  314. mockKMS := &MockKMSClient{
  315. DescribeSecretWithContextFunc: func(ctx context.Context, input *kms.DescribeSecretInput, opts ...request.Option) (*kms.DescribeSecretOutput, error) {
  316. return &kms.DescribeSecretOutput{}, nil
  317. },
  318. }
  319. c := NewClient(mockKMS)
  320. exists, err := c.SecretExists(context.Background(), fakePushScretRemoteRef{
  321. RemoteKey: "test-secret",
  322. })
  323. assert.True(t, exists)
  324. assert.NoError(t, err)
  325. }