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 }