DockerCLI/cli/compose/template/template_test.go

286 lines
6.6 KiB
Go

// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.21
package template
import (
"fmt"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
var defaults = map[string]string{
"FOO": "first",
"BAR": "",
}
func defaultMapping(name string) (string, bool) {
val, ok := defaults[name]
return val, ok
}
func TestEscaped(t *testing.T) {
result, err := Substitute("$${foo}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("${foo}", result))
}
func TestSubstituteNoMatch(t *testing.T) {
result, err := Substitute("foo", defaultMapping)
assert.NilError(t, err)
assert.Equal(t, "foo", result)
}
func TestInvalid(t *testing.T) {
invalidTemplates := []string{
"${",
"$}",
"${}",
"${ }",
"${ foo}",
"${foo }",
"${foo!}",
}
for _, template := range invalidTemplates {
_, err := Substitute(template, defaultMapping)
assert.ErrorContains(t, err, "Invalid template")
}
}
func TestNoValueNoDefault(t *testing.T) {
for _, template := range []string{"This ${missing} var", "This ${BAR} var"} {
result, err := Substitute(template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("This var", result))
}
}
func TestValueNoDefault(t *testing.T) {
for _, template := range []string{"This $FOO var", "This ${FOO} var"} {
result, err := Substitute(template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("This first var", result))
}
}
func TestNoValueWithDefault(t *testing.T) {
for _, template := range []string{"ok ${missing:-def}", "ok ${missing-def}"} {
result, err := Substitute(template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok def", result))
}
}
func TestEmptyValueWithSoftDefault(t *testing.T) {
result, err := Substitute("ok ${BAR:-def}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok def", result))
}
func TestValueWithSoftDefault(t *testing.T) {
result, err := Substitute("ok ${FOO:-def}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok first", result))
}
func TestEmptyValueWithHardDefault(t *testing.T) {
result, err := Substitute("ok ${BAR-def}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok ", result))
}
func TestNonAlphanumericDefault(t *testing.T) {
result, err := Substitute("ok ${BAR:-/non:-alphanumeric}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok /non:-alphanumeric", result))
}
func TestMandatoryVariableErrors(t *testing.T) {
testCases := []struct {
template string
expectedError string
}{
{
template: "not ok ${UNSET_VAR:?Mandatory Variable Unset}",
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
},
{
template: "not ok ${BAR:?Mandatory Variable Empty}",
expectedError: "required variable BAR is missing a value: Mandatory Variable Empty",
},
{
template: "not ok ${UNSET_VAR:?}",
expectedError: "required variable UNSET_VAR is missing a value",
},
{
template: "not ok ${UNSET_VAR?Mandatory Variable Unset}",
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
},
{
template: "not ok ${UNSET_VAR?}",
expectedError: "required variable UNSET_VAR is missing a value",
},
}
for _, tc := range testCases {
_, err := Substitute(tc.template, defaultMapping)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
assert.Check(t, is.ErrorType(err, &InvalidTemplateError{}))
}
}
func TestDefaultsForMandatoryVariables(t *testing.T) {
testCases := []struct {
template string
expected string
}{
{
template: "ok ${FOO:?err}",
expected: "ok first",
},
{
template: "ok ${FOO?err}",
expected: "ok first",
},
{
template: "ok ${BAR?err}",
expected: "ok ",
},
}
for _, tc := range testCases {
result, err := Substitute(tc.template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal(tc.expected, result))
}
}
func TestSubstituteWithCustomFunc(t *testing.T) {
errIsMissing := func(substitution string, mapping Mapping) (string, bool, error) {
value, found := mapping(substitution)
if !found {
return "", true, &InvalidTemplateError{
Template: fmt.Sprintf("required variable %s is missing a value", substitution),
}
}
return value, true, nil
}
result, err := SubstituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok first", result))
result, err = SubstituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok ", result))
_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
assert.Check(t, is.ErrorContains(err, "required variable"))
}
func TestExtractVariables(t *testing.T) {
testCases := []struct {
name string
dict map[string]any
expected map[string]string
}{
{
name: "empty",
dict: map[string]any{},
expected: map[string]string{},
},
{
name: "no-variables",
dict: map[string]any{
"foo": "bar",
},
expected: map[string]string{},
},
{
name: "variable-without-curly-braces",
dict: map[string]any{
"foo": "$bar",
},
expected: map[string]string{
"bar": "",
},
},
{
name: "variable",
dict: map[string]any{
"foo": "${bar}",
},
expected: map[string]string{
"bar": "",
},
},
{
name: "required-variable",
dict: map[string]any{
"foo": "${bar?:foo}",
},
expected: map[string]string{
"bar": "",
},
},
{
name: "required-variable2",
dict: map[string]any{
"foo": "${bar?foo}",
},
expected: map[string]string{
"bar": "",
},
},
{
name: "default-variable",
dict: map[string]any{
"foo": "${bar:-foo}",
},
expected: map[string]string{
"bar": "foo",
},
},
{
name: "default-variable2",
dict: map[string]any{
"foo": "${bar-foo}",
},
expected: map[string]string{
"bar": "foo",
},
},
{
name: "multiple-values",
dict: map[string]any{
"foo": "${bar:-foo}",
"bar": map[string]any{
"foo": "${fruit:-banana}",
"bar": "vegetable",
},
"baz": []any{
"foo",
"$docker:${project:-cli}",
"$toto",
},
},
expected: map[string]string{
"bar": "foo",
"fruit": "banana",
"toto": "",
"docker": "",
"project": "cli",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := ExtractVariables(tc.dict, defaultPattern)
assert.Check(t, is.DeepEqual(actual, tc.expected))
})
}
}