github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/compose/template/template_test.go (about)

     1  package template
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"gotest.tools/v3/assert"
     8  	is "gotest.tools/v3/assert/cmp"
     9  )
    10  
    11  var defaults = map[string]string{
    12  	"FOO": "first",
    13  	"BAR": "",
    14  }
    15  
    16  func defaultMapping(name string) (string, bool) {
    17  	val, ok := defaults[name]
    18  	return val, ok
    19  }
    20  
    21  func TestEscaped(t *testing.T) {
    22  	result, err := Substitute("$${foo}", defaultMapping)
    23  	assert.NilError(t, err)
    24  	assert.Check(t, is.Equal("${foo}", result))
    25  }
    26  
    27  func TestSubstituteNoMatch(t *testing.T) {
    28  	result, err := Substitute("foo", defaultMapping)
    29  	assert.NilError(t, err)
    30  	assert.Equal(t, "foo", result)
    31  }
    32  
    33  func TestInvalid(t *testing.T) {
    34  	invalidTemplates := []string{
    35  		"${",
    36  		"$}",
    37  		"${}",
    38  		"${ }",
    39  		"${ foo}",
    40  		"${foo }",
    41  		"${foo!}",
    42  	}
    43  
    44  	for _, template := range invalidTemplates {
    45  		_, err := Substitute(template, defaultMapping)
    46  		assert.ErrorContains(t, err, "Invalid template")
    47  	}
    48  }
    49  
    50  func TestNoValueNoDefault(t *testing.T) {
    51  	for _, template := range []string{"This ${missing} var", "This ${BAR} var"} {
    52  		result, err := Substitute(template, defaultMapping)
    53  		assert.NilError(t, err)
    54  		assert.Check(t, is.Equal("This  var", result))
    55  	}
    56  }
    57  
    58  func TestValueNoDefault(t *testing.T) {
    59  	for _, template := range []string{"This $FOO var", "This ${FOO} var"} {
    60  		result, err := Substitute(template, defaultMapping)
    61  		assert.NilError(t, err)
    62  		assert.Check(t, is.Equal("This first var", result))
    63  	}
    64  }
    65  
    66  func TestNoValueWithDefault(t *testing.T) {
    67  	for _, template := range []string{"ok ${missing:-def}", "ok ${missing-def}"} {
    68  		result, err := Substitute(template, defaultMapping)
    69  		assert.NilError(t, err)
    70  		assert.Check(t, is.Equal("ok def", result))
    71  	}
    72  }
    73  
    74  func TestEmptyValueWithSoftDefault(t *testing.T) {
    75  	result, err := Substitute("ok ${BAR:-def}", defaultMapping)
    76  	assert.NilError(t, err)
    77  	assert.Check(t, is.Equal("ok def", result))
    78  }
    79  
    80  func TestValueWithSoftDefault(t *testing.T) {
    81  	result, err := Substitute("ok ${FOO:-def}", defaultMapping)
    82  	assert.NilError(t, err)
    83  	assert.Check(t, is.Equal("ok first", result))
    84  }
    85  
    86  func TestEmptyValueWithHardDefault(t *testing.T) {
    87  	result, err := Substitute("ok ${BAR-def}", defaultMapping)
    88  	assert.NilError(t, err)
    89  	assert.Check(t, is.Equal("ok ", result))
    90  }
    91  
    92  func TestNonAlphanumericDefault(t *testing.T) {
    93  	result, err := Substitute("ok ${BAR:-/non:-alphanumeric}", defaultMapping)
    94  	assert.NilError(t, err)
    95  	assert.Check(t, is.Equal("ok /non:-alphanumeric", result))
    96  }
    97  
    98  func TestMandatoryVariableErrors(t *testing.T) {
    99  	testCases := []struct {
   100  		template      string
   101  		expectedError string
   102  	}{
   103  		{
   104  			template:      "not ok ${UNSET_VAR:?Mandatory Variable Unset}",
   105  			expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
   106  		},
   107  		{
   108  			template:      "not ok ${BAR:?Mandatory Variable Empty}",
   109  			expectedError: "required variable BAR is missing a value: Mandatory Variable Empty",
   110  		},
   111  		{
   112  			template:      "not ok ${UNSET_VAR:?}",
   113  			expectedError: "required variable UNSET_VAR is missing a value",
   114  		},
   115  		{
   116  			template:      "not ok ${UNSET_VAR?Mandatory Variable Unset}",
   117  			expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
   118  		},
   119  		{
   120  			template:      "not ok ${UNSET_VAR?}",
   121  			expectedError: "required variable UNSET_VAR is missing a value",
   122  		},
   123  	}
   124  
   125  	for _, tc := range testCases {
   126  		_, err := Substitute(tc.template, defaultMapping)
   127  		assert.Check(t, is.ErrorContains(err, tc.expectedError))
   128  		assert.Check(t, is.ErrorType(err, &InvalidTemplateError{}))
   129  	}
   130  }
   131  
   132  func TestDefaultsForMandatoryVariables(t *testing.T) {
   133  	testCases := []struct {
   134  		template string
   135  		expected string
   136  	}{
   137  		{
   138  			template: "ok ${FOO:?err}",
   139  			expected: "ok first",
   140  		},
   141  		{
   142  			template: "ok ${FOO?err}",
   143  			expected: "ok first",
   144  		},
   145  		{
   146  			template: "ok ${BAR?err}",
   147  			expected: "ok ",
   148  		},
   149  	}
   150  
   151  	for _, tc := range testCases {
   152  		result, err := Substitute(tc.template, defaultMapping)
   153  		assert.NilError(t, err)
   154  		assert.Check(t, is.Equal(tc.expected, result))
   155  	}
   156  }
   157  
   158  func TestSubstituteWithCustomFunc(t *testing.T) {
   159  	errIsMissing := func(substitution string, mapping Mapping) (string, bool, error) {
   160  		value, found := mapping(substitution)
   161  		if !found {
   162  			return "", true, &InvalidTemplateError{
   163  				Template: fmt.Sprintf("required variable %s is missing a value", substitution),
   164  			}
   165  		}
   166  		return value, true, nil
   167  	}
   168  
   169  	result, err := SubstituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
   170  	assert.NilError(t, err)
   171  	assert.Check(t, is.Equal("ok first", result))
   172  
   173  	result, err = SubstituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
   174  	assert.NilError(t, err)
   175  	assert.Check(t, is.Equal("ok ", result))
   176  
   177  	_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
   178  	assert.Check(t, is.ErrorContains(err, "required variable"))
   179  }
   180  
   181  func TestExtractVariables(t *testing.T) {
   182  	testCases := []struct {
   183  		name     string
   184  		dict     map[string]interface{}
   185  		expected map[string]string
   186  	}{
   187  		{
   188  			name:     "empty",
   189  			dict:     map[string]interface{}{},
   190  			expected: map[string]string{},
   191  		},
   192  		{
   193  			name: "no-variables",
   194  			dict: map[string]interface{}{
   195  				"foo": "bar",
   196  			},
   197  			expected: map[string]string{},
   198  		},
   199  		{
   200  			name: "variable-without-curly-braces",
   201  			dict: map[string]interface{}{
   202  				"foo": "$bar",
   203  			},
   204  			expected: map[string]string{
   205  				"bar": "",
   206  			},
   207  		},
   208  		{
   209  			name: "variable",
   210  			dict: map[string]interface{}{
   211  				"foo": "${bar}",
   212  			},
   213  			expected: map[string]string{
   214  				"bar": "",
   215  			},
   216  		},
   217  		{
   218  			name: "required-variable",
   219  			dict: map[string]interface{}{
   220  				"foo": "${bar?:foo}",
   221  			},
   222  			expected: map[string]string{
   223  				"bar": "",
   224  			},
   225  		},
   226  		{
   227  			name: "required-variable2",
   228  			dict: map[string]interface{}{
   229  				"foo": "${bar?foo}",
   230  			},
   231  			expected: map[string]string{
   232  				"bar": "",
   233  			},
   234  		},
   235  		{
   236  			name: "default-variable",
   237  			dict: map[string]interface{}{
   238  				"foo": "${bar:-foo}",
   239  			},
   240  			expected: map[string]string{
   241  				"bar": "foo",
   242  			},
   243  		},
   244  		{
   245  			name: "default-variable2",
   246  			dict: map[string]interface{}{
   247  				"foo": "${bar-foo}",
   248  			},
   249  			expected: map[string]string{
   250  				"bar": "foo",
   251  			},
   252  		},
   253  		{
   254  			name: "multiple-values",
   255  			dict: map[string]interface{}{
   256  				"foo": "${bar:-foo}",
   257  				"bar": map[string]interface{}{
   258  					"foo": "${fruit:-banana}",
   259  					"bar": "vegetable",
   260  				},
   261  				"baz": []interface{}{
   262  					"foo",
   263  					"$docker:${project:-cli}",
   264  					"$toto",
   265  				},
   266  			},
   267  			expected: map[string]string{
   268  				"bar":     "foo",
   269  				"fruit":   "banana",
   270  				"toto":    "",
   271  				"docker":  "",
   272  				"project": "cli",
   273  			},
   274  		},
   275  	}
   276  	for _, tc := range testCases {
   277  		tc := tc
   278  		t.Run(tc.name, func(t *testing.T) {
   279  			actual := ExtractVariables(tc.dict, defaultPattern)
   280  			assert.Check(t, is.DeepEqual(actual, tc.expected))
   281  		})
   282  	}
   283  }