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

package main_test

import (
	"fmt"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)

func errorContains(err error, possibilities ...string) bool {
	for _, x := range possibilities {
		if strings.Contains(err.Error(), x) {
			return true
		}
	}
	return false
}

const (
	target = `
apiVersion: apps/v1
metadata:
  name: myDeploy
kind: Deployment
spec:
  replica: 2
  template:
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - name: nginx
        image: nginx
`
	targetWithNamespace = `
apiVersion: apps/v1
metadata:
  name: myDeploy
  namespace: namespace1
kind: Deployment
spec:
  replica: 2
  template:
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - name: nginx
        image: nginx
`
	targetNoschema = `
apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    A: X
    B: Y
`
)

func TestPatchStrategicMergeTransformerMissingFile(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()
	_, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`, target)
	if assert.Error(t, err) && !errorContains(err,
		"'/patch.yaml' doesn't exist",
		"cannot unmarshal string") {
		t.Fatalf("unexpected err: %v", err)
	}
}

func TestBadPatchStrategicMergeTransformer(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()
	_, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
patches: 'thisIsNotAPatch'
`, target)
	if assert.Error(t, err) && !errorContains(err,
		"cannot unmarshal string into Go value of type map[string]interface {}",
		"fails configuration: missing Resource metadata") {
		t.Fatalf("unexpected err: %v", err)
	}
}

func TestBothEmptyPatchStrategicMergeTransformer(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()
	_, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
`, target)
	if assert.Error(t, err) && !errorContains(
		err, "empty file path and empty patch content") {
		t.Fatalf("unexpected err: %v", err)
	}
}

func TestPatchStrategicMergeTransformerFromFiles(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.WriteF("/patch.yaml", `
apiVersion: apps/v1
metadata:
  name: myDeploy
kind: Deployment
spec:
  template:
    metadata:
      labels:
        new-label: new-value
  replica: 3
`)

	th.RunTransformerAndCheckResult(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`,
		target, `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myDeploy
spec:
  replica: 3
  template:
    metadata:
      labels:
        new-label: new-value
        old-label: old-value
    spec:
      containers:
      - image: nginx
        name: nginx
`)
}

func TestPatchStrategicMergeTransformerWithInlineJSON(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.RunTransformerAndCheckResult(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
patches: '{"apiVersion": "apps/v1", "metadata": {"name": "myDeploy"}, "kind": "Deployment", "spec": {"replica": 3}}'
`,
		target, `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myDeploy
spec:
  replica: 3
  template:
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - image: nginx
        name: nginx
`)
}

func TestPatchStrategicMergeTransformerWithInlineYAML(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.RunTransformerAndCheckResult(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
patches: |-
  apiVersion: apps/v1
  metadata:
    name: myDeploy
  kind: Deployment
  spec:
    replica: 3
  ---
  apiVersion: apps/v1
  metadata:
    name: myDeploy
  kind: Deployment
  spec:
    template:
      spec:
        containers:
        - name: nginx
          image: nginx:latest
`,
		target, `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myDeploy
spec:
  replica: 3
  template:
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - image: nginx:latest
        name: nginx
`)
}

func TestPatchStrategicMergeTransformerMultiplePatches(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.WriteF("patch1.yaml", `
apiVersion: apps/v1
metadata:
  name: myDeploy
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        env:
        - name: SOMEENV
          value: BAR
`)

	th.WriteF("patch2.yaml", `
apiVersion: apps/v1
metadata:
  name: myDeploy
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: nginx
        env:
        - name: ANOTHERENV
          value: HELLO
      - name: busybox
        image: busybox
`)

	th.RunTransformerAndCheckResult(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch1.yaml
- patch2.yaml
`,
		target, `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myDeploy
spec:
  replica: 2
  template:
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - env:
        - name: ANOTHERENV
          value: HELLO
        - name: SOMEENV
          value: BAR
        image: nginx:latest
        name: nginx
      - image: busybox
        name: busybox
`)
}

func TestStrategicMergeTransformerWrongNamespace(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()
	th.WriteF("patch.yaml", `
apiVersion: apps/v1
metadata:
  name: myDeploy
  namespace: namespace2
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        env:
        - name: SOMEENV
          value: BAR
`)
	_, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`, targetWithNamespace)
	if assert.Error(t, err) && !errorContains(
		err, "failed to find unique target for patch") {
		t.Fatalf("expected error to contain %q but get %v", "failed to find target for patch", err)
	}
}

// issue #2734 -- https://github.com/kubernetes-sigs/kustomize/issues/2734
// kyaml cleans up things some folks prefer it didnt
// 1. []    -- see initContainers and imagePullSecrets
// 2. {}    -- see emptyDir
// 3. null  -- see creationTimestamp

const anUncleanDeploymentResource = `apiVersion: apps/v1
kind: Deployment
metadata:
  name: sas-crunchy-data-postgres-operator
spec:
  replicas: 1
  template:
    metadata:
      creationTimestamp: null
    spec:
      serviceAccountName: postgres-operator
      containers:
      - name: apiserver
        image: sas-crunchy-data-operator-api-server
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8443
        envFrom: []
        volumeMounts:
        - name: security-ssh
          mountPath: /security-ssh
        - mountPath: /tmp
          name: tmp
      imagePullSecrets: []
      initContainers: []
      volumes:
      - emptyDir: {}
        name: security-ssh
      - emptyDir: {}
        name: tmp
`

// This is the preffered result (and what we get with kustomize 3.7.0)
const expectedCleanedDeployment = `apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    workload.sas.com/class: stateless
  name: sas-crunchy-data-postgres-operator
spec:
  replicas: 1
  template:
    metadata:
      creationTimestamp: null
      labels:
        workload.sas.com/class: stateless
    spec:
      containers:
      - envFrom: []
        image: sas-crunchy-data-operator-api-server
        imagePullPolicy: IfNotPresent
        name: apiserver
        ports:
        - containerPort: 8443
        volumeMounts:
        - mountPath: /security-ssh
          name: security-ssh
        - mountPath: /tmp
          name: tmp
      imagePullSecrets: []
      initContainers: []
      serviceAccountName: postgres-operator
      tolerations:
      - effect: NoSchedule
        key: workload.sas.com/class
        operator: Equal
        value: stateful
      volumes:
      - emptyDir: {}
        name: security-ssh
      - emptyDir: {}
        name: tmp
`

func TestPatchStrategicMergeTransformerCleanupItems(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.WriteF("/patch.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sas-crunchy-data-postgres-operator
  labels:
    workload.sas.com/class: stateless
spec:
  template:
    metadata:
      labels:
        workload.sas.com/class: stateless
    spec:
      tolerations:
        - effect: NoSchedule
          key: workload.sas.com/class
          operator: Equal
          value: stateful
`)

	th.RunTransformerAndCheckResult(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`,
		anUncleanDeploymentResource,
		expectedCleanedDeployment) // prefer expectedCleanedDeployment
}

func TestStrategicMergeTransformerNoSchema(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.WriteF("patch.yaml", `
apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    B:
    C: Z
`)
	th.RunTransformerAndCheckResult(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`,
		targetNoschema,
		`
apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    A: X
    C: Z
`)
}

func TestStrategicMergeTransformerNoSchemaMultiPatches(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()
	// This patch wants to delete "B".
	th.WriteF("patch1.yaml", `
apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    B:
    C: Z
`)
	th.WriteF("patch2.yaml", `
apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    C: Z
    D: W
  baz:
    hello: world
`)
	resMap, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch1.yaml
- patch2.yaml
`, `apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    A: X
    B: Y
`)
	require.NoError(t, err)
	th.AssertActualEqualsExpectedNoIdAnnotations(
		resMap, `
apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    A: X
    C: Z
    D: W
  baz:
    hello: world
`)
	resMap, err = th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch2.yaml
`, `apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    A: X
    B: Y
`)
	require.NoError(t, err)
	th.AssertActualEqualsExpectedNoIdAnnotations(
		// This time only patch2 is applied.
		resMap, `
apiVersion: example.com/v1
kind: Foo
metadata:
  name: my-foo
spec:
  bar:
    A: X
    B: "Y"
    C: Z
    D: W
  baz:
    hello: world
`)
}

// simple utility function to add an namespace in a resource
// used as base, patch or expected result. Simply looks
// for specs: in order to add namespace: xxxx before this line
func addNamespace(namespace string, base string) string {
	res := strings.Replace(base,
		"\nspec:\n",
		"\n  namespace: "+namespace+"\nspec:\n",
		1)
	return res
}

// compareExpectedError compares the expectedError and the actualError return by GetFieldValue
func compareExpectedError(t *testing.T, name string, err error, errorMsg string) {
	if assert.Error(t, err, name) && !errorContains(err, errorMsg) {
		t.Fatalf("%q; - expected error: \"%s\", got error: \"%v\"",
			name, errorMsg, err.Error())
	}
}

const Deployment string = "Deployment"
const MyCRD string = "MyCRD"

// baseResource produces a base object which used to test
// patch transformation
// Also the structure is matching the Deployment syntax
// the kind can be replaced to allow testing using CRD
// without access to the schema
func baseResource(kind string) string {

	res := `
apiVersion: apps/v1
kind: %s
metadata:
  name: deploy1
spec:
  template:
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - name: nginx
        image: nginx`
	return fmt.Sprintf(res, kind)
}

// addContainerAndEnvPatch produces a patch object which adds
// an entry in the env slice of the first/nginx container
// as well as adding a label in the metadata
// Note that for SMP/WithSchema merge, the name:nginx entry
// is mandatory
func addLabelAndEnvPatch(kind string) string {
	res := `
apiVersion: apps/v1
kind: %s
metadata:
  name: deploy1
spec:
  template:
    metadata:
      labels:
        some-label: some-value
    spec:
      containers:
       - name: nginx
         env:
         - name: SOMEENV
           value: SOMEVALUE`

	return fmt.Sprintf(res, kind)
}

// addContainerAndEnvPatch produces a patch object which adds
// an entry in the env slice of the first/nginx container
// as well as adding a second container in the container list
// Note that for SMP/WithSchema merge, the name:nginx entry
// is mandatory
func addContainerAndEnvPatch(kind string) string {

	res := `
apiVersion: apps/v1
kind: %s
metadata:
  name: deploy1
spec:
  template:
    spec:
      containers:
      - name: nginx
        env:
        - name: ANOTHERENV
          value: ANOTHERVALUE
      - name: anothercontainer
        image: anotherimage`

	return fmt.Sprintf(res, kind)
}

// addContainerAndEnvPatch produces a patch object which replaces
// the value of the image field in the first/nginx container
// Note that for SMP/WithSchema merge, the name:nginx entry
// is mandatory
func changeImagePatch(kind string, newImage string) string {

	res := `
apiVersion: apps/v1
kind: %s
metadata:
  name: deploy1
spec:
  template:
    spec:
      containers:
      - name: nginx
        image: %s`

	return fmt.Sprintf(res, kind, newImage)
}

// utility method building the expected output of a SMP
func expectedResultSMP() string {

	return `apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy1
spec:
  template:
    metadata:
      labels:
        old-label: old-value
        some-label: some-value
    spec:
      containers:
      - env:
        - name: SOMEENV
          value: SOMEVALUE
        image: nginx
        name: nginx
`
}

// utility method building the expected output of a JMP.
// imagename parameter allows to build a result consistent
// with the JMP behavior which basically overrides the
// entire "containers" list.
func expectedResultJMP(imagename string) string {

	res := `apiVersion: apps/v1
kind: MyCRD
metadata:
  name: deploy1
spec:
  template:
    metadata:
      labels:
        old-label: old-value
        some-label: some-value
    spec:
      containers:
      - env:
        - name: SOMEENV
          value: SOMEVALUE
        name: nginx
`

	if imagename == "" {
		return res
	}

	res = `apiVersion: apps/v1
kind: MyCRD
metadata:
  name: deploy1
spec:
  template:
    metadata:
      labels:
        old-label: old-value
        some-label: some-value
    spec:
      containers:
      - image: %s
        name: nginx
`

	return fmt.Sprintf(res, imagename)
}

// utility method to build the expected result of a multipatch
// the order of the patches still have influence especially
// in the insertion location within arrays.
func expectedResultMultiPatch(kind string, reversed bool) string {

	res := `apiVersion: apps/v1
kind: %s
metadata:
  name: deploy1
spec:
  template:
    metadata:
      labels:
        old-label: old-value
        some-label: some-value
    spec:
      containers:
      - env:
        - name: ANOTHERENV
          value: ANOTHERVALUE
        - name: SOMEENV
          value: SOMEVALUE
        image: nginx:latest
        name: nginx
      - image: anotherimage
        name: anothercontainer
`

	reversedres := `apiVersion: apps/v1
kind: %s
metadata:
  name: deploy1
spec:
  template:
    metadata:
      labels:
        old-label: old-value
        some-label: some-value
    spec:
      containers:
      - env:
        - name: SOMEENV
          value: SOMEVALUE
        - name: ANOTHERENV
          value: ANOTHERVALUE
        image: nginx:latest
        name: nginx
      - image: anotherimage
        name: anothercontainer
`

	if reversed {
		return fmt.Sprintf(reversedres, kind)
	}

	return fmt.Sprintf(res, kind)
}

func toConfig(patches ...string) string {
	config := `
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
`
	for idx := range patches {
		config = fmt.Sprintf("%s\n- ./patch%d.yaml", config, idx)
	}

	return config
}

// TestSinglePatch validates the single patch use cases
// regarless of the schema availibility, which in turns
// relies on StrategicMergePatch or simple JSON Patch.
func TestSinglePatch(t *testing.T) {
	tests := []struct {
		name          string
		base          string
		patch         string
		expected      string
		errorExpected bool
		errorMsg      string
	}{
		{
			name:          "withschema",
			base:          baseResource(Deployment),
			patch:         addLabelAndEnvPatch(Deployment),
			errorExpected: false,
			expected:      expectedResultSMP(),
		},
		{
			name:          "noschema",
			base:          baseResource(MyCRD),
			patch:         addLabelAndEnvPatch(MyCRD),
			errorExpected: false,
			expected:      expectedResultJMP(""),
		},
	}

	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	for _, test := range tests {
		th.ResetLoaderRoot(fmt.Sprintf("/%s", test.name))
		th.WriteF(fmt.Sprintf("/%s/patch%d.yaml", test.name, 0), test.patch)
		if test.errorExpected {
			_, err := th.RunTransformer(toConfig(test.patch), test.base)
			compareExpectedError(t, test.name, err, test.errorMsg)
		} else {
			th.RunTransformerAndCheckResult(
				toConfig(test.patch), test.base, test.expected)
		}
	}
}

type testRecord struct {
	base          string
	patch         []string
	expected      string
	errorExpected bool
	errorMsg      string
}

// TestMultiplePatches checks that the patches are applied
// properly, that the same result is obtained,
// regardless of the order of the patches and regardless
// of the schema availability (SMP vs JSON)
func TestMultiplePatches(t *testing.T) {
	tests := map[string]testRecord{
		"withschema-label-image-container": {
			base: baseResource(Deployment),
			patch: []string{
				addLabelAndEnvPatch(Deployment),
				changeImagePatch(Deployment, "nginx:latest"),
				addContainerAndEnvPatch(Deployment),
			},
			expected: expectedResultMultiPatch(Deployment, false),
		},
		"withschema-image-container-label": {
			base: baseResource(Deployment),
			patch: []string{
				changeImagePatch(Deployment, "nginx:latest"),
				addContainerAndEnvPatch(Deployment),
				addLabelAndEnvPatch(Deployment),
			},
			expected: expectedResultMultiPatch(Deployment, true),
		},
		"withschema-container-label-image": {
			base: baseResource(Deployment),
			patch: []string{
				addContainerAndEnvPatch(Deployment),
				addLabelAndEnvPatch(Deployment),
				changeImagePatch(Deployment, "nginx:latest"),
			},
			expected: expectedResultMultiPatch(Deployment, true),
		},
		"noschema-1.7.9-label-latest": {
			base: baseResource(MyCRD),
			patch: []string{
				changeImagePatch(MyCRD, "nginx:1.7.9"),
				addLabelAndEnvPatch(MyCRD),
				changeImagePatch(MyCRD, "nginx:latest"),
			},
			errorExpected: false,
			// Theses patches aren't commutable (you get a different result
			// if they are ordered differently).  This is allowed without error.
			expected: expectedResultJMP("nginx:latest"),
		},
		"noschema-latest-label-1.7.9": {
			base: baseResource(MyCRD),
			patch: []string{
				changeImagePatch(MyCRD, "nginx:latest"),
				addLabelAndEnvPatch(MyCRD),
				changeImagePatch(MyCRD, "nginx:1.7.9"),
			},
			errorExpected: false,
			// Theses patches aren't commutable (you get a different result
			// if they are ordered differently).  This is allowed without error.
			expected: expectedResultJMP("nginx:1.7.9"),
		},
	}

	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			th.ResetLoaderRoot(fmt.Sprintf("/%s", name))
			for idx, patch := range test.patch {
				th.WriteF(fmt.Sprintf("/%s/patch%d.yaml", name, idx), patch)
			}
			if test.errorExpected {
				_, err := th.RunTransformer(toConfig(test.patch...), test.base)
				compareExpectedError(t, name, err, test.errorMsg)
			} else {
				th.RunTransformerAndCheckResult(
					toConfig(test.patch...), test.base, test.expected)
			}
		})
	}
}

// TestMultipleNamespaces before the same patch
// on two objects have the same name but in a different namespaces
func TestMultipleNamespaces(t *testing.T) {
	tests := []struct {
		name          string
		base          []string
		patch         []string
		expected      []string
		errorExpected bool
		errorMsg      string
	}{
		{
			name: "withschema-ns1-ns2",
			base: []string{
				addNamespace("ns1", baseResource(Deployment)),
				addNamespace("ns2", baseResource(Deployment)),
			},
			patch: []string{
				addNamespace("ns1", addLabelAndEnvPatch(Deployment)),
				addNamespace("ns2", addLabelAndEnvPatch(Deployment)),
			},
			errorExpected: false,
			expected: []string{
				addNamespace("ns1", expectedResultSMP()),
				addNamespace("ns2", expectedResultSMP()),
			},
		},
		{
			name: "noschema-ns1-ns2",
			base: []string{
				addNamespace("ns1", baseResource(MyCRD)),
				addNamespace("ns2", baseResource(MyCRD)),
			},
			patch: []string{
				addNamespace("ns1", addLabelAndEnvPatch(MyCRD)),
				addNamespace("ns2", addLabelAndEnvPatch(MyCRD)),
			},
			errorExpected: false,
			expected: []string{
				addNamespace("ns1", expectedResultJMP("")),
				addNamespace("ns2", expectedResultJMP("")),
			},
		},
		{
			name:          "withschema-ns1-ns2",
			base:          []string{addNamespace("ns1", baseResource(Deployment))},
			patch:         []string{addNamespace("ns2", changeImagePatch(Deployment, "nginx:1.7.9"))},
			errorExpected: true,
			errorMsg:      "failed to find unique target for patch",
		},
		{
			name:          "withschema-nil-ns2",
			base:          []string{baseResource(Deployment)},
			patch:         []string{addNamespace("ns2", changeImagePatch(Deployment, "nginx:1.7.9"))},
			errorExpected: true,
			errorMsg:      "failed to find unique target for patch",
		},
		{
			name:          "withschema-ns1-nil",
			base:          []string{addNamespace("ns1", baseResource(Deployment))},
			patch:         []string{changeImagePatch(Deployment, "nginx:1.7.9")},
			errorExpected: true,
			errorMsg:      "failed to find unique target for patch",
		},
		{
			name:          "noschema-ns1-ns2",
			base:          []string{addNamespace("ns1", baseResource(MyCRD))},
			patch:         []string{addNamespace("ns2", changeImagePatch(MyCRD, "nginx:1.7.9"))},
			errorExpected: true,
			errorMsg:      "failed to find unique target for patch",
		},
		{
			name:          "noschema-nil-ns2",
			base:          []string{baseResource(MyCRD)},
			patch:         []string{addNamespace("ns2", changeImagePatch(MyCRD, "nginx:1.7.9"))},
			errorExpected: true,
			errorMsg:      "failed to find unique target for patch",
		},
		{
			name:          "noschema-ns1-nil",
			base:          []string{addNamespace("ns1", baseResource(MyCRD))},
			patch:         []string{changeImagePatch(MyCRD, "nginx:1.7.9")},
			errorExpected: true,
			errorMsg:      "failed to find unique target for patch",
		},
	}

	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	for _, test := range tests {
		th.ResetLoaderRoot(fmt.Sprintf("/%s", test.name))
		for idx, patch := range test.patch {
			th.WriteF(fmt.Sprintf("/%s/patch%d.yaml", test.name, idx), patch)
		}

		if test.errorExpected {
			_, err := th.RunTransformer(
				toConfig(test.patch...), strings.Join(test.base, "\n---\n"))
			compareExpectedError(t, test.name, err, test.errorMsg)
		} else {
			th.RunTransformerAndCheckResult(
				toConfig(test.patch...),
				strings.Join(test.base, "\n---\n"),
				strings.Join(test.expected, "---\n"))
		}
	}
}

func TestPatchStrategicMergeTransformerPatchDelete1(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.WriteF("patch.yaml", `
apiVersion: apps/v1
metadata:
  name: myDeploy
kind: Deployment
spec:
  replica: 2
  template:
    $patch: delete
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - name: nginx
        image: nginx
`)

	rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`, target)

	th.AssertActualEqualsExpectedNoIdAnnotations(rm, `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myDeploy
spec:
  replica: 2
`)
}

func TestPatchStrategicMergeTransformerPatchDelete2(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.WriteF("patch.yaml", `
apiVersion: apps/v1
metadata:
  name: myDeploy
kind: Deployment
spec:
  $patch: delete
  replica: 2
  template:
    metadata:
      labels:
        old-label: old-value
    spec:
      containers:
      - name: nginx
        image: nginx
`)

	rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`, target)

	th.AssertActualEqualsExpectedNoIdAnnotations(rm, `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myDeploy
`)
}

func TestPatchStrategicMergeTransformerPatchDelete3(t *testing.T) {
	th := kusttest_test.MakeEnhancedHarness(t).
		PrepBuiltin("PatchStrategicMergeTransformer")
	defer th.Reset()

	th.WriteF("patch.yaml", `
apiVersion: apps/v1
metadata:
  name: myDeploy
kind: Deployment
$patch: delete
`)

	rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
  name: notImportantHere
paths:
- patch.yaml
`, target)

	th.AssertActualEqualsExpected(rm, ``)
}
