package hooks_test import ( "testing" "github.com/docker/cli/cli-plugins/hooks" "github.com/spf13/cobra" "gotest.tools/v3/assert" ) // TestParseTemplate tests parsing templates as returned by plugins. // // It uses fixed string fixtures to lock in compatibility with existing // plugin templates, so older formats continue to work even if we add new // template forms. // // For helper-backed cases, it also verifies that templates produced by the // current TemplateReplace* helpers parse to the same output. This lets us // evolve the emitted template format without breaking older plugins. func TestParseTemplate(t *testing.T) { type testFlag struct { name string value string } tests := []struct { doc string template string // compatibility fixture; keep even if helpers emit a newer form templateFunc func() string flags []testFlag args []string expectedOutput []string }{ { doc: "empty template", template: "", expectedOutput: []string{""}, }, { doc: "plain message", template: "a plain template message", expectedOutput: []string{"a plain template message"}, }, { doc: "subcommand name", template: "hello {{.Name}}", // NOTE: fixture; do not modify without considering plugin compatibility templateFunc: func() string { return "hello " + hooks.TemplateReplaceSubcommandName() }, expectedOutput: []string{"hello pull"}, }, { doc: "single flag", template: `{{flag . "tag"}}`, // NOTE: fixture; do not modify without considering plugin compatibility templateFunc: func() string { return hooks.TemplateReplaceFlagValue("tag") }, flags: []testFlag{ {name: "tag", value: "my-tag"}, }, expectedOutput: []string{"my-tag"}, }, { doc: "multiple flags", template: `{{flag . "test-one"}} {{flag . "test2"}}`, // NOTE: fixture; do not modify without considering plugin compatibility templateFunc: func() string { return hooks.TemplateReplaceFlagValue("test-one") + " " + hooks.TemplateReplaceFlagValue("test2") }, flags: []testFlag{ { name: "test-one", value: "value", }, { name: "test2", value: "value2", }, }, expectedOutput: []string{"value value2"}, }, { doc: "multiple args", template: `{{arg . 0}} {{arg . 1}}`, // NOTE: fixture; do not modify without considering plugin compatibility templateFunc: func() string { return hooks.TemplateReplaceArg(0) + " " + hooks.TemplateReplaceArg(1) }, args: []string{"zero", "one"}, expectedOutput: []string{"zero one"}, }, { doc: "arg in sentence", template: "You just pulled {{arg . 0}}", // NOTE: fixture; do not modify without considering plugin compatibility templateFunc: func() string { return "You just pulled " + hooks.TemplateReplaceArg(0) }, args: []string{"alpine"}, expectedOutput: []string{"You just pulled alpine"}, }, { doc: "multiline output", template: "one line\nanother line!", expectedOutput: []string{"one line", "another line!"}, }, } for _, tc := range tests { t.Run(tc.doc, func(t *testing.T) { testCmd := &cobra.Command{ Use: "pull", Args: cobra.ExactArgs(len(tc.args)), } for _, f := range tc.flags { _ = testCmd.Flags().String(f.name, "", "") err := testCmd.Flag(f.name).Value.Set(f.value) assert.NilError(t, err) } err := testCmd.Flags().Parse(tc.args) assert.NilError(t, err) // Validate using fixtures. out, err := hooks.ParseTemplate(tc.template, testCmd) assert.NilError(t, err) assert.DeepEqual(t, out, tc.expectedOutput) if tc.templateFunc != nil { // Validate using the current template function equivalent. out, err = hooks.ParseTemplate(tc.templateFunc(), testCmd) assert.NilError(t, err) assert.DeepEqual(t, out, tc.expectedOutput) } }) } }