externalsecret_controller_manifest_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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 externalsecret
  14. import (
  15. "context"
  16. "testing"
  17. "github.com/go-logr/logr"
  18. "github.com/stretchr/testify/assert"
  19. "github.com/stretchr/testify/require"
  20. v1 "k8s.io/api/core/v1"
  21. apierrors "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  24. "k8s.io/apimachinery/pkg/runtime/schema"
  25. "k8s.io/client-go/kubernetes/scheme"
  26. "k8s.io/utils/ptr"
  27. ctrl "sigs.k8s.io/controller-runtime"
  28. fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
  29. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  30. )
  31. func TestIsGenericTarget(t *testing.T) {
  32. tests := []struct {
  33. name string
  34. es *esv1.ExternalSecret
  35. expected bool
  36. }{
  37. {
  38. name: "nil manifest - Secret target",
  39. es: &esv1.ExternalSecret{
  40. Spec: esv1.ExternalSecretSpec{
  41. Target: esv1.ExternalSecretTarget{
  42. Manifest: nil,
  43. },
  44. },
  45. },
  46. expected: false,
  47. },
  48. {
  49. name: "ConfigMap manifest target",
  50. es: &esv1.ExternalSecret{
  51. Spec: esv1.ExternalSecretSpec{
  52. Target: esv1.ExternalSecretTarget{
  53. Manifest: &esv1.ManifestReference{
  54. APIVersion: "v1",
  55. Kind: "ConfigMap",
  56. },
  57. },
  58. },
  59. },
  60. expected: true,
  61. },
  62. {
  63. name: "Custom Resource manifest target",
  64. es: &esv1.ExternalSecret{
  65. Spec: esv1.ExternalSecretSpec{
  66. Target: esv1.ExternalSecretTarget{
  67. Manifest: &esv1.ManifestReference{
  68. APIVersion: "argoproj.io/v1alpha1",
  69. Kind: "Application",
  70. },
  71. },
  72. },
  73. },
  74. expected: true,
  75. },
  76. }
  77. for _, tt := range tests {
  78. t.Run(tt.name, func(t *testing.T) {
  79. result := isGenericTarget(tt.es)
  80. assert.Equal(t, tt.expected, result)
  81. })
  82. }
  83. }
  84. func TestValidateGenericTarget(t *testing.T) {
  85. tests := []struct {
  86. name string
  87. es *esv1.ExternalSecret
  88. allowGenericTargets bool
  89. expectedError bool
  90. errorContains string
  91. }{
  92. {
  93. name: "ConfigMap target - flag enabled - valid",
  94. es: &esv1.ExternalSecret{
  95. Spec: esv1.ExternalSecretSpec{
  96. Target: esv1.ExternalSecretTarget{
  97. Manifest: &esv1.ManifestReference{
  98. APIVersion: "v1",
  99. Kind: "ConfigMap",
  100. },
  101. },
  102. },
  103. },
  104. allowGenericTargets: true,
  105. expectedError: false,
  106. },
  107. {
  108. name: "ConfigMap target - flag disabled",
  109. es: &esv1.ExternalSecret{
  110. Spec: esv1.ExternalSecretSpec{
  111. Target: esv1.ExternalSecretTarget{
  112. Manifest: &esv1.ManifestReference{
  113. APIVersion: "v1",
  114. Kind: "ConfigMap",
  115. },
  116. },
  117. },
  118. },
  119. allowGenericTargets: false,
  120. expectedError: true,
  121. errorContains: "generic targets are disabled",
  122. },
  123. {
  124. name: "Missing APIVersion",
  125. es: &esv1.ExternalSecret{
  126. Spec: esv1.ExternalSecretSpec{
  127. Target: esv1.ExternalSecretTarget{
  128. Manifest: &esv1.ManifestReference{
  129. APIVersion: "",
  130. Kind: "ConfigMap",
  131. },
  132. },
  133. },
  134. },
  135. allowGenericTargets: true,
  136. expectedError: true,
  137. errorContains: "apiVersion is required",
  138. },
  139. {
  140. name: "Missing Kind",
  141. es: &esv1.ExternalSecret{
  142. Spec: esv1.ExternalSecretSpec{
  143. Target: esv1.ExternalSecretTarget{
  144. Manifest: &esv1.ManifestReference{
  145. APIVersion: "v1",
  146. Kind: "",
  147. },
  148. },
  149. },
  150. },
  151. allowGenericTargets: true,
  152. expectedError: true,
  153. errorContains: "kind is required",
  154. },
  155. }
  156. for _, tt := range tests {
  157. t.Run(tt.name, func(t *testing.T) {
  158. r := &Reconciler{
  159. AllowGenericTargets: tt.allowGenericTargets,
  160. }
  161. log := ctrl.Log.WithName("test")
  162. err := r.validateGenericTarget(log, tt.es)
  163. if tt.expectedError {
  164. assert.Error(t, err)
  165. if tt.errorContains != "" {
  166. assert.Contains(t, err.Error(), tt.errorContains)
  167. }
  168. } else {
  169. assert.NoError(t, err)
  170. }
  171. })
  172. }
  173. }
  174. func TestGetTargetGVK(t *testing.T) {
  175. tests := []struct {
  176. name string
  177. es *esv1.ExternalSecret
  178. expected schema.GroupVersionKind
  179. }{
  180. {
  181. name: "ConfigMap target",
  182. es: &esv1.ExternalSecret{
  183. Spec: esv1.ExternalSecretSpec{
  184. Target: esv1.ExternalSecretTarget{
  185. Manifest: &esv1.ManifestReference{
  186. APIVersion: "v1",
  187. Kind: "ConfigMap",
  188. },
  189. },
  190. },
  191. },
  192. expected: schema.GroupVersionKind{
  193. Group: "",
  194. Version: "v1",
  195. Kind: "ConfigMap",
  196. },
  197. },
  198. {
  199. name: "ArgoCD Application target",
  200. es: &esv1.ExternalSecret{
  201. Spec: esv1.ExternalSecretSpec{
  202. Target: esv1.ExternalSecretTarget{
  203. Manifest: &esv1.ManifestReference{
  204. APIVersion: "argoproj.io/v1alpha1",
  205. Kind: "Application",
  206. },
  207. },
  208. },
  209. },
  210. expected: schema.GroupVersionKind{
  211. Group: "argoproj.io",
  212. Version: "v1alpha1",
  213. Kind: "Application",
  214. },
  215. },
  216. }
  217. for _, tt := range tests {
  218. t.Run(tt.name, func(t *testing.T) {
  219. result := getTargetGVK(tt.es)
  220. assert.Equal(t, tt.expected, result)
  221. })
  222. }
  223. }
  224. func TestGetTargetName(t *testing.T) {
  225. tests := []struct {
  226. name string
  227. es *esv1.ExternalSecret
  228. expected string
  229. }{
  230. {
  231. name: "Use target name when specified",
  232. es: &esv1.ExternalSecret{
  233. ObjectMeta: metav1.ObjectMeta{
  234. Name: "my-external-secret",
  235. },
  236. Spec: esv1.ExternalSecretSpec{
  237. Target: esv1.ExternalSecretTarget{
  238. Name: "custom-target-name",
  239. },
  240. },
  241. },
  242. expected: "custom-target-name",
  243. },
  244. {
  245. name: "Use ExternalSecret name when target name not specified",
  246. es: &esv1.ExternalSecret{
  247. ObjectMeta: metav1.ObjectMeta{
  248. Name: "my-external-secret",
  249. },
  250. Spec: esv1.ExternalSecretSpec{
  251. Target: esv1.ExternalSecretTarget{
  252. Name: "",
  253. },
  254. },
  255. },
  256. expected: "my-external-secret",
  257. },
  258. }
  259. for _, tt := range tests {
  260. t.Run(tt.name, func(t *testing.T) {
  261. result := getTargetName(tt.es)
  262. assert.Equal(t, tt.expected, result)
  263. })
  264. }
  265. }
  266. func TestCreateSimpleManifest(t *testing.T) {
  267. tests := []struct {
  268. name string
  269. kind string
  270. dataMap map[string][]byte
  271. validate func(t *testing.T, obj *unstructured.Unstructured)
  272. }{
  273. {
  274. name: "ConfigMap with data",
  275. kind: "ConfigMap",
  276. dataMap: map[string][]byte{
  277. "key1": []byte("value1"),
  278. "key2": []byte("value2"),
  279. },
  280. validate: func(t *testing.T, obj *unstructured.Unstructured) {
  281. // Directly access the data field
  282. data, ok := obj.Object["data"].(map[string]string)
  283. require.True(t, ok, "data should be map[string]string")
  284. assert.Equal(t, "value1", data["key1"])
  285. assert.Equal(t, "value2", data["key2"])
  286. },
  287. },
  288. {
  289. name: "Custom resource with spec.data",
  290. kind: "CustomResource",
  291. dataMap: map[string][]byte{
  292. "config": []byte("my-config"),
  293. },
  294. validate: func(t *testing.T, obj *unstructured.Unstructured) {
  295. spec, ok := obj.Object["spec"].(map[string]interface{})
  296. require.True(t, ok, "spec should be map[string]interface{}")
  297. data, ok := spec["data"].(map[string]string)
  298. require.True(t, ok, "spec.data should be map[string]string")
  299. assert.Equal(t, "my-config", data["config"])
  300. },
  301. },
  302. }
  303. for _, tt := range tests {
  304. t.Run(tt.name, func(t *testing.T) {
  305. r := &Reconciler{}
  306. obj := &unstructured.Unstructured{
  307. Object: make(map[string]interface{}),
  308. }
  309. obj.SetKind(tt.kind)
  310. result, err := r.createSimpleManifest(obj, tt.dataMap)
  311. require.NoError(t, err)
  312. assert.NotNil(t, result)
  313. if tt.validate != nil {
  314. tt.validate(t, result)
  315. }
  316. })
  317. }
  318. }
  319. func TestApplyTemplateToManifest_SimpleConfigMap(t *testing.T) {
  320. // Setup
  321. _ = esv1.AddToScheme(scheme.Scheme)
  322. fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
  323. r := &Reconciler{
  324. Client: fakeClient,
  325. }
  326. es := &esv1.ExternalSecret{
  327. ObjectMeta: metav1.ObjectMeta{
  328. Name: "test-es",
  329. Namespace: "default",
  330. },
  331. Spec: esv1.ExternalSecretSpec{
  332. Target: esv1.ExternalSecretTarget{
  333. Name: "test-configmap",
  334. Manifest: &esv1.ManifestReference{
  335. APIVersion: "v1",
  336. Kind: "ConfigMap",
  337. },
  338. },
  339. },
  340. }
  341. dataMap := map[string][]byte{
  342. "key1": []byte("value1"),
  343. "key2": []byte("value2"),
  344. }
  345. // Execute
  346. result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, nil)
  347. // Verify
  348. require.NoError(t, err)
  349. assert.NotNil(t, result)
  350. assert.Equal(t, "ConfigMap", result.GetKind())
  351. assert.Equal(t, "test-configmap", result.GetName())
  352. assert.Equal(t, "default", result.GetNamespace())
  353. // Verify data
  354. data, ok := result.Object["data"].(map[string]string)
  355. require.True(t, ok, "data should be map[string]string")
  356. assert.Equal(t, "value1", data["key1"])
  357. assert.Equal(t, "value2", data["key2"])
  358. // Verify managed label
  359. labels := result.GetLabels()
  360. assert.Equal(t, esv1.LabelManagedValue, labels[esv1.LabelManaged])
  361. }
  362. func TestApplyTemplateToManifest_WithMetadata(t *testing.T) {
  363. // Setup
  364. _ = esv1.AddToScheme(scheme.Scheme)
  365. fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
  366. r := &Reconciler{
  367. Client: fakeClient,
  368. }
  369. es := &esv1.ExternalSecret{
  370. ObjectMeta: metav1.ObjectMeta{
  371. Name: "test-es",
  372. Namespace: "default",
  373. },
  374. Spec: esv1.ExternalSecretSpec{
  375. Target: esv1.ExternalSecretTarget{
  376. Name: "test-configmap",
  377. Manifest: &esv1.ManifestReference{
  378. APIVersion: "v1",
  379. Kind: "ConfigMap",
  380. },
  381. Template: &esv1.ExternalSecretTemplate{
  382. EngineVersion: esv1.TemplateEngineV2, // Set engine version
  383. Metadata: esv1.ExternalSecretTemplateMetadata{
  384. Labels: map[string]string{
  385. "app": "myapp",
  386. "tier": "backend",
  387. },
  388. Annotations: map[string]string{
  389. "description": "This is a test",
  390. },
  391. },
  392. },
  393. },
  394. },
  395. }
  396. dataMap := map[string][]byte{
  397. "config": []byte("test-config"),
  398. }
  399. // Execute
  400. result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, nil)
  401. // Verify
  402. require.NoError(t, err)
  403. assert.NotNil(t, result)
  404. // Verify labels
  405. labels := result.GetLabels()
  406. assert.Equal(t, "myapp", labels["app"])
  407. assert.Equal(t, "backend", labels["tier"])
  408. assert.Equal(t, esv1.LabelManagedValue, labels[esv1.LabelManaged])
  409. // Verify annotations
  410. annotations := result.GetAnnotations()
  411. assert.Equal(t, "This is a test", annotations["description"])
  412. }
  413. func TestGetGenericResource(t *testing.T) {
  414. // Setup
  415. _ = esv1.AddToScheme(scheme.Scheme)
  416. // Create a ConfigMap to find
  417. existingConfigMap := &unstructured.Unstructured{
  418. Object: map[string]interface{}{
  419. "apiVersion": "v1",
  420. "kind": "ConfigMap",
  421. "metadata": map[string]interface{}{
  422. "name": "test-cm",
  423. "namespace": "default",
  424. },
  425. "data": map[string]interface{}{
  426. "key": "value",
  427. },
  428. },
  429. }
  430. _ = esv1.AddToScheme(scheme.Scheme)
  431. fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(existingConfigMap).Build()
  432. r := &Reconciler{
  433. Client: fakeClient,
  434. }
  435. es := &esv1.ExternalSecret{
  436. ObjectMeta: metav1.ObjectMeta{
  437. Name: "test-es",
  438. Namespace: "default",
  439. },
  440. Spec: esv1.ExternalSecretSpec{
  441. Target: esv1.ExternalSecretTarget{
  442. Name: "test-cm",
  443. Manifest: &esv1.ManifestReference{
  444. APIVersion: "v1",
  445. Kind: "ConfigMap",
  446. },
  447. },
  448. },
  449. }
  450. // Execute
  451. result, err := r.getGenericResource(context.Background(), logr.Discard(), es)
  452. // Verify
  453. require.NoError(t, err)
  454. assert.NotNil(t, result)
  455. assert.Equal(t, "ConfigMap", result.GetKind())
  456. assert.Equal(t, "test-cm", result.GetName())
  457. // Verify data
  458. data, found, err := unstructured.NestedStringMap(result.Object, "data")
  459. require.NoError(t, err)
  460. require.True(t, found)
  461. assert.Equal(t, "value", data["key"])
  462. }
  463. func TestGetGenericResource_NotFound(t *testing.T) {
  464. // Setup
  465. _ = esv1.AddToScheme(scheme.Scheme)
  466. fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
  467. r := &Reconciler{
  468. Client: fakeClient,
  469. }
  470. es := &esv1.ExternalSecret{
  471. ObjectMeta: metav1.ObjectMeta{
  472. Name: "test-es",
  473. Namespace: "default",
  474. },
  475. Spec: esv1.ExternalSecretSpec{
  476. Target: esv1.ExternalSecretTarget{
  477. Name: "nonexistent-cm",
  478. Manifest: &esv1.ManifestReference{
  479. APIVersion: "v1",
  480. Kind: "ConfigMap",
  481. },
  482. },
  483. },
  484. }
  485. // Execute
  486. result, err := r.getGenericResource(context.Background(), logr.Discard(), es)
  487. // Verify - should return an error and nil result when resource doesn't exist
  488. assert.Error(t, err)
  489. assert.True(t, apierrors.IsNotFound(err))
  490. assert.Nil(t, result)
  491. }
  492. func init() {
  493. // Initialize scheme for tests
  494. _ = esv1.AddToScheme(scheme.Scheme)
  495. _ = v1.AddToScheme(scheme.Scheme)
  496. }
  497. func TestApplyTemplateToManifest_LiteralWithDeployment(t *testing.T) {
  498. // Test that literal templates work with complex objects like Deployments
  499. _ = esv1.AddToScheme(scheme.Scheme)
  500. fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
  501. r := &Reconciler{
  502. Client: fakeClient,
  503. }
  504. es := &esv1.ExternalSecret{
  505. ObjectMeta: metav1.ObjectMeta{
  506. Name: "test-es",
  507. Namespace: "default",
  508. },
  509. Spec: esv1.ExternalSecretSpec{
  510. Target: esv1.ExternalSecretTarget{
  511. Name: "test-deployment",
  512. Manifest: &esv1.ManifestReference{
  513. APIVersion: "apps/v1",
  514. Kind: "Deployment",
  515. },
  516. Template: &esv1.ExternalSecretTemplate{
  517. EngineVersion: esv1.TemplateEngineV2,
  518. TemplateFrom: []esv1.TemplateFrom{
  519. {
  520. Target: "spec",
  521. Literal: ptr.To(`
  522. replicas: {{ .replicas }}
  523. selector:
  524. matchLabels:
  525. app: myapp
  526. template:
  527. metadata:
  528. labels:
  529. app: myapp
  530. spec:
  531. containers:
  532. - name: nginx
  533. image: nginx:{{ .version }}
  534. ports:
  535. - containerPort: 80
  536. `),
  537. },
  538. },
  539. },
  540. },
  541. },
  542. }
  543. dataMap := map[string][]byte{
  544. "replicas": []byte("3"),
  545. "version": []byte("1.21"),
  546. }
  547. result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, nil)
  548. require.NoError(t, err)
  549. assert.NotNil(t, result)
  550. assert.Equal(t, "Deployment", result.GetKind())
  551. assert.Equal(t, "test-deployment", result.GetName())
  552. spec, found, err := unstructured.NestedMap(result.Object, "spec")
  553. require.NoError(t, err)
  554. require.True(t, found, "spec should exist")
  555. replicas, found, err := unstructured.NestedInt64(result.Object, "spec", "replicas")
  556. require.NoError(t, err)
  557. require.True(t, found, "spec.replicas should exist")
  558. assert.Equal(t, int64(3), replicas)
  559. containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers")
  560. require.NoError(t, err)
  561. require.True(t, found, "containers should exist")
  562. require.Len(t, containers, 1, "should have 1 container")
  563. container, ok := containers[0].(map[string]any)
  564. require.True(t, ok, "container should be a map")
  565. assert.Equal(t, "nginx:1.21", container["image"])
  566. t.Logf("Result spec: %+v", spec)
  567. }
  568. func TestApplyTemplateToManifest_MergeBehavior(t *testing.T) {
  569. _ = esv1.AddToScheme(scheme.Scheme)
  570. fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
  571. r := &Reconciler{
  572. Client: fakeClient,
  573. }
  574. es := &esv1.ExternalSecret{
  575. ObjectMeta: metav1.ObjectMeta{
  576. Name: "test-es",
  577. Namespace: "default",
  578. },
  579. Spec: esv1.ExternalSecretSpec{
  580. Target: esv1.ExternalSecretTarget{
  581. Name: "test-slack-config",
  582. Manifest: &esv1.ManifestReference{
  583. APIVersion: "notification.toolkit.fluxcd.io/v1beta1",
  584. Kind: "Provider",
  585. },
  586. Template: &esv1.ExternalSecretTemplate{
  587. EngineVersion: esv1.TemplateEngineV2,
  588. TemplateFrom: []esv1.TemplateFrom{
  589. {
  590. Target: "spec.slack",
  591. Literal: ptr.To(`api_url: {{ .url }}`),
  592. },
  593. },
  594. },
  595. },
  596. },
  597. }
  598. existingResource := &unstructured.Unstructured{
  599. Object: map[string]interface{}{
  600. "apiVersion": "notification.toolkit.fluxcd.io/v1beta1",
  601. "kind": "Provider",
  602. "metadata": map[string]interface{}{
  603. "name": "test-slack-config",
  604. "namespace": "default",
  605. "resourceVersion": "12345",
  606. "uid": "test-uid-123",
  607. },
  608. "spec": map[string]interface{}{
  609. "type": "slack",
  610. "slack": map[string]interface{}{
  611. "channel": "general",
  612. "username": "bot",
  613. },
  614. },
  615. },
  616. }
  617. dataMap := map[string][]byte{
  618. "url": []byte("https://hooks.slack.com/services/XXX"),
  619. }
  620. result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, existingResource)
  621. require.NoError(t, err)
  622. assert.NotNil(t, result)
  623. assert.Equal(t, "Provider", result.GetKind())
  624. assert.Equal(t, "test-slack-config", result.GetName())
  625. specType, found, err := unstructured.NestedString(result.Object, "spec", "type")
  626. require.NoError(t, err)
  627. require.True(t, found, "spec.type should be preserved")
  628. assert.Equal(t, "slack", specType, "spec.type should be preserved from existing resource")
  629. slackChannel, found, err := unstructured.NestedString(result.Object, "spec", "slack", "channel")
  630. require.NoError(t, err)
  631. require.True(t, found, "spec.slack.channel should be preserved")
  632. assert.Equal(t, "general", slackChannel, "spec.slack.channel should be preserved from existing resource")
  633. slackUsername, found, err := unstructured.NestedString(result.Object, "spec", "slack", "username")
  634. require.NoError(t, err)
  635. require.True(t, found, "spec.slack.username should be preserved")
  636. assert.Equal(t, "bot", slackUsername, "spec.slack.username should be preserved from existing resource")
  637. apiURL, found, err := unstructured.NestedString(result.Object, "spec", "slack", "api_url")
  638. require.NoError(t, err)
  639. require.True(t, found, "spec.slack.api_url should be added from template")
  640. assert.Equal(t, "https://hooks.slack.com/services/XXX", apiURL, "spec.slack.api_url should come from template")
  641. assert.Equal(t, "12345", result.GetResourceVersion(), "resourceVersion should be preserved")
  642. assert.Equal(t, "test-uid-123", string(result.GetUID()), "uid should be preserved")
  643. t.Logf("Result spec: %+v", result.Object["spec"])
  644. }