package cmd
import (
"errors"
"os"
"strings"
"testing"
)
func TestLocalCmdArgValidation(t *testing.T) {
cases := []struct {
name string
args []string
expectError bool
}{
{
name: "no arguments",
args: []string{},
expectError: true,
},
{
name: "one argument",
args: []string{"chart1"},
expectError: true,
},
{
name: "three arguments",
args: []string{"chart1", "chart2", "chart3"},
expectError: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cmd := localCmd()
cmd.SetArgs(tc.args)
err := cmd.Execute()
if tc.expectError && err == nil {
t.Errorf("Expected error but got none")
}
if !tc.expectError && err != nil {
t.Errorf("Expected no error but got: %v", err)
}
})
}
}
func TestLocalCmdExecution(t *testing.T) {
manifestYAML := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: value
`
setupFakeHelm(t, "default", manifestYAML, "", "")
chart1 := t.TempDir()
chart2 := t.TempDir()
cmd := localCmd()
cmd.SetArgs([]string{chart1, chart2})
err := cmd.Execute()
if err != nil {
t.Errorf("Expected no error but got: %v", err)
}
}
func TestLocalCmdNoChanges(t *testing.T) {
manifestYAML := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: value
`
setupFakeHelm(t, "default", manifestYAML, "", "")
chart1 := t.TempDir()
chart2 := t.TempDir()
output, err := captureStdout(func() {
cmd := localCmd()
cmd.SetArgs([]string{chart1, chart2})
if execErr := cmd.Execute(); execErr != nil {
t.Errorf("Expected no error but got: %v", execErr)
}
})
if err != nil {
t.Fatalf("Failed to capture stdout: %v", err)
}
if output != "" {
t.Errorf("Expected no output when charts are identical, got: %q", output)
}
}
func TestLocalCmdWithChanges(t *testing.T) {
manifest1 := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: value1
`
manifest2 := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: value2
`
setupFakeHelmDual(t, manifest1, manifest2)
chart1 := t.TempDir()
chart2 := t.TempDir()
output, err := captureStdout(func() {
cmd := localCmd()
cmd.SetArgs([]string{chart1, chart2})
if execErr := cmd.Execute(); execErr != nil {
t.Errorf("Expected no error but got: %v", execErr)
}
})
if err != nil {
t.Fatalf("Failed to capture stdout: %v", err)
}
if !strings.Contains(output, "value1") || !strings.Contains(output, "value2") {
t.Errorf("Expected diff output containing value1 and value2, got: %q", output)
}
}
func TestLocalCmdDetailedExitCode(t *testing.T) {
manifest1 := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: value1
`
manifest2 := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: value2
`
setupFakeHelmDual(t, manifest1, manifest2)
chart1 := t.TempDir()
chart2 := t.TempDir()
cmd := localCmd()
cmd.SetArgs([]string{chart1, chart2, "--detailed-exitcode"})
err := cmd.Execute()
if err == nil {
t.Fatal("Expected error with exit code 2 but got nil")
}
var diffErr Error
if !errors.As(err, &diffErr) {
t.Fatalf("Expected Error type but got %T: %v", err, err)
}
if diffErr.Code != 2 {
t.Errorf("Expected exit code 2 but got %d", diffErr.Code)
}
}
func TestLocalCmdDetailedExitCodeNoChanges(t *testing.T) {
manifestYAML := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
key: value
`
setupFakeHelm(t, "default", manifestYAML, "", "")
chart1 := t.TempDir()
chart2 := t.TempDir()
cmd := localCmd()
cmd.SetArgs([]string{chart1, chart2, "--detailed-exitcode"})
err := cmd.Execute()
if err != nil {
t.Errorf("Expected no error when no changes, but got: %v", err)
}
}
func TestLocalCmdNamespace(t *testing.T) {
manifestYAML := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
data:
key: value
`
argsFile := t.TempDir() + "/args"
setupFakeHelm(t, "capture_args", manifestYAML, argsFile, "")
chart1 := t.TempDir()
chart2 := t.TempDir()
cmd := localCmd()
cmd.SetArgs([]string{chart1, chart2, "--namespace", "myns"})
err := cmd.Execute()
if err != nil {
t.Fatalf("Expected no error but got: %v", err)
}
args1, err := os.ReadFile(argsFile)
if err != nil {
t.Fatalf("Expected fake helm args file to be readable, but got: %v", err)
}
if !strings.Contains(string(args1), "--namespace myns") {
t.Errorf("Expected --namespace myns in helm template args, got: %q", string(args1))
}
}
func TestLocalCmdHelmTemplateError(t *testing.T) {
setupFakeHelm(t, "error", "", "", "")
chart1 := t.TempDir()
chart2 := t.TempDir()
cmd := localCmd()
cmd.SetArgs([]string{chart1, chart2})
err := cmd.Execute()
if err == nil {
t.Fatal("Expected error when helm template fails but got nil")
}
}
func TestPrepareStdinValues(t *testing.T) {
l := &local{
valueFiles: valueFiles{"-", "-", "real-values.yaml"},
}
stdinContent := `key: stdin-value
`
tmpStdin, err := os.CreateTemp("", "fake-stdin")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpStdin.Name())
if _, err := tmpStdin.WriteString(stdinContent); err != nil {
t.Fatal(err)
}
if err := tmpStdin.Close(); err != nil {
t.Fatal(err)
}
oldStdin := os.Stdin
f, err := os.Open(tmpStdin.Name())
if err != nil {
t.Fatal(err)
}
os.Stdin = f
defer func() {
os.Stdin = oldStdin
f.Close()
}()
cleanup, err := l.prepareStdinValues()
if err != nil {
t.Fatalf("Expected no error but got: %v", err)
}
if cleanup == nil {
t.Fatal("Expected cleanup function but got nil")
}
if l.valueFiles[0] == "-" {
t.Error("Expected first valueFile to be replaced with temp file path")
}
if l.valueFiles[1] == "-" {
t.Error("Expected second valueFile to be replaced with temp file path")
}
if l.valueFiles[0] != l.valueFiles[1] {
t.Errorf("Expected both '-' entries to point to the same temp file, got %q and %q", l.valueFiles[0], l.valueFiles[1])
}
if _, err := os.Stat(l.valueFiles[0]); os.IsNotExist(err) {
t.Errorf("Expected temp file %q to exist", l.valueFiles[0])
}
if l.valueFiles[2] != "real-values.yaml" {
t.Errorf("Expected third valueFile to be unchanged, got %q", l.valueFiles[2])
}
data, err := os.ReadFile(l.valueFiles[0])
if err != nil {
t.Fatalf("Failed to read temp file: %v", err)
}
if string(data) != stdinContent {
t.Errorf("Expected temp file to contain stdin content %q, got %q", stdinContent, string(data))
}
cleanup()
if _, err := os.Stat(l.valueFiles[0]); !os.IsNotExist(err) {
t.Errorf("Expected temp file %q to be removed after cleanup", l.valueFiles[0])
}
}
func TestPrepareStdinValuesNoStdin(t *testing.T) {
l := &local{
valueFiles: valueFiles{"values1.yaml", "values2.yaml"},
}
cleanup, err := l.prepareStdinValues()
if err != nil {
t.Fatalf("Expected no error but got: %v", err)
}
if cleanup != nil {
t.Error("Expected nil cleanup function when no stdin values")
}
if l.valueFiles[0] != "values1.yaml" || l.valueFiles[1] != "values2.yaml" {
t.Errorf("Expected valueFiles to be unchanged, got %v", l.valueFiles)
}
}
func setupFakeHelm(t *testing.T, mode, output, argsFile, countFile string) {
t.Helper()
t.Setenv("HELM_DIFF_FAKE_HELM", "1")
t.Setenv("HELM_DIFF_FAKE_HELM_MODE", mode)
t.Setenv("HELM_DIFF_FAKE_OUTPUT", output)
t.Setenv("HELM_DIFF_FAKE_ARGS_FILE", argsFile)
t.Setenv("HELM_DIFF_FAKE_COUNT_FILE", countFile)
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
t.Setenv("HELM_BIN", exe)
}
func setupFakeHelmDual(t *testing.T, manifest1, manifest2 string) {
t.Helper()
countFile := t.TempDir() + "/call_count"
setupFakeHelm(t, "dual", "", "", countFile)
t.Setenv("HELM_DIFF_FAKE_OUTPUT_1", manifest1)
t.Setenv("HELM_DIFF_FAKE_OUTPUT_2", manifest2)
}