|
| 1 | +package charts |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + "testing" |
| 6 | +) |
| 7 | + |
| 8 | +// TestTalmLibraryFiles_NormalizesAndStripsPrefix pins the embedded talm |
| 9 | +// library collector. Keys must be relative to the talm/ root (so they |
| 10 | +// line up with a project's vendored charts/talm/ tree), the library |
| 11 | +// Chart.yaml must come back with its name/version normalized to %s (so a |
| 12 | +// version stamp never counts as content), and the helpers template must |
| 13 | +// be present verbatim. |
| 14 | +func TestTalmLibraryFiles_NormalizesAndStripsPrefix(t *testing.T) { |
| 15 | + files, err := TalmLibraryFiles() |
| 16 | + if err != nil { |
| 17 | + t.Fatalf("TalmLibraryFiles: %v", err) |
| 18 | + } |
| 19 | + |
| 20 | + if len(files) == 0 { |
| 21 | + t.Fatal("TalmLibraryFiles returned empty map; embedded talm library is missing") |
| 22 | + } |
| 23 | + |
| 24 | + for path := range files { |
| 25 | + if strings.HasPrefix(path, "talm/") { |
| 26 | + t.Errorf("key %q still carries the talm/ prefix; it would not match a vendored charts/talm/ relative path", path) |
| 27 | + } |
| 28 | + } |
| 29 | + |
| 30 | + chart, ok := files["Chart.yaml"] |
| 31 | + if !ok { |
| 32 | + t.Fatal("expected Chart.yaml entry keyed relative to the talm/ root") |
| 33 | + } |
| 34 | + if !strings.Contains(chart, "version: %s") { |
| 35 | + t.Errorf("library Chart.yaml not normalized; a version stamp would be treated as content drift:\n%s", chart) |
| 36 | + } |
| 37 | + |
| 38 | + if _, ok := files["templates/_helpers.tpl"]; !ok { |
| 39 | + t.Error("expected templates/_helpers.tpl in the embedded talm library") |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +// TestHashChartFiles_OrderIndependent pins that the digest depends on the |
| 44 | +// set of (path, content) pairs, not on map iteration order. Go map order |
| 45 | +// is randomized, so a digest that folded order in would be non- |
| 46 | +// deterministic across runs and falsely report drift. |
| 47 | +func TestHashChartFiles_OrderIndependent(t *testing.T) { |
| 48 | + a := map[string]string{ |
| 49 | + "Chart.yaml": "name: %s\nversion: %s\n", |
| 50 | + "templates/_helpers.tpl": "{{- define \"x\" -}}{{- end -}}", |
| 51 | + } |
| 52 | + b := map[string]string{ |
| 53 | + "templates/_helpers.tpl": "{{- define \"x\" -}}{{- end -}}", |
| 54 | + "Chart.yaml": "name: %s\nversion: %s\n", |
| 55 | + } |
| 56 | + |
| 57 | + if HashChartFiles(a) != HashChartFiles(b) { |
| 58 | + t.Error("digest depends on map order; it must be deterministic over the (path, content) set") |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +// TestHashChartFiles_ContentSensitive pins that any change to a file's |
| 63 | +// content changes the digest. This is the signal the drift check relies |
| 64 | +// on: a stale helpers template must hash differently from a fresh one. |
| 65 | +func TestHashChartFiles_ContentSensitive(t *testing.T) { |
| 66 | + base := map[string]string{"templates/_helpers.tpl": "old"} |
| 67 | + changed := map[string]string{"templates/_helpers.tpl": "new"} |
| 68 | + |
| 69 | + if HashChartFiles(base) == HashChartFiles(changed) { |
| 70 | + t.Error("digest is insensitive to content; real chart drift would go undetected") |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +// TestHashChartFiles_PathBoundaryUnambiguous guards the length-prefixed |
| 75 | +// framing: two different file trees must not collide just because their |
| 76 | +// concatenated path+content bytes happen to line up. Without framing, |
| 77 | +// {"ab": "c"} and {"a": "bc"} would hash identically. |
| 78 | +func TestHashChartFiles_PathBoundaryUnambiguous(t *testing.T) { |
| 79 | + left := map[string]string{"ab": "c"} |
| 80 | + right := map[string]string{"a": "bc"} |
| 81 | + |
| 82 | + if HashChartFiles(left) == HashChartFiles(right) { |
| 83 | + t.Error("path/content boundary is ambiguous; distinct trees collide to the same digest") |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +// TestNormalizeChartMeta_VersionStampDoesNotAffectHash is the core |
| 88 | +// correctness guard for the whole drift feature: two library trees that |
| 89 | +// differ ONLY in the Chart.yaml version stamp must hash identically once |
| 90 | +// normalized. A binary version bump that left charts/talm/ byte-identical |
| 91 | +// must not be reported as drift — that false positive is exactly what the |
| 92 | +// version-number comparison approach got wrong. |
| 93 | +func TestNormalizeChartMeta_VersionStampDoesNotAffectHash(t *testing.T) { |
| 94 | + old := map[string]string{ |
| 95 | + "Chart.yaml": NormalizeChartMeta("Chart.yaml", "name: talm\nversion: 0.27.0\ntype: library\n"), |
| 96 | + "templates/_helpers.tpl": "{{- define \"x\" -}}{{- end -}}", |
| 97 | + } |
| 98 | + fresh := map[string]string{ |
| 99 | + "Chart.yaml": NormalizeChartMeta("Chart.yaml", "name: talm\nversion: 0.30.0\ntype: library\n"), |
| 100 | + "templates/_helpers.tpl": "{{- define \"x\" -}}{{- end -}}", |
| 101 | + } |
| 102 | + |
| 103 | + if HashChartFiles(old) != HashChartFiles(fresh) { |
| 104 | + t.Error("version-only difference changed the digest; a pure version bump would raise a false drift warning") |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +// TestNormalizeChartMeta_LeavesNonChartYamlUntouched pins that the |
| 109 | +// normalizer only rewrites Chart.yaml. A helpers template that happens to |
| 110 | +// contain a `version:` line (e.g. inside a rendered manifest snippet) must |
| 111 | +// pass through unchanged, or genuine drift in that template would be |
| 112 | +// masked. |
| 113 | +func TestNormalizeChartMeta_LeavesNonChartYamlUntouched(t *testing.T) { |
| 114 | + const tpl = "version: 1.2.3 # part of a rendered example, not chart metadata" |
| 115 | + if got := NormalizeChartMeta("_helpers.tpl", tpl); got != tpl { |
| 116 | + t.Errorf("NormalizeChartMeta rewrote a non-Chart.yaml file: %q", got) |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +// TestNormalizeChartMeta_PreservesApiAndAppVersion pins that only the |
| 121 | +// `name`/`version` metadata is folded to a placeholder. The camelCase |
| 122 | +// `apiVersion`/`appVersion` keys must survive verbatim — otherwise a real |
| 123 | +// change to those fields would be normalized away and hidden from the drift |
| 124 | +// comparison. Guards against a future regex tweak (e.g. adding `(?i)`) that |
| 125 | +// would start eating them. |
| 126 | +func TestNormalizeChartMeta_PreservesApiAndAppVersion(t *testing.T) { |
| 127 | + const chart = "apiVersion: v2\nname: talm\nversion: 0.1.0\nappVersion: 1.30.0\ntype: library\n" |
| 128 | + |
| 129 | + got := NormalizeChartMeta("Chart.yaml", chart) |
| 130 | + |
| 131 | + if !strings.Contains(got, "apiVersion: v2") { |
| 132 | + t.Errorf("apiVersion was rewritten:\n%s", got) |
| 133 | + } |
| 134 | + if !strings.Contains(got, "appVersion: 1.30.0") { |
| 135 | + t.Errorf("appVersion was rewritten:\n%s", got) |
| 136 | + } |
| 137 | + if !strings.Contains(got, "name: %s") || !strings.Contains(got, "version: %s") { |
| 138 | + t.Errorf("name/version not normalized:\n%s", got) |
| 139 | + } |
| 140 | +} |
0 commit comments