// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package accumulator

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
	"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
	"sigs.k8s.io/kustomize/api/provider"
	"sigs.k8s.io/kustomize/api/resmap"
	resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
	"sigs.k8s.io/kustomize/kyaml/resid"
)

const notEqualErrFmt = "expected (self) doesn't match actual (other): %v"

func TestNameReferenceHappyRun(t *testing.T) {
	m := resmaptest_test.NewRmBuilderDefault(t).AddWithName(
		"cm1",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name": "someprefix-cm1-somehash",
			},
		}).AddWithName(
		"cm2",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name": "someprefix-cm2-somehash",
			},
		}).AddWithName(
		"secret1",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Secret",
			"metadata": map[string]interface{}{
				"name": "someprefix-secret1-somehash",
			},
		}).AddWithName(
		"claim1",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "PersistentVolumeClaim",
			"metadata": map[string]interface{}{
				"name": "someprefix-claim1",
			},
		}).Add(
		map[string]interface{}{
			"group":      "networking.k8s.io",
			"apiVersion": "v1beta1",
			"kind":       "Ingress",
			"metadata": map[string]interface{}{
				"name": "ingress1",
				"annotations": map[string]interface{}{
					"ingress.kubernetes.io/auth-secret":           "secret1",
					"nginx.ingress.kubernetes.io/auth-secret":     "secret1",
					"nginx.ingress.kubernetes.io/auth-tls-secret": "secret1",
				},
			},
			"spec": map[string]interface{}{
				"backend": map[string]interface{}{
					"serviceName": "testsvc",
					"servicePort": "80",
				},
			},
		},
	).Add(
		map[string]interface{}{
			"group":      "apps",
			"apiVersion": "v1",
			"kind":       "Deployment",
			"metadata": map[string]interface{}{
				"name": "deploy1",
			},
			"spec": map[string]interface{}{
				"template": map[string]interface{}{
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"name":  "nginx",
								"image": "nginx:1.7.9",
								"env": []interface{}{
									map[string]interface{}{
										"name": "CM_FOO",
										"valueFrom": map[string]interface{}{
											"configMapKeyRef": map[string]interface{}{
												"name": "cm1",
												"key":  "somekey",
											},
										},
									},
									map[string]interface{}{
										"name": "SECRET_FOO",
										"valueFrom": map[string]interface{}{
											"secretKeyRef": map[string]interface{}{
												"name": "secret1",
												"key":  "somekey",
											},
										},
									},
								},
								"envFrom": []interface{}{
									map[string]interface{}{
										"configMapRef": map[string]interface{}{
											"name": "cm1",
											"key":  "somekey",
										},
									},
									map[string]interface{}{
										"secretRef": map[string]interface{}{
											"name": "secret1",
											"key":  "somekey",
										},
									},
								},
							},
						},
						"imagePullSecrets": []interface{}{
							map[string]interface{}{
								"name": "secret1",
							},
						},
						"volumes": map[string]interface{}{
							"configMap": map[string]interface{}{
								"name": "cm1",
							},
							"projected": map[string]interface{}{
								"sources": map[string]interface{}{
									"configMap": map[string]interface{}{
										"name": "cm2",
									},
									"secret": map[string]interface{}{
										"name": "secret1",
									},
								},
							},
							"secret": map[string]interface{}{
								"secretName": "secret1",
							},
							"persistentVolumeClaim": map[string]interface{}{
								"claimName": "claim1",
							},
						},
					},
				},
			},
		}).Add(
		map[string]interface{}{
			"group":      "apps",
			"apiVersion": "v1",
			"kind":       "StatefulSet",
			"metadata": map[string]interface{}{
				"name": "statefulset1",
			},
			"spec": map[string]interface{}{
				"template": map[string]interface{}{
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"name":  "nginx",
								"image": "nginx:1.7.9",
							},
						},
						"volumes": map[string]interface{}{
							"projected": map[string]interface{}{
								"sources": map[string]interface{}{
									"configMap": map[string]interface{}{
										"name": "cm2",
									},
									"secret": map[string]interface{}{
										"name": "secret1",
									},
								},
							},
						},
					},
				},
			},
		}).AddWithName("sa",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ServiceAccount",
			"metadata": map[string]interface{}{
				"name":      "someprefix-sa",
				"namespace": "test",
			},
		}).Add(
		map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRoleBinding",
			"metadata": map[string]interface{}{
				"name": "crb",
			},
			"subjects": []interface{}{
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      "sa",
					"namespace": "test",
				},
			},
		}).Add(
		map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRole",
			"metadata": map[string]interface{}{
				"name": "cr",
			},
			"rules": []interface{}{
				map[string]interface{}{
					"resources": []interface{}{
						"secrets",
					},
					"resourceNames": []interface{}{
						"secret1",
						"secret1",
						"secret2",
						"cm1",
					},
				},
			},
		}).Add(
		map[string]interface{}{
			"apiVersion": "batch/v1beta1",
			"kind":       "CronJob",
			"metadata": map[string]interface{}{
				"name": "cronjob1",
			},
			"spec": map[string]interface{}{
				"schedule": "0 14 * * *",
				"jobTemplate": map[string]interface{}{
					"spec": map[string]interface{}{
						"template": map[string]interface{}{
							"spec": map[string]interface{}{
								"containers": []interface{}{
									map[string]interface{}{
										"name":  "main",
										"image": "myimage",
									},
								},
								"volumes": map[string]interface{}{
									"projected": map[string]interface{}{
										"sources": map[string]interface{}{
											"configMap": map[string]interface{}{
												"name": "cm2",
											},
											"secret": map[string]interface{}{
												"name": "secret1",
											},
										},
									},
								},
							},
						},
					},
				},
			},
		}).ResMap()

	expected := resmaptest_test.NewSeededRmBuilderDefault(
		t, m.ShallowCopy()).ReplaceResource(
		map[string]interface{}{
			"group":      "apps",
			"apiVersion": "v1",
			"kind":       "Deployment",
			"metadata": map[string]interface{}{
				"name": "deploy1",
			},
			"spec": map[string]interface{}{
				"template": map[string]interface{}{
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"name":  "nginx",
								"image": "nginx:1.7.9",
								"env": []interface{}{
									map[string]interface{}{
										"name": "CM_FOO",
										"valueFrom": map[string]interface{}{
											"configMapKeyRef": map[string]interface{}{
												"name": "someprefix-cm1-somehash",
												"key":  "somekey",
											},
										},
									},
									map[string]interface{}{
										"name": "SECRET_FOO",
										"valueFrom": map[string]interface{}{
											"secretKeyRef": map[string]interface{}{
												"name": "someprefix-secret1-somehash",
												"key":  "somekey",
											},
										},
									},
								},
								"envFrom": []interface{}{
									map[string]interface{}{
										"configMapRef": map[string]interface{}{
											"name": "someprefix-cm1-somehash",
											"key":  "somekey",
										},
									},
									map[string]interface{}{
										"secretRef": map[string]interface{}{
											"name": "someprefix-secret1-somehash",
											"key":  "somekey",
										},
									},
								},
							},
						},
						"imagePullSecrets": []interface{}{
							map[string]interface{}{
								"name": "someprefix-secret1-somehash",
							},
						},
						"volumes": map[string]interface{}{
							"configMap": map[string]interface{}{
								"name": "someprefix-cm1-somehash",
							},
							"projected": map[string]interface{}{
								"sources": map[string]interface{}{
									"configMap": map[string]interface{}{
										"name": "someprefix-cm2-somehash",
									},
									"secret": map[string]interface{}{
										"name": "someprefix-secret1-somehash",
									},
								},
							},
							"secret": map[string]interface{}{
								"secretName": "someprefix-secret1-somehash",
							},
							"persistentVolumeClaim": map[string]interface{}{
								"claimName": "someprefix-claim1",
							},
						},
					},
				},
			},
		}).ReplaceResource(
		map[string]interface{}{
			"group":      "apps",
			"apiVersion": "v1",
			"kind":       "StatefulSet",
			"metadata": map[string]interface{}{
				"name": "statefulset1",
			},
			"spec": map[string]interface{}{
				"template": map[string]interface{}{
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"name":  "nginx",
								"image": "nginx:1.7.9",
							},
						},
						"volumes": map[string]interface{}{
							"projected": map[string]interface{}{
								"sources": map[string]interface{}{
									"configMap": map[string]interface{}{
										"name": "someprefix-cm2-somehash",
									},
									"secret": map[string]interface{}{
										"name": "someprefix-secret1-somehash",
									},
								},
							},
						},
					},
				},
			},
		}).ReplaceResource(
		map[string]interface{}{
			"group":      "networking.k8s.io",
			"apiVersion": "v1beta1",
			"kind":       "Ingress",
			"metadata": map[string]interface{}{
				"name": "ingress1",
				"annotations": map[string]interface{}{
					"ingress.kubernetes.io/auth-secret":           "someprefix-secret1-somehash",
					"nginx.ingress.kubernetes.io/auth-secret":     "someprefix-secret1-somehash",
					"nginx.ingress.kubernetes.io/auth-tls-secret": "someprefix-secret1-somehash",
				},
			},
			"spec": map[string]interface{}{
				"backend": map[string]interface{}{
					"serviceName": "testsvc",
					"servicePort": "80",
				},
			},
		}).ReplaceResource(
		map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRoleBinding",
			"metadata": map[string]interface{}{
				"name": "crb",
			},
			"subjects": []interface{}{
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      "someprefix-sa",
					"namespace": "test",
				},
			},
		}).ReplaceResource(
		map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRole",
			"metadata": map[string]interface{}{
				"name": "cr",
			},
			"rules": []interface{}{
				map[string]interface{}{
					"resources": []interface{}{
						"secrets",
					},
					"resourceNames": []interface{}{
						"someprefix-secret1-somehash",
						"someprefix-secret1-somehash",
						"secret2",
						"someprefix-cm1-somehash",
					},
				},
			},
		}).ReplaceResource(
		map[string]interface{}{
			"apiVersion": "batch/v1beta1",
			"kind":       "CronJob",
			"metadata": map[string]interface{}{
				"name": "cronjob1",
			},
			"spec": map[string]interface{}{
				"schedule": "0 14 * * *",
				"jobTemplate": map[string]interface{}{
					"spec": map[string]interface{}{
						"template": map[string]interface{}{
							"spec": map[string]interface{}{
								"containers": []interface{}{
									map[string]interface{}{
										"name":  "main",
										"image": "myimage",
									},
								},
								"volumes": map[string]interface{}{
									"projected": map[string]interface{}{
										"sources": map[string]interface{}{
											"configMap": map[string]interface{}{
												"name": "someprefix-cm2-somehash",
											},
											"secret": map[string]interface{}{
												"name": "someprefix-secret1-somehash",
											},
										},
									},
								},
							},
						},
					},
				},
			},
		}).ResMap()

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	err := nrt.Transform(m)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if err = expected.ErrorIfNotEqualLists(m); err != nil {
		t.Fatalf(notEqualErrFmt, err)
	}
}

func TestNameReferenceUnhappyRun(t *testing.T) {
	tests := []struct {
		resMap      resmap.ResMap
		expectedErr string
	}{
		{
			resMap: resmaptest_test.NewRmBuilderDefault(t).Add(
				map[string]interface{}{
					"apiVersion": "rbac.authorization.k8s.io/v1",
					"kind":       "ClusterRole",
					"metadata": map[string]interface{}{
						"name": "cr",
					},
					"rules": []interface{}{
						map[string]interface{}{
							"resources": []interface{}{
								"secrets",
							},
							"resourceNames": []interface{}{
								[]interface{}{},
							},
						},
					},
				}).ResMap(),
			expectedErr: "is expected to be"},
		{
			resMap: resmaptest_test.NewRmBuilderDefault(t).Add(
				map[string]interface{}{
					"apiVersion": "rbac.authorization.k8s.io/v1",
					"kind":       "ClusterRole",
					"metadata": map[string]interface{}{
						"name": "cr",
					},
					"rules": []interface{}{
						map[string]interface{}{
							"resources": []interface{}{
								"secrets",
							},
							"resourceNames": map[string]interface{}{
								"foo": "bar",
							},
						},
					},
				}).ResMap(),
			expectedErr: `updating name reference in 'rules/resourceNames' field of 'ClusterRole.v1.rbac.authorization.k8s.io/cr.[noNs]': ` +
				`considering field 'rules/resourceNames' of object ClusterRole.v1.rbac.authorization.k8s.io/cr.[noNs]: visit traversal on ` +
				`path: [resourceNames]: path config error; no 'name' field in node`,
		},
		{
			// When targeting a map reference, we need to update both name and namespace, so multiple
			// possible referral targets with different namespaces should not be considered identical.
			// This test covers a bug where the difference in namespace was ignored and one candidate was chosen at random.
			resMap: resmaptest_test.NewRmBuilderDefault(t).AddWithNsAndName(
				"", orgname,
				map[string]interface{}{
					"apiVersion": "v1",
					"kind":       "ServiceAccount",
					"metadata": map[string]interface{}{
						"name":      orgname,
						"namespace": ns1,
					},
				},
			).AddWithNsAndName(
				"", orgname,
				map[string]interface{}{
					"apiVersion": "v1",
					"kind":       "ServiceAccount",
					"metadata": map[string]interface{}{
						"name":      orgname,
						"namespace": ns2,
					},
				},
			).Add(
				map[string]interface{}{
					"apiVersion": "rbac.authorization.k8s.io/v1",
					"kind":       "ClusterRoleBinding",
					"metadata": map[string]interface{}{
						"name": orgname,
					},
					"roleRef": map[string]interface{}{
						"apiGroup": "rbac.authorization.k8s.io",
						"kind":     "ClusterRole",
						"name":     orgname,
					},
					"subjects": []interface{}{
						map[string]interface{}{
							"kind": "ServiceAccount",
							"name": orgname,
						},
					},
				},
			).ResMap(),
			expectedErr: "found multiple possible referrals: ServiceAccount.v1.[noGrp]/uniquename.ns1, ServiceAccount.v1.[noGrp]/uniquename.ns2",
		},
	}

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	for _, test := range tests {
		err := nrt.Transform(test.resMap)
		if err == nil {
			t.Fatalf("expected error to happen")
		}

		if !strings.Contains(err.Error(), test.expectedErr) {
			t.Fatalf("Incorrect error.\nExpected:\n %s\nGot:\n%v",
				test.expectedErr, err)
		}
	}
}

func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
	rf := provider.NewDefaultDepProvider().GetResourceFactory()

	v1, err := rf.FromMapWithName(
		"volume1",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "PersistentVolume",
			"metadata": map[string]interface{}{
				"name": "someprefix-volume1",
			},
		})
	if err != nil {
		t.Fatalf("failed to get new instance with given name: %v", err)
	}
	c1, err := rf.FromMapWithName(
		"claim1",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "PersistentVolumeClaim",
			"metadata": map[string]interface{}{
				"name":      "someprefix-claim1",
				"namespace": "some-namespace",
			},
			"spec": map[string]interface{}{
				"volumeName": "volume1",
			},
		})
	if err != nil {
		t.Fatalf("failed to get new instance with given name: %v", err)
	}
	v2 := v1.DeepCopy()
	c2, err := rf.FromMapWithName(
		"claim1",
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "PersistentVolumeClaim",
			"metadata": map[string]interface{}{
				"name":      "someprefix-claim1",
				"namespace": "some-namespace",
			},
			"spec": map[string]interface{}{
				"volumeName": "someprefix-volume1",
			},
		})
	if err != nil {
		t.Fatalf("failed to get new instance with given name: %v", err)
	}

	m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(v1).AddR(c1).ResMap()

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	if err := nrt.Transform(m1); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(v2).AddR(c2).ResMap()
	v2.AppendRefBy(c2.CurId())

	if err := m1.ErrorIfNotEqualLists(m2); err != nil {
		t.Fatalf(notEqualErrFmt, err)
	}
}

// utility map to create a deployment object
// with (metadatanamespace, metadataname) as key
// and pointing to "refname" secret and configmap
func deploymentMap(metadatanamespace string, metadataname string,
	configmapref string, secretref string) map[string]interface{} {
	deployment := map[string]interface{}{
		"group":      "apps",
		"apiVersion": "v1",
		"kind":       "Deployment",
		"metadata": map[string]interface{}{
			"name": metadataname,
		},
		"spec": map[string]interface{}{
			"template": map[string]interface{}{
				"spec": map[string]interface{}{
					"containers": []interface{}{
						map[string]interface{}{
							"name":  "nginx",
							"image": "nginx:1.7.9",
							"env": []interface{}{
								map[string]interface{}{
									"name": "CM_FOO",
									"valueFrom": map[string]interface{}{
										"configMapKeyRef": map[string]interface{}{
											"name": configmapref,
											"key":  "somekey",
										},
									},
								},
								map[string]interface{}{
									"name": "SECRET_FOO",
									"valueFrom": map[string]interface{}{
										"secretKeyRef": map[string]interface{}{
											"name": secretref,
											"key":  "somekey",
										},
									},
								},
							},
						},
					},
				},
			},
		},
	}

	if metadatanamespace != "" {
		metadata := deployment["metadata"].(map[string]interface{})
		metadata["namespace"] = metadatanamespace
	}
	return deployment
}

const (
	defaultNs = "default"
	ns1       = "ns1"
	ns2       = "ns2"
	ns3       = "ns3"
	ns4       = "ns4"

	orgname      = "uniquename"
	prefixedname = "prefix-uniquename"
	suffixedname = "uniquename-suffix"
	modifiedname = "modifiedname"
)

// TestNameReferenceNamespace creates serviceAccount and clusterRoleBinding
// object with the same original names (uniquename) in different namespaces
// and with different current Id.
func TestNameReferenceNamespace(t *testing.T) {
	m := resmaptest_test.NewRmBuilderDefault(t).
		// Add ConfigMap with the same org name in noNs, "ns1" and "ns2" namespaces
		AddWithName(orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name": modifiedname,
			}}).
		AddWithNsAndName(ns1, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name":      prefixedname,
				"namespace": ns1,
			}}).
		AddWithNsAndName(ns2, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name":      suffixedname,
				"namespace": ns2,
			}}).
		// Add Secret with the same org name in noNs, "ns1" and "ns2" namespaces
		AddWithNsAndName(defaultNs, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Secret",
			"metadata": map[string]interface{}{
				"name":      modifiedname,
				"namespace": defaultNs,
			}}).
		AddWithNsAndName(ns1, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Secret",
			"metadata": map[string]interface{}{
				"name":      prefixedname,
				"namespace": ns1,
			}}).
		AddWithNsAndName(ns2, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Secret",
			"metadata": map[string]interface{}{
				"name":      suffixedname,
				"namespace": ns2,
			}}).
		// Add Deployment with the same org name in noNs, "ns1" and "ns2" namespaces
		AddWithNsAndName(defaultNs, orgname, deploymentMap(defaultNs, modifiedname, modifiedname, modifiedname)).
		AddWithNsAndName(ns1, orgname, deploymentMap(ns1, prefixedname, orgname, orgname)).
		AddWithNsAndName(ns2, orgname, deploymentMap(ns2, suffixedname, orgname, orgname)).ResMap()

	expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
		ReplaceResource(deploymentMap(defaultNs, modifiedname, modifiedname, modifiedname)).
		ReplaceResource(deploymentMap(ns1, prefixedname, prefixedname, prefixedname)).
		ReplaceResource(deploymentMap(ns2, suffixedname, suffixedname, suffixedname)).ResMap()

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	err := nrt.Transform(m)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	m.RemoveBuildAnnotations()
	if err = expected.ErrorIfNotEqualLists(m); err != nil {
		t.Fatalf(notEqualErrFmt, err)
	}
}

// TestNameReferenceNamespace creates serviceAccount and clusterRoleBinding
// object with the same original names (uniquename) in different namespaces
// and with different current Id.
func TestNameReferenceClusterWide(t *testing.T) {
	m := resmaptest_test.NewRmBuilderDefault(t).
		// Add ServiceAccount with the same org name in noNs, "ns1" and "ns2" namespaces
		AddWithName(orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ServiceAccount",
			"metadata": map[string]interface{}{
				"name": modifiedname,
			}}).
		AddWithNsAndName(ns1, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ServiceAccount",
			"metadata": map[string]interface{}{
				"name":      prefixedname,
				"namespace": ns1,
			}}).
		AddWithNsAndName(ns2, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ServiceAccount",
			"metadata": map[string]interface{}{
				"name":      suffixedname,
				"namespace": ns2,
			}}).
		// Add a PersistentVolume to have a clusterwide resource
		AddWithName(orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "PersistentVolume",
			"metadata": map[string]interface{}{
				"name": modifiedname,
			}}).
		AddWithName(orgname, map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRole",
			"metadata": map[string]interface{}{
				"name": modifiedname,
			},
			"rules": []interface{}{
				map[string]interface{}{
					"resources": []interface{}{
						"persistentvolumes",
					},
					"resourceNames": []interface{}{
						orgname,
					},
				},
			}}).
		AddWithName(orgname, map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRoleBinding",
			"metadata": map[string]interface{}{
				"name": modifiedname,
			},
			"roleRef": map[string]interface{}{
				"apiGroup": "rbac.authorization.k8s.io",
				"kind":     "ClusterRole",
				"name":     orgname,
			},
			"subjects": []interface{}{
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": defaultNs,
				},
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": ns1,
				},
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": ns2,
				},
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": "random",
				},
			}}).ResMap()

	expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
		ReplaceResource(
			map[string]interface{}{
				"apiVersion": "rbac.authorization.k8s.io/v1",
				"kind":       "ClusterRole",
				"metadata": map[string]interface{}{
					"name": modifiedname,
				},
				// Behavior of the transformer is still imperfect
				// It should use the (resources,apigroup,resourceNames) as
				// combination to select the candidates.
				"rules": []interface{}{
					map[string]interface{}{
						"resources": []interface{}{
							"persistentvolumes",
						},
						"resourceNames": []interface{}{
							modifiedname,
						},
					},
				}}).
		ReplaceResource(
			map[string]interface{}{
				"apiVersion": "rbac.authorization.k8s.io/v1",
				"kind":       "ClusterRoleBinding",
				"metadata": map[string]interface{}{
					"name": modifiedname,
				},
				"roleRef": map[string]interface{}{
					"apiGroup": "rbac.authorization.k8s.io",
					"kind":     "ClusterRole",
					"name":     modifiedname,
				},
				// The following tests required a change in
				// getNameFunc implementation in order to leverage
				// the namespace field.
				"subjects": []interface{}{
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      modifiedname,
						"namespace": defaultNs,
					},
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      prefixedname,
						"namespace": ns1,
					},
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      suffixedname,
						"namespace": ns2,
					},
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      orgname,
						"namespace": "random",
					},
				},
			}).ResMap()

	clusterRoleId := resid.NewResId(
		resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"), modifiedname)
	clusterRoleBindingId := resid.NewResId(
		resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"), modifiedname)
	clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
	clusterRole.AppendRefBy(clusterRoleBindingId)

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	err := nrt.Transform(m)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expected.RemoveBuildAnnotations()
	m.RemoveBuildAnnotations()

	if err = expected.ErrorIfNotEqualLists(m); err != nil {
		t.Fatalf(notEqualErrFmt, err)
	}
}

// TestNameReferenceNamespaceTransformation creates serviceAccount and clusterRoleBinding
// object with the same original names (uniquename) in different namespaces
// and with different current Id.
func TestNameReferenceNamespaceTransformation(t *testing.T) {
	m := resmaptest_test.NewRmBuilderDefault(t).
		AddWithNsAndName(ns4, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Secret",
			"metadata": map[string]interface{}{
				"name":      orgname,
				"namespace": ns4,
			}}).
		// Add ServiceAccount with the same org name in "ns1" namespaces
		AddWithNsAndName(ns1, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ServiceAccount",
			"metadata": map[string]interface{}{
				"name":      prefixedname,
				"namespace": ns1,
			}}).
		// Simulate NamespaceTransformer effect (ns3 transformed in ns2)
		AddWithNsAndName(ns3, orgname, map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ServiceAccount",
			"metadata": map[string]interface{}{
				"name":      suffixedname,
				"namespace": ns2,
			}}).
		AddWithName(orgname, map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRole",
			"metadata": map[string]interface{}{
				"name": modifiedname,
			}}).
		AddWithName(orgname, map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRoleBinding",
			"metadata": map[string]interface{}{
				"name": modifiedname,
			},
			"roleRef": map[string]interface{}{
				"apiGroup": "rbac.authorization.k8s.io",
				"kind":     "ClusterRole",
				"name":     orgname,
			},
			"subjects": []interface{}{
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": ns1,
				},
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": ns3,
				},
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": "random",
				},
				map[string]interface{}{
					"kind":      "ServiceAccount",
					"name":      orgname,
					"namespace": ns4,
				},
			}}).ResMap()

	expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
		ReplaceResource(
			map[string]interface{}{
				"apiVersion": "rbac.authorization.k8s.io/v1",
				"kind":       "ClusterRoleBinding",
				"metadata": map[string]interface{}{
					"name": modifiedname,
				},
				"roleRef": map[string]interface{}{
					"apiGroup": "rbac.authorization.k8s.io",
					"kind":     "ClusterRole",
					"name":     modifiedname,
				},
				// The following tests required a change in
				// getNameFunc implementation in order to leverage
				// the namespace field.
				"subjects": []interface{}{
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      prefixedname,
						"namespace": ns1,
					},
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      suffixedname,
						"namespace": ns2,
					},
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      orgname,
						"namespace": "random",
					},
					map[string]interface{}{
						"kind":      "ServiceAccount",
						"name":      orgname,
						"namespace": ns4,
					},
				},
			}).ResMap()

	clusterRoleId := resid.NewResId(
		resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"),
		modifiedname)
	clusterRoleBindingId := resid.NewResId(
		resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"),
		modifiedname)
	clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
	clusterRole.AppendRefBy(clusterRoleBindingId)

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	err := nrt.Transform(m)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	m.RemoveBuildAnnotations()
	if err = expected.ErrorIfNotEqualLists(m); err != nil {
		t.Fatalf(notEqualErrFmt, err)
	}
}

// TestNameReferenceNamespace creates configmap, secret, deployment
// It validates the change done is IsSameFuzzyNamespace which
// uses the IsNsEquals method instead of the simple == operator.
func TestNameReferenceCandidateSelection(t *testing.T) {
	m := resmaptest_test.NewRmBuilderDefault(t).
		AddWithName("cm1", map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name": "p1-cm1-hash",
			}}).
		AddWithNsAndName("default", "secret1", map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Secret",
			"metadata": map[string]interface{}{
				"name":      "p1-secret1-hash",
				"namespace": "default",
			}}).
		AddWithName("deploy1", deploymentMap("", "p1-deploy1", "cm1", "secret1")).
		ResMap()

	expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
		ReplaceResource(deploymentMap("", "p1-deploy1", "p1-cm1-hash", "p1-secret1-hash")).
		ResMap()

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	err := nrt.Transform(m)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	m.RemoveBuildAnnotations()
	if err = expected.ErrorIfNotEqualLists(m); err != nil {
		t.Fatalf(notEqualErrFmt, err)
	}
}

func TestNameReferenceCandidateDisambiguationByNamespace(t *testing.T) {
	// The ClusterRole refers to both configmaps, since it is not namespace-specific.
	// Since both names are updated consistently, the transformer should be able to
	// silently update the ClusterRole as well.
	// This test guards against a regression where allNamesAndNamespacesAreTheSame would be
	// used to detect referral candidate identity instead of allNamesAreTheSame.
	m := resmaptest_test.NewRmBuilderDefault(t).AddWithName(orgname,
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name":      suffixedname,
				"namespace": ns1,
			},
		},
	).AddWithName(orgname,
		map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "ConfigMap",
			"metadata": map[string]interface{}{
				"name":      suffixedname,
				"namespace": ns2,
			},
		},
	).Add(
		map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRole",
			"metadata": map[string]interface{}{
				"name": orgname,
			},
			"rules": []interface{}{
				map[string]interface{}{
					"resources": []interface{}{
						"configmaps",
					},
					"resourceNames": []interface{}{
						orgname,
					},
				},
			},
		},
	).ResMap()

	expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
		ReplaceResource(map[string]interface{}{
			"apiVersion": "rbac.authorization.k8s.io/v1",
			"kind":       "ClusterRole",
			"metadata": map[string]interface{}{
				"name": orgname,
			},
			"rules": []interface{}{
				map[string]interface{}{
					"resources": []interface{}{
						"configmaps",
					},
					"resourceNames": []interface{}{
						suffixedname,
					},
				},
			},
		},
		).ResMap()

	nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
	require.NoError(t, nrt.Transform(m))

	m.RemoveBuildAnnotations()
	if err := expected.ErrorIfNotEqualLists(m); err != nil {
		t.Fatalf(notEqualErrFmt, err)
	}
}
