package diff
import (
"bytes"
"encoding/json"
"os"
"testing"
"github.com/aryann/difflib"
"github.com/mgutz/ansi"
"github.com/stretchr/testify/require"
"github.com/databus23/helm-diff/v3/manifest"
)
var text1 = "" +
"line1\n" +
"line2\n" +
"line3\n" +
"line4\n" +
"line5\n" +
"line6\n" +
"line7\n" +
"line8\n" +
"line9\n" +
"line10"
var text2 = "" +
"line1 - different!\n" +
"line2 - different!\n" +
"line3\n" +
"line4\n" +
"line5\n" +
"line6\n" +
"line7\n" +
"line8 - different!\n" +
"line9\n" +
"line10"
var text3 = "" +
"line1\r\n" +
"line2\r\n" +
"line3\r\n" +
"line4\r\n" +
"line5\r\n" +
"line6\r\n" +
"line7\r\n" +
"line8\r\n" +
"line9\r\n" +
"line10"
func TestPrintDiffWithContext(t *testing.T) {
t.Run("context-disabled", func(t *testing.T) {
assertDiff(t, text1, text2, -1, false, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
" line4\n"+
" line5\n"+
" line6\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
" line10\n")
})
t.Run("context-0", func(t *testing.T) {
assertDiff(t, text1, text2, 0, false, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
"...\n"+
"- line8\n"+
"+ line8 - different!\n"+
"...\n")
})
t.Run("context-0-no-strip-cr", func(t *testing.T) {
assertDiff(t, text1, text3, 0, false, ""+
"- line1\n"+
"- line2\n"+
"- line3\n"+
"- line4\n"+
"- line5\n"+
"- line6\n"+
"- line7\n"+
"- line8\n"+
"- line9\n"+
"+ line1\r\n"+
"+ line2\r\n"+
"+ line3\r\n"+
"+ line4\r\n"+
"+ line5\r\n"+
"+ line6\r\n"+
"+ line7\r\n"+
"+ line8\r\n"+
"+ line9\r\n"+
"...\n")
})
t.Run("context-0-strip-cr", func(t *testing.T) {
assertDiff(t, text1, text3, 0, true, ""+
"...\n")
})
t.Run("context-1", func(t *testing.T) {
assertDiff(t, text1, text2, 1, false, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
"...\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
"...\n")
})
t.Run("context-2", func(t *testing.T) {
assertDiff(t, text1, text2, 2, false, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
" line4\n"+
"...\n"+
" line6\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
" line10\n")
})
t.Run("context-3", func(t *testing.T) {
assertDiff(t, text1, text2, 3, false, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
" line4\n"+
" line5\n"+
" line6\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
" line10\n")
})
}
func assertDiff(t *testing.T, before, after string, context int, stripTrailingCR bool, expected string) {
ansi.DisableColors(true)
var output bytes.Buffer
diffs := diffStrings(before, after, stripTrailingCR)
printDiffRecords([]string{}, "some-resource", context, diffs, &output)
actual := output.String()
if actual != expected {
t.Errorf("Unexpected diff output: \nExpected:\n#%v# \nActual:\n#%v#", expected, actual)
}
}
func TestManifests(t *testing.T) {
ansi.DisableColors(true)
specBeta := map[string]*manifest.MappingResult{
"default, nginx, Deployment (apps)": {
Name: "default, nginx, Deployment (apps)",
Kind: "Deployment",
Content: `
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx
`,
},
}
specRelease := map[string]*manifest.MappingResult{
"default, nginx, Deployment (apps)": {
Name: "default, nginx, Deployment (apps)",
Kind: "Deployment",
Content: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
`,
},
}
specReleaseSpec := map[string]*manifest.MappingResult{
"default, nginx, Deployment (apps)": {
Name: "default, nginx, Deployment (apps)",
Kind: "Deployment",
Content: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
`,
},
}
specReleaseRenamed := map[string]*manifest.MappingResult{
"default, nginx-renamed, Deployment (apps)": {
Name: "default, nginx-renamed, Deployment (apps)",
Kind: "Deployment",
Content: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-renamed
spec:
replicas: 3
`,
},
}
specReleaseRenamedAndUpdated := map[string]*manifest.MappingResult{
"default, nginx-renamed, Deployment (apps)": {
Name: "default, nginx-renamed, Deployment (apps)",
Kind: "Deployment",
Content: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-renamed
spec:
replicas: 1
`,
},
}
specReleaseRenamedAndAdded := map[string]*manifest.MappingResult{
"default, nginx-renamed, Deployment (apps)": {
Name: "default, nginx-renamed, Deployment (apps)",
Kind: "Deployment",
Content: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-renamed
spec:
replicas: 3
selector:
matchLabels:
app: nginx-renamed
`,
},
}
specReleaseKeep := map[string]*manifest.MappingResult{
"default, nginx, Deployment (apps)": {
Name: "default, nginx, Deployment (apps)",
Kind: "Deployment",
Content: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
annotations:
helm.sh/resource-policy: keep
`,
ResourcePolicy: "keep",
},
}
t.Run("OnChange", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has changed:
- apiVersion: apps/v1beta1
+ apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
`, buf1.String())
})
t.Run("OnChangeWithSuppress", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{"apiVersion"}}
if changesSeen := Manifests(specBeta, specReleaseSpec, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has changed:
kind: Deployment
metadata:
name: nginx
+ spec:
+ replicas: 3
`, buf1.String())
})
t.Run("OnChangeWithSuppressAll", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{"apiVersion"}}
if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has changed, but diff is empty after suppression.
`, buf1.String())
})
t.Run("OnChangeRename", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}}
if changesSeen := Manifests(specReleaseSpec, specReleaseRenamed, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has changed:
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx
+ name: nginx-renamed
spec:
replicas: 3
`, buf1.String())
})
t.Run("OnChangeRenameAndUpdate", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}}
if changesSeen := Manifests(specReleaseSpec, specReleaseRenamedAndUpdated, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has changed:
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx
+ name: nginx-renamed
spec:
- replicas: 3
+ replicas: 1
`, buf1.String())
})
t.Run("OnChangeRenameAndAdded", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}}
if changesSeen := Manifests(specReleaseSpec, specReleaseRenamedAndAdded, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has changed:
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx
+ name: nginx-renamed
spec:
replicas: 3
+ selector:
+ matchLabels:
+ app: nginx-renamed
`, buf1.String())
})
t.Run("OnChangeRenameAndAddedWithPartialSuppress", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{"app: "}}
if changesSeen := Manifests(specReleaseSpec, specReleaseRenamedAndAdded, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has changed:
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx
+ name: nginx-renamed
spec:
replicas: 3
+ selector:
+ matchLabels:
`, buf1.String())
})
t.Run("OnChangeRenameAndRemoved", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}}
if changesSeen := Manifests(specReleaseRenamedAndAdded, specReleaseSpec, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx-renamed, Deployment (apps) has changed:
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx-renamed
+ name: nginx
spec:
replicas: 3
- selector:
- matchLabels:
- app: nginx-renamed
`, buf1.String())
})
t.Run("OnChangeRenameAndRemovedWithPartialSuppress", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{"app: "}}
if changesSeen := Manifests(specReleaseRenamedAndAdded, specReleaseSpec, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx-renamed, Deployment (apps) has changed:
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx-renamed
+ name: nginx
spec:
replicas: 3
- selector:
- matchLabels:
`, buf1.String())
})
t.Run("OnNoChange", func(t *testing.T) {
var buf2 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specRelease, specRelease, &diffOptions, &buf2); changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`")
}
require.Empty(t, buf2.String())
})
t.Run("OnChangeRemoved", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}}
if changesSeen := Manifests(specRelease, nil, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) has been removed:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: nginx
- `+`
`, buf1.String())
})
t.Run("OnChangeRemovedWithResourcePolicyKeep", func(t *testing.T) {
var buf2 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specReleaseKeep, nil, &diffOptions, &buf2); changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`")
}
require.Empty(t, buf2.String())
})
t.Run("OnChangeSimple", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"simple", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, nginx, Deployment (apps) to be changed.
Plan: 0 to add, 1 to change, 0 to destroy, 0 to change ownership.
`, buf1.String())
})
t.Run("OnNoChangeSimple", func(t *testing.T) {
var buf2 bytes.Buffer
diffOptions := Options{"simple", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specRelease, specRelease, &diffOptions, &buf2); changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`")
}
require.Equal(t, "Plan: 0 to add, 0 to change, 0 to destroy, 0 to change ownership.\n", buf2.String())
})
t.Run("OnChangeTemplate", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"template", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.JSONEq(t, `[{
"api": "apps",
"kind": "Deployment",
"namespace": "default",
"name": "nginx",
"change": "MODIFY"
}]
`, buf1.String())
})
t.Run("OnChangeJSON", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"json", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.JSONEq(t, `[{
"api": "apps",
"kind": "Deployment",
"namespace": "default",
"name": "nginx",
"change": "MODIFY"
}]
`, buf1.String())
})
t.Run("OnNoChangeTemplate", func(t *testing.T) {
var buf2 bytes.Buffer
diffOptions := Options{"template", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specRelease, specRelease, &diffOptions, &buf2); changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`")
}
require.Equal(t, "[]\n", buf2.String())
})
t.Run("OnChangeCustomTemplate", func(t *testing.T) {
var buf1 bytes.Buffer
os.Setenv("HELM_DIFF_TPL", "testdata/customTemplate.tpl")
diffOptions := Options{"template", 10, false, true, false, []string{}, 0.0, []string{}}
if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`")
}
require.Equal(t, "Resource name: nginx\n", buf1.String())
})
}
func TestStructuredOutputModify(t *testing.T) {
ansi.DisableColors(true)
opts := &Options{OutputFormat: "structured"}
oldManifest := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: prod
spec:
replicas: 2
template:
spec:
containers:
- name: app
image: demo:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- standard
priorities:
- 1
booleanArrayField:
- true
multiTypeField:
- 3
- true
- "string"
`
newManifest := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: prod
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: demo:v2
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- dedicated
priorities:
- 2
booleanArrayField:
- false
multiTypeField:
- false
- "new-string"
- 2
`
oldIndex := manifest.Parse([]byte(oldManifest), "prod", true)
newIndex := manifest.Parse([]byte(newManifest), "prod", true)
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed)
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
entry := entries[0]
require.Equal(t, "MODIFY", entry.ChangeType)
require.Equal(t, "apps/v1", entry.APIVersion)
require.Equal(t, "Deployment", entry.Kind)
require.Equal(t, "prod", entry.Namespace)
require.Equal(t, "web", entry.Name)
require.Len(t, entry.Changes, 8)
replicasChange, ok := findChange(entry.Changes, "spec", "replicas")
require.True(t, ok)
require.InDelta(t, float64(2), replicasChange.OldValue, 0.001)
require.InDelta(t, float64(3), replicasChange.NewValue, 0.001)
imageChange, ok := findChange(entry.Changes, "spec.template.spec.containers[0]", "image")
require.True(t, ok)
require.Equal(t, "demo:v1", imageChange.OldValue)
require.Equal(t, "demo:v2", imageChange.NewValue)
affinityChange, ok := findChange(entry.Changes, "spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values", "0")
require.True(t, ok)
require.Equal(t, "standard", affinityChange.OldValue)
require.Equal(t, "dedicated", affinityChange.NewValue)
priorityChange, ok := findChange(entry.Changes, "spec.template.spec.priorities", "0")
require.True(t, ok)
require.InDelta(t, float64(1), priorityChange.OldValue, 0.001)
require.InDelta(t, float64(2), priorityChange.NewValue, 0.001)
booleanArrayFieldChange, ok := findChange(entry.Changes, "spec.template.spec.booleanArrayField", "0")
require.True(t, ok)
require.Equal(t, true, booleanArrayFieldChange.OldValue)
require.Equal(t, false, booleanArrayFieldChange.NewValue)
multiTypeFieldChange0, ok := findChange(entry.Changes, "spec.template.spec.multiTypeField", "0")
require.True(t, ok)
require.InDelta(t, float64(3), multiTypeFieldChange0.OldValue, 0.001)
require.Equal(t, false, multiTypeFieldChange0.NewValue)
multiTypeFieldChange1, ok := findChange(entry.Changes, "spec.template.spec.multiTypeField", "1")
require.True(t, ok)
require.Equal(t, true, multiTypeFieldChange1.OldValue)
require.Equal(t, "new-string", multiTypeFieldChange1.NewValue)
multiTypeFieldChange2, ok := findChange(entry.Changes, "spec.template.spec.multiTypeField", "2")
require.True(t, ok)
require.Equal(t, "string", multiTypeFieldChange2.OldValue)
require.InDelta(t, float64(2), multiTypeFieldChange2.NewValue, 0.001)
}
func TestStructuredOutputAddAndRemove(t *testing.T) {
ansi.DisableColors(true)
opts := &Options{OutputFormat: "structured"}
newManifest := `
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
namespace: ops
spec:
restartPolicy: "Never"
containers:
- name: app
image: demo:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- standard
`
newIndex := manifest.Parse([]byte(newManifest), "ops", true)
var buf bytes.Buffer
changed := Manifests(map[string]*manifest.MappingResult{}, newIndex, opts, &buf)
require.True(t, changed)
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "ADD", entries[0].ChangeType)
require.True(t, entries[0].ResourceStatus.NewExists)
require.False(t, entries[0].ResourceStatus.OldExists)
buf.Reset()
changed = Manifests(newIndex, map[string]*manifest.MappingResult{}, opts, &buf)
require.True(t, changed)
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "REMOVE", entries[0].ChangeType)
require.True(t, entries[0].ResourceStatus.OldExists)
require.False(t, entries[0].ResourceStatus.NewExists)
}
func TestStructuredOutputSuppressedKind(t *testing.T) {
ansi.DisableColors(true)
opts := &Options{
OutputFormat: "structured",
SuppressedKinds: []string{"Secret"},
}
oldManifest := `
apiVersion: v1
kind: Secret
metadata:
name: creds
data:
password: c29tZQ==
`
newManifest := `
apiVersion: v1
kind: Secret
metadata:
name: creds
data:
password: Zm9v
`
oldIndex := manifest.Parse([]byte(oldManifest), "default", true)
newIndex := manifest.Parse([]byte(newManifest), "default", true)
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed)
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.True(t, entries[0].ChangesSuppressed)
require.Empty(t, entries[0].Changes)
}
func findChange(changes []FieldChange, path, field string) (FieldChange, bool) {
for _, change := range changes {
if change.Path == path && change.Field == field {
return change, true
}
}
return FieldChange{}, false
}
func TestStructuredOutputErrorPaths(t *testing.T) {
ansi.DisableColors(true)
key := "default, failing, ConfigMap (v1)"
opts := &Options{OutputFormat: "structured"}
validConfigMap := `
apiVersion: v1
kind: ConfigMap
metadata:
name: valid
namespace: default
data:
foo: bar
`
makeMapping := func(content string) *manifest.MappingResult {
return &manifest.MappingResult{
Name: key,
Kind: "ConfigMap",
Content: content,
}
}
t.Run("InvalidNewManifestYAML", func(t *testing.T) {
oldIndex := map[string]*manifest.MappingResult{
key: makeMapping(validConfigMap),
}
newIndex := map[string]*manifest.MappingResult{
key: makeMapping(":\n not-valid: value"),
}
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed, "Should report resource-level change even when YAML parsing fails")
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "MODIFY", entries[0].ChangeType)
})
t.Run("InvalidOldManifestYAML", func(t *testing.T) {
oldIndex := map[string]*manifest.MappingResult{
key: makeMapping("metadata:\n name: invalid\n namespace: default\n labels:\n : bad"),
}
newIndex := map[string]*manifest.MappingResult{
key: makeMapping(validConfigMap),
}
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed, "Should report resource-level change even when YAML parsing fails")
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "MODIFY", entries[0].ChangeType)
})
t.Run("ArrayDocumentProducesJSONUnmarshalError", func(t *testing.T) {
listManifest := `
- apiVersion: v1
kind: ConfigMap
metadata:
name: list
namespace: default
data:
key: value
`
newIndex := map[string]*manifest.MappingResult{
key: makeMapping(listManifest),
}
var buf bytes.Buffer
changed := Manifests(map[string]*manifest.MappingResult{}, newIndex, opts, &buf)
require.True(t, changed, "Should report resource-level change even when JSON unmarshal fails")
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "ADD", entries[0].ChangeType)
})
}
func TestStructuredOutputAdditionalScenarios(t *testing.T) {
ansi.DisableColors(true)
opts := &Options{OutputFormat: "structured"}
t.Run("EmptyManifestHandling", func(t *testing.T) {
emptyManifest := ``
validManifest := `
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: default
`
oldIndex := manifest.Parse([]byte(emptyManifest), "default", true)
newIndex := manifest.Parse([]byte(validManifest), "default", true)
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed)
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "ADD", entries[0].ChangeType)
require.False(t, entries[0].ResourceStatus.OldExists)
require.True(t, entries[0].ResourceStatus.NewExists)
})
t.Run("NullYAMLDocument", func(t *testing.T) {
nullManifest := `null`
validManifest := `
apiVersion: v1
kind: Service
metadata:
name: test
namespace: default
`
oldIndex := manifest.Parse([]byte(nullManifest), "default", true)
newIndex := manifest.Parse([]byte(validManifest), "default", true)
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed)
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "ADD", entries[0].ChangeType)
})
t.Run("ComplexNestedStructuresForJSONPatch", func(t *testing.T) {
oldManifest := `
apiVersion: v1
kind: ConfigMap
metadata:
name: complex
namespace: default
labels:
app: test
version: v1
data:
config.yaml: |
nested:
deeply:
value: old
`
newManifest := `
apiVersion: v1
kind: ConfigMap
metadata:
name: complex
namespace: default
labels:
app: test
version: v2
data:
config.yaml: |
nested:
deeply:
value: new
`
oldIndex := manifest.Parse([]byte(oldManifest), "default", true)
newIndex := manifest.Parse([]byte(newManifest), "default", true)
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed)
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "MODIFY", entries[0].ChangeType)
require.NotEmpty(t, entries[0].Changes, "Should detect changes in nested structures")
versionChange, found := findChange(entries[0].Changes, "metadata.labels", "version")
require.True(t, found, "Should find version label change")
require.Equal(t, "v1", versionChange.OldValue)
require.Equal(t, "v2", versionChange.NewValue)
})
t.Run("ArrayChangesInStructuredOutput", func(t *testing.T) {
oldManifest := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: prod
spec:
template:
spec:
containers:
- name: app
image: myapp:v1
env:
- name: KEY1
value: val1
`
newManifest := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: prod
spec:
template:
spec:
containers:
- name: app
image: myapp:v2
env:
- name: KEY1
value: val1
- name: KEY2
value: val2
`
oldIndex := manifest.Parse([]byte(oldManifest), "prod", true)
newIndex := manifest.Parse([]byte(newManifest), "prod", true)
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.True(t, changed)
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Len(t, entries, 1)
require.Equal(t, "MODIFY", entries[0].ChangeType)
require.NotEmpty(t, entries[0].Changes, "Should detect changes")
imageChange, found := findChange(entries[0].Changes, "spec.template.spec.containers[0]", "image")
require.True(t, found, "Should find image change")
require.Equal(t, "myapp:v1", imageChange.OldValue)
require.Equal(t, "myapp:v2", imageChange.NewValue)
})
t.Run("BothManifestsEmpty", func(t *testing.T) {
emptyManifest1 := ``
emptyManifest2 := ``
oldIndex := manifest.Parse([]byte(emptyManifest1), "default", true)
newIndex := manifest.Parse([]byte(emptyManifest2), "default", true)
var buf bytes.Buffer
changed := Manifests(oldIndex, newIndex, opts, &buf)
require.False(t, changed, "No changes should be detected with both empty")
var entries []StructuredEntry
require.NoError(t, json.Unmarshal(buf.Bytes(), &entries))
require.Empty(t, entries, "Should have no entries for empty manifests")
})
}
func TestManifestsWithRedactedSecrets(t *testing.T) {
ansi.DisableColors(true)
specSecretWithByteData := map[string]*manifest.MappingResult{
"default, foobar, Secret (v1)": {
Name: "default, foobar, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foobar
type: Opaque
data:
key1: dmFsdWUx
key2: dmFsdWUy
key3: dmFsdWUz
`,
},
}
specSecretWithByteDataChanged := map[string]*manifest.MappingResult{
"default, foobar, Secret (v1)": {
Name: "default, foobar, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foobar
type: Opaque
data:
key1: dmFsdWUxY2hhbmdlZA==
key2: dmFsdWUy
key4: dmFsdWU0
`,
},
}
specSecretWithStringData := map[string]*manifest.MappingResult{
"default, foobar, Secret (v1)": {
Name: "default, foobar, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foobar
type: Opaque
stringData:
key1: value1
key2: value2
key3: value3
`,
},
}
specSecretWithStringDataChanged := map[string]*manifest.MappingResult{
"default, foobar, Secret (v1)": {
Name: "default, foobar, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foobar
type: Opaque
stringData:
key1: value1changed
key2: value2
key4: value4
`,
},
}
t.Run("OnChangeSecretWithByteData", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, false, false, []string{}, 0.5, []string{}}
if changesSeen := Manifests(specSecretWithByteData, specSecretWithByteDataChanged, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, foobar, Secret (v1) has changed:
apiVersion: v1
kind: Secret
metadata:
name: foobar
data:
- key1: '-------- # (6 bytes)'
+ key1: '++++++++ # (13 bytes)'
key2: 'REDACTED # (6 bytes)'
- key3: '-------- # (6 bytes)'
+ key4: '++++++++ # (6 bytes)'
type: Opaque
`, buf1.String())
})
t.Run("OnChangeSecretWithStringData", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, false, false, []string{}, 0.5, []string{}}
if changesSeen := Manifests(specSecretWithStringData, specSecretWithStringDataChanged, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, foobar, Secret (v1) has changed:
apiVersion: v1
kind: Secret
metadata:
name: foobar
data:
- key1: '-------- # (6 bytes)'
+ key1: '++++++++ # (13 bytes)'
key2: 'REDACTED # (6 bytes)'
- key3: '-------- # (6 bytes)'
+ key4: '++++++++ # (6 bytes)'
type: Opaque
`, buf1.String())
})
}
func TestDoSuppress(t *testing.T) {
for _, tt := range []struct {
name string
input Report
supressRegex []string
expected Report
}{
{
name: "noop",
input: Report{},
supressRegex: []string{},
expected: Report{},
},
{
name: "simple",
input: Report{
Entries: []ReportEntry{
{
Diffs: diffStrings("hello: world", "hello: world2", false),
},
},
},
supressRegex: []string{},
expected: Report{
Entries: []ReportEntry{
{
Diffs: diffStrings("hello: world", "hello: world2", false),
},
},
},
},
{
name: "ignore all",
input: Report{
Entries: []ReportEntry{
{
Diffs: diffStrings("hello: world", "hello: world2", false),
},
},
},
supressRegex: []string{".*world2?"},
expected: Report{
Entries: []ReportEntry{
{
Diffs: []difflib.DiffRecord{},
},
},
},
},
{
name: "ignore partial",
input: Report{
Entries: []ReportEntry{
{
Diffs: diffStrings("hello: world", "hello: world2", false),
},
},
},
supressRegex: []string{".*world2"},
expected: Report{
Entries: []ReportEntry{
{
Diffs: []difflib.DiffRecord{
{
Payload: "hello: world",
Delta: difflib.LeftOnly,
},
},
},
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
report, err := doSuppress(tt.input, tt.supressRegex)
require.NoError(t, err)
require.Equal(t, tt.expected, report)
})
}
}
func TestChangeOwnership(t *testing.T) {
ansi.DisableColors(true)
specOriginal := map[string]*manifest.MappingResult{
"default, foobar, ConfigMap (v1)": {
Name: "default, foobar, ConfigMap (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: ConfigMap
metadata:
name: foobar
data:
key1: value1
`,
},
}
t.Run("OnChangeOwnershipWithoutSpecChange", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}}
newOwnedReleases := map[string]OwnershipDiff{
"default, foobar, ConfigMap (v1)": {
OldRelease: "default/oldfoobar",
NewRelease: "default/foobar",
},
}
if changesSeen := ManifestsOwnership(specOriginal, specOriginal, newOwnedReleases, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, foobar, ConfigMap (v1) changed ownership:
- default/oldfoobar
+ default/foobar
`, buf1.String())
})
t.Run("OnChangeOwnershipWithSpecChange", func(t *testing.T) {
var buf1 bytes.Buffer
diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}}
specNew := map[string]*manifest.MappingResult{
"default, foobar, ConfigMap (v1)": {
Name: "default, foobar, ConfigMap (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: ConfigMap
metadata:
name: foobar
data:
key1: newValue1
`,
},
}
newOwnedReleases := map[string]OwnershipDiff{
"default, foobar, ConfigMap (v1)": {
OldRelease: "default/oldfoobar",
NewRelease: "default/foobar",
},
}
if changesSeen := ManifestsOwnership(specOriginal, specNew, newOwnedReleases, &diffOptions, &buf1); !changesSeen {
t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`")
}
require.Equal(t, `default, foobar, ConfigMap (v1) changed ownership:
- default/oldfoobar
+ default/foobar
default, foobar, ConfigMap (v1) has changed:
apiVersion: v1
kind: ConfigMap
metadata:
name: foobar
data:
- key1: value1
+ key1: newValue1
`, buf1.String())
})
}
func TestDecodeSecrets(t *testing.T) {
ansi.DisableColors(true)
t.Run("decodeSecrets with valid base64 data", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: dmFsdWUx
key2: dmFsdWUy
`,
}
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: bmV3dmFsdWUx
key2: dmFsdWUy
`,
}
decodeSecrets(old, new)
require.Contains(t, old.Content, "key1: value1")
require.Contains(t, old.Content, "key2: value2")
require.Contains(t, new.Content, "key1: newvalue1")
require.Contains(t, new.Content, "key2: value2")
})
t.Run("decodeSecrets with stringData", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
stringData:
key1: value1
key2: value2
`,
}
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
stringData:
key1: value1changed
key2: value2
`,
}
decodeSecrets(old, new)
require.Contains(t, old.Content, "key1: value1")
require.Contains(t, old.Content, "key2: value2")
require.Contains(t, new.Content, "key1: value1changed")
require.Contains(t, new.Content, "key2: value2")
})
t.Run("decodeSecrets with stringData and data ensuring that stringData always precedes/overrides data on Secrets", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
stringData:
key1: value1.stringdata
key2: value2.stringdata
data:
key2: dmFsdWUyLmRhdGE=
key3: dmFsdWUzLmRhdGE=
`,
}
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
stringData:
key1: value1changed.stringdata
key2: value2.stringdata
data:
key3: dmFsdWUzLmRhdGE=
`,
}
decodeSecrets(old, new)
require.Contains(t, old.Content, "key1: value1.stringdata")
require.Contains(t, old.Content, "key2: value2.stringdata")
require.Contains(t, old.Content, "key3: value3.data")
require.Contains(t, new.Content, "key1: value1changed.stringdata")
require.Contains(t, new.Content, "key2: value2.stringdata")
require.Contains(t, new.Content, "key3: value3.data")
})
t.Run("decodeSecrets with invalid base64", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: invalidbase64
`,
}
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: dmFsdWUx
`,
}
decodeSecrets(old, new)
require.Contains(t, old.Content, "Error parsing old secret")
require.Contains(t, new.Content, "key1: value1")
})
t.Run("decodeSecrets with non-Secret kind", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, ConfigMap (v1)",
Kind: "ConfigMap",
Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n",
}
new := &manifest.MappingResult{
Name: "default, foo, ConfigMap (v1)",
Kind: "ConfigMap",
Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n",
}
origOld := old.Content
origNew := new.Content
decodeSecrets(old, new)
require.Equal(t, origOld, old.Content)
require.Equal(t, origNew, new.Content)
})
t.Run("decodeSecrets with nil arguments", func(t *testing.T) {
decodeSecrets(nil, nil)
})
}
func TestRedactSecrets(t *testing.T) {
ansi.DisableColors(true)
t.Run("redactSecrets with valid base64 data", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: dmFsdWUx
key2: dmFsdWUy
`,
}
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: bmV3dmFsdWUx
key2: dmFsdWUy
`,
}
redactSecrets(old, new)
require.Contains(t, old.Content, "key1: '-------- # (6 bytes)'")
require.Contains(t, old.Content, "key2: 'REDACTED # (6 bytes)'")
require.Contains(t, new.Content, "key1: '++++++++ # (9 bytes)'")
require.Contains(t, new.Content, "key2: 'REDACTED # (6 bytes)'")
})
t.Run("redactSecrets with stringData", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
stringData:
key1: value1
key2: value2
`,
}
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
stringData:
key1: value1changed
key2: value2
`,
}
redactSecrets(old, new)
require.Contains(t, old.Content, "key1: '-------- # (6 bytes)'")
require.Contains(t, old.Content, "key2: 'REDACTED # (6 bytes)'")
require.Contains(t, new.Content, "key1: '++++++++ # (13 bytes)'")
require.Contains(t, new.Content, "key2: 'REDACTED # (6 bytes)'")
})
t.Run("redactSecrets with nil arguments", func(t *testing.T) {
redactSecrets(nil, nil)
})
t.Run("redactSecrets with non-Secret kind", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, ConfigMap (v1)",
Kind: "ConfigMap",
Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n",
}
new := &manifest.MappingResult{
Name: "default, foo, ConfigMap (v1)",
Kind: "ConfigMap",
Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n",
}
origOld := old.Content
origNew := new.Content
redactSecrets(old, new)
require.Equal(t, origOld, old.Content)
require.Equal(t, origNew, new.Content)
})
t.Run("redactSecrets with invalid YAML", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: "invalid: yaml: :::",
}
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: "invalid: yaml: :::",
}
redactSecrets(old, new)
require.Contains(t, old.Content, "Error parsing old secret")
require.Contains(t, new.Content, "Error parsing new secret")
})
t.Run("redactSecrets with only old secret", func(t *testing.T) {
old := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: dmFsdWUx
`,
}
redactSecrets(old, nil)
require.Contains(t, old.Content, "key1: '-------- # (6 bytes)'")
})
t.Run("redactSecrets with only new secret", func(t *testing.T) {
new := &manifest.MappingResult{
Name: "default, foo, Secret (v1)",
Kind: "Secret",
Content: `
apiVersion: v1
kind: Secret
metadata:
name: foo
type: Opaque
data:
key1: dmFsdWUx
`,
}
redactSecrets(nil, new)
require.Contains(t, new.Content, "key1: '++++++++ # (6 bytes)'")
})
}
func TestRenameDetectionLengthRatio(t *testing.T) {
ansi.DisableColors(true)
makeSpec := func(name string, content string) map[string]*manifest.MappingResult {
return map[string]*manifest.MappingResult{
name: {
Name: name,
Kind: "Deployment",
Content: content,
},
}
}
shortContent := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: short
spec:
replicas: 1
`
shortContentRenamed := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: short-renamed
spec:
replicas: 1
`
longContent := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: very-long
spec:
replicas: 1
template:
spec:
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
env:
- name: VAR1
value: "hello"
- name: VAR2
value: "world"
- name: VAR3
value: "foo"
- name: VAR4
value: "bar"
- name: VAR5
value: "baz"
resources:
limits:
cpu: "1"
memory: "1Gi"
`
t.Run("similar length detects rename", func(t *testing.T) {
var buf bytes.Buffer
opts := &Options{OutputFormat: "diff", OutputContext: 10, ShowSecrets: true, FindRenames: 0.5}
oldSpec := makeSpec("default, short, Deployment (apps)", shortContent)
newSpec := makeSpec("default, short-renamed, Deployment (apps)", shortContentRenamed)
changed := Manifests(oldSpec, newSpec, opts, &buf)
require.True(t, changed)
require.Contains(t, buf.String(), "default, short, Deployment (apps) has changed")
})
t.Run("very different length skips rename", func(t *testing.T) {
var buf bytes.Buffer
opts := &Options{OutputFormat: "diff", OutputContext: 10, ShowSecrets: true, FindRenames: 0.5}
oldSpec := makeSpec("default, short, Deployment (apps)", shortContent)
newSpec := makeSpec("default, very-long, Deployment (apps)", longContent)
changed := Manifests(oldSpec, newSpec, opts, &buf)
require.True(t, changed)
require.Contains(t, buf.String(), "default, short, Deployment (apps) has been removed")
require.Contains(t, buf.String(), "default, very-long, Deployment (apps) has been added")
})
t.Run("empty content skipped", func(t *testing.T) {
var buf bytes.Buffer
opts := &Options{OutputFormat: "diff", OutputContext: 10, ShowSecrets: true, FindRenames: 0.5}
oldSpec := map[string]*manifest.MappingResult{
"default, empty, Deployment (apps)": {
Name: "default, empty, Deployment (apps)",
Kind: "Deployment",
Content: "",
},
}
newSpec := makeSpec("default, short-renamed, Deployment (apps)", shortContentRenamed)
changed := Manifests(oldSpec, newSpec, opts, &buf)
require.True(t, changed)
require.Contains(t, buf.String(), "has been added")
})
t.Run("different kind skipped", func(t *testing.T) {
var buf bytes.Buffer
opts := &Options{OutputFormat: "diff", OutputContext: 10, ShowSecrets: true, FindRenames: 0.5}
oldSpec := map[string]*manifest.MappingResult{
"default, svc, Service (v1)": {
Name: "default, svc, Service (v1)",
Kind: "Service",
Content: shortContent,
},
}
newSpec := makeSpec("default, svc-renamed, Deployment (apps)", shortContentRenamed)
changed := Manifests(oldSpec, newSpec, opts, &buf)
require.True(t, changed)
require.Contains(t, buf.String(), "has been removed")
require.Contains(t, buf.String(), "has been added")
})
}