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 }