diff --git a/handler/template_test.go b/handler/template_test.go new file mode 100644 index 0000000..13b7525 --- /dev/null +++ b/handler/template_test.go @@ -0,0 +1,192 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package handler + +import ( + "fmt" + "os/exec" + "testing" + "text/template" + + "github.com/stretchr/testify/assert" +) + +func Test_runTemplate(t *testing.T) { + t.Parallel() + + cases := []struct { + Name string + Template *template.Template + Env TemplateEnv + Expected string + ShouldError bool + HandlerFunc func(t *testing.T, expected, actual string) bool + }{ + { + Name: "Empty", + Template: s2t(""), + }, + { + Name: "TextOnly", + Template: s2t("hello, this is a text only template"), + Expected: "hello, this is a text only template", + }, + { + Name: "WithEvent", + Template: s2t("{{.Event.Instance}}:{{.Event.State}}:{{.Event.Type}}"), + Expected: "foo_instance:MASTER:INSTANCE", + Env: TemplateEnv{ + Event: MessageEvent{ + Instance: "foo_instance", + Type: "INSTANCE", + State: "MASTER", + }, + }, + }, + { + Name: "WithCxt", + Template: s2t("{{.Cxt.key1}}:{{.Cxt.key2.subkey1}}:{{.Cxt.UpCase.Key}}"), + Expected: "value1:value2:value3", + Env: TemplateEnv{ + Cxt: map[string]any{ + "key1": "value1", + "key2": map[string]string{ + "subkey1": "value2", + }, + "UpCase": map[string]string{ + "Key": "value3", + }, + }, + }, + }, + { + Name: "WithCxtAndEvent", + Template: s2t("{{.Event.Instance}}->{{.Cxt.key1}}:{{.Cxt.key2.subkey1}}:{{.Cxt.UpCase.Key}}"), + Expected: "baz_instance->value1:value2:value3", + Env: TemplateEnv{ + Cxt: map[string]any{ + "key1": "value1", + "key2": map[string]string{ + "subkey1": "value2", + }, + "UpCase": map[string]string{ + "Key": "value3", + }, + }, + Event: MessageEvent{ + Instance: "baz_instance", + }, + }, + }, + } + + for i, test := range cases { + test := test // Avoid closure capture by ref + tname := fmt.Sprintf("%s@%d:%d", test.Name, i+1, len(cases)) + + t.Run(tname, func(t *testing.T) { + t.Parallel() + + if test.HandlerFunc == nil { + test.HandlerFunc = Noop[string] + if test.Expected != "" { + test.HandlerFunc = ExpectEqual[string] + if test.ShouldError { + test.HandlerFunc = ExpectNotEqual[string] + } + } + } + + actual, err := runTemplate(test.Template, &test.Env) + if test.ShouldError && err != nil { + return + } else if assert.NoError(t, err) { + test.HandlerFunc(t, test.Expected, actual) + } + }) + } +} + +func Test_mkCmd(t *testing.T) { + t.Parallel() + + cases := []struct { + Name string + Cmdline string + Expected *exec.Cmd + ShouldError bool + HandlerFunc func(t *testing.T, expected, actual *exec.Cmd) bool + }{ + { + Name: "Empty", + Cmdline: "", + ShouldError: true, + }, + { + Name: "CmdOnly", + Cmdline: "/bin/echo", + Expected: exec.Command("/bin/echo"), + }, + { + Name: "WithArgsSimple", + Cmdline: "/bin/echo hello, world!", + Expected: exec.Command("/bin/echo", "hello,", "world!"), + }, + { + Name: "WithArgsEscape", + Cmdline: `/bin/echo \$ESCAPED $VAR`, + Expected: exec.Command("/bin/echo", `$ESCAPED`, "$VAR"), + }, + { + Name: "WithArgsQuoted", + Cmdline: `/bin/echo "hello, world" ... 'these have spaces'`, + Expected: exec.Command("/bin/echo", "hello, world", "...", "these have spaces"), + }, + } + + for i, test := range cases { + test := test // Avoid closure capture by ref + tname := fmt.Sprintf("%s@%d:%d", test.Name, i+1, len(cases)) + + t.Run(tname, func(t *testing.T) { + t.Parallel() + + if test.HandlerFunc == nil { + test.HandlerFunc = Noop[*exec.Cmd] + if test.Expected != nil { + test.HandlerFunc = ExpectEqual[*exec.Cmd] + if test.ShouldError { + test.HandlerFunc = ExpectNotEqual[*exec.Cmd] + } + } + } + + actual, err := mkCmd(test.Cmdline) + if test.ShouldError && err != nil { + return + } else if assert.NoError(t, err) { + test.HandlerFunc(t, test.Expected, actual) + } + }) + } +} + +func ExpectEqual[T any](t *testing.T, expected, actual T) bool { + return assert.Equal(t, expected, actual) +} + +func ExpectNotEqual[T any](t *testing.T, expected, actual T) bool { + return assert.NotEqual(t, expected, actual) +} + +func Noop[T any](_ *testing.T, _, _ T) bool { + return true +} + +func s2t(s string) *template.Template { + return template.Must(template.New("TestHandler").Parse(s)) +}