github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/compose/template/template_test.go (about)

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