github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/taskenv/util_test.go (about) 1 package taskenv 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/hashicorp/nomad/ci" 8 "github.com/stretchr/testify/require" 9 "github.com/zclconf/go-cty/cty" 10 ) 11 12 // TestAddNestedKey_Ok asserts test cases that succeed when passed to 13 // addNestedKey. 14 func TestAddNestedKey_Ok(t *testing.T) { 15 ci.Parallel(t) 16 17 cases := []struct { 18 // M will be initialized if unset 19 M map[string]interface{} 20 K string 21 // Value is always "x" 22 Result map[string]interface{} 23 }{ 24 { 25 K: "foo", 26 Result: map[string]interface{}{ 27 "foo": "x", 28 }, 29 }, 30 { 31 K: "foo.bar", 32 Result: map[string]interface{}{ 33 "foo": map[string]interface{}{ 34 "bar": "x", 35 }, 36 }, 37 }, 38 { 39 K: "foo.bar.quux", 40 Result: map[string]interface{}{ 41 "foo": map[string]interface{}{ 42 "bar": map[string]interface{}{ 43 "quux": "x", 44 }, 45 }, 46 }, 47 }, 48 { 49 K: "a.b.c", 50 Result: map[string]interface{}{ 51 "a": map[string]interface{}{ 52 "b": map[string]interface{}{ 53 "c": "x", 54 }, 55 }, 56 }, 57 }, 58 { 59 // Nested object b should take precedence over values 60 M: map[string]interface{}{ 61 "a": map[string]interface{}{ 62 "b": map[string]interface{}{ 63 "c": "c", 64 }, 65 }, 66 }, 67 K: "a.b", 68 Result: map[string]interface{}{ 69 "a": map[string]interface{}{ 70 "b": map[string]interface{}{ 71 "c": "c", 72 }, 73 }, 74 }, 75 }, 76 { 77 M: map[string]interface{}{ 78 "a": map[string]interface{}{ 79 "x": "x", 80 }, 81 "z": "z", 82 }, 83 K: "a.b.c", 84 Result: map[string]interface{}{ 85 "a": map[string]interface{}{ 86 "b": map[string]interface{}{ 87 "c": "x", 88 }, 89 "x": "x", 90 }, 91 "z": "z", 92 }, 93 }, 94 { 95 M: map[string]interface{}{ 96 "foo": map[string]interface{}{ 97 "bar": map[string]interface{}{ 98 "a": "z", 99 "quux": "z", 100 }, 101 }, 102 }, 103 K: "foo.bar.quux", 104 Result: map[string]interface{}{ 105 "foo": map[string]interface{}{ 106 "bar": map[string]interface{}{ 107 "a": "z", 108 "quux": "x", 109 }, 110 }, 111 }, 112 }, 113 { 114 M: map[string]interface{}{ 115 "foo": "1", 116 "bar": "2", 117 "quux": "3", 118 }, 119 K: "a.bbbbbb.c", 120 Result: map[string]interface{}{ 121 "foo": "1", 122 "bar": "2", 123 "quux": "3", 124 "a": map[string]interface{}{ 125 "bbbbbb": map[string]interface{}{ 126 "c": "x", 127 }, 128 }, 129 }, 130 }, 131 // Regardless of whether attr.driver.qemu = "1" is added first 132 // or second, attr.driver.qemu.version = "..." should take 133 // precedence (nested maps take precedence over values) 134 { 135 M: map[string]interface{}{ 136 "attr": map[string]interface{}{ 137 "driver": map[string]interface{}{ 138 "qemu": "1", 139 }, 140 }, 141 }, 142 K: "attr.driver.qemu.version", 143 Result: map[string]interface{}{ 144 "attr": map[string]interface{}{ 145 "driver": map[string]interface{}{ 146 "qemu": map[string]interface{}{ 147 "version": "x", 148 }, 149 }, 150 }, 151 }, 152 }, 153 { 154 M: map[string]interface{}{ 155 "attr": map[string]interface{}{ 156 "driver": map[string]interface{}{ 157 "qemu": map[string]interface{}{ 158 "version": "1.2.3", 159 }, 160 }, 161 }, 162 }, 163 K: "attr.driver.qemu", 164 Result: map[string]interface{}{ 165 "attr": map[string]interface{}{ 166 "driver": map[string]interface{}{ 167 "qemu": map[string]interface{}{ 168 "version": "1.2.3", 169 }, 170 }, 171 }, 172 }, 173 }, 174 { 175 M: map[string]interface{}{ 176 "a": "a", 177 }, 178 K: "a.b", 179 Result: map[string]interface{}{ 180 "a": map[string]interface{}{ 181 "b": "x", 182 }, 183 }, 184 }, 185 { 186 M: map[string]interface{}{ 187 "a": "a", 188 "foo": map[string]interface{}{ 189 "b": "b", 190 "bar": "quux", 191 }, 192 "c": map[string]interface{}{}, 193 }, 194 K: "foo.bar.quux", 195 Result: map[string]interface{}{ 196 "a": "a", 197 "foo": map[string]interface{}{ 198 "b": "b", 199 "bar": map[string]interface{}{ 200 "quux": "x", 201 }, 202 }, 203 "c": map[string]interface{}{}, 204 }, 205 }, 206 } 207 208 for i := range cases { 209 tc := cases[i] 210 name := tc.K 211 if len(tc.M) > 0 { 212 name = fmt.Sprintf("%s-%d", name, len(tc.M)) 213 } 214 t.Run(name, func(t *testing.T) { 215 ci.Parallel(t) 216 if tc.M == nil { 217 tc.M = map[string]interface{}{} 218 } 219 require.NoError(t, addNestedKey(tc.M, tc.K, "x")) 220 require.Equal(t, tc.Result, tc.M) 221 }) 222 } 223 } 224 225 // TestAddNestedKey_Bad asserts test cases return an error when passed to 226 // addNestedKey. 227 func TestAddNestedKey_Bad(t *testing.T) { 228 ci.Parallel(t) 229 230 cases := []struct { 231 // M will be initialized if unset 232 M func() map[string]interface{} 233 K string 234 // Value is always "x" 235 // Result is compared by Error() string equality 236 Result error 237 }{ 238 { 239 K: ".", 240 Result: ErrInvalidObjectPath, 241 }, 242 { 243 K: ".foo", 244 Result: ErrInvalidObjectPath, 245 }, 246 { 247 K: "foo.", 248 Result: ErrInvalidObjectPath, 249 }, 250 { 251 K: ".a.", 252 Result: ErrInvalidObjectPath, 253 }, 254 { 255 K: "foo..bar", 256 Result: ErrInvalidObjectPath, 257 }, 258 { 259 K: "foo...bar", 260 Result: ErrInvalidObjectPath, 261 }, 262 { 263 K: "foo.bar..quux", 264 Result: ErrInvalidObjectPath, 265 }, 266 { 267 K: "foo..bar.quux", 268 Result: ErrInvalidObjectPath, 269 }, 270 { 271 K: "foo.bar.quux.", 272 Result: ErrInvalidObjectPath, 273 }, 274 { 275 M: func() map[string]interface{} { 276 return map[string]interface{}{ 277 "a": "a", 278 "foo": map[string]interface{}{ 279 "b": "b", 280 "bar": map[string]interface{}{ 281 "c": "c", 282 }, 283 }, 284 } 285 }, 286 K: "foo.bar.quux.", 287 Result: ErrInvalidObjectPath, 288 }, 289 { 290 M: func() map[string]interface{} { 291 return map[string]interface{}{ 292 "a": "a", 293 "foo": map[string]interface{}{ 294 "b": "b", 295 "bar": map[string]interface{}{ 296 "c": "c", 297 }, 298 }, 299 } 300 }, 301 K: "foo.bar..quux", 302 Result: ErrInvalidObjectPath, 303 }, 304 { 305 M: func() map[string]interface{} { 306 return map[string]interface{}{ 307 "a": "a", 308 "foo": map[string]interface{}{ 309 "b": "b", 310 "bar": map[string]interface{}{ 311 "c": "c", 312 }, 313 }, 314 } 315 }, 316 K: "foo.bar..quux", 317 Result: ErrInvalidObjectPath, 318 }, 319 } 320 321 for i := range cases { 322 tc := cases[i] 323 name := tc.K 324 if tc.M != nil { 325 name += "-cleanup" 326 } 327 t.Run(name, func(t *testing.T) { 328 ci.Parallel(t) 329 330 // Copy original M value to ensure it doesn't get altered 331 if tc.M == nil { 332 tc.M = func() map[string]interface{} { 333 return map[string]interface{}{} 334 } 335 } 336 337 // Call func and assert error 338 m := tc.M() 339 err := addNestedKey(m, tc.K, "x") 340 require.EqualError(t, err, tc.Result.Error()) 341 342 // Ensure M wasn't altered 343 require.Equal(t, tc.M(), m) 344 }) 345 } 346 } 347 348 func TestCtyify_Ok(t *testing.T) { 349 ci.Parallel(t) 350 351 cases := []struct { 352 Name string 353 In map[string]interface{} 354 Out map[string]cty.Value 355 }{ 356 { 357 Name: "OneVal", 358 In: map[string]interface{}{ 359 "a": "b", 360 }, 361 Out: map[string]cty.Value{ 362 "a": cty.StringVal("b"), 363 }, 364 }, 365 { 366 Name: "MultiVal", 367 In: map[string]interface{}{ 368 "a": "b", 369 "foo": "bar", 370 }, 371 Out: map[string]cty.Value{ 372 "a": cty.StringVal("b"), 373 "foo": cty.StringVal("bar"), 374 }, 375 }, 376 { 377 Name: "NestedVals", 378 In: map[string]interface{}{ 379 "a": "b", 380 "foo": map[string]interface{}{ 381 "c": "d", 382 "bar": map[string]interface{}{ 383 "quux": "z", 384 }, 385 }, 386 "123": map[string]interface{}{ 387 "bar": map[string]interface{}{ 388 "456": "789", 389 }, 390 }, 391 }, 392 Out: map[string]cty.Value{ 393 "a": cty.StringVal("b"), 394 "foo": cty.ObjectVal(map[string]cty.Value{ 395 "c": cty.StringVal("d"), 396 "bar": cty.ObjectVal(map[string]cty.Value{ 397 "quux": cty.StringVal("z"), 398 }), 399 }), 400 "123": cty.ObjectVal(map[string]cty.Value{ 401 "bar": cty.ObjectVal(map[string]cty.Value{ 402 "456": cty.StringVal("789"), 403 }), 404 }), 405 }, 406 }, 407 } 408 409 for i := range cases { 410 tc := cases[i] 411 t.Run(tc.Name, func(t *testing.T) { 412 ci.Parallel(t) 413 414 // ctiyif and check for errors 415 result, err := ctyify(tc.In) 416 require.NoError(t, err) 417 418 // convert results to ObjectVals and compare with RawEquals 419 resultObj := cty.ObjectVal(result) 420 OutObj := cty.ObjectVal(tc.Out) 421 require.True(t, OutObj.RawEquals(resultObj)) 422 }) 423 } 424 } 425 426 func TestCtyify_Bad(t *testing.T) { 427 ci.Parallel(t) 428 429 cases := []struct { 430 Name string 431 In map[string]interface{} 432 Out map[string]cty.Value 433 }{ 434 { 435 Name: "NonStringVal", 436 In: map[string]interface{}{ 437 "a": 1, 438 }, 439 }, 440 { 441 Name: "NestedNonString", 442 In: map[string]interface{}{ 443 "foo": map[string]interface{}{ 444 "c": 1, 445 }, 446 }, 447 }, 448 } 449 450 for i := range cases { 451 tc := cases[i] 452 t.Run(tc.Name, func(t *testing.T) { 453 ci.Parallel(t) 454 455 // ctiyif and check for errors 456 result, err := ctyify(tc.In) 457 require.Error(t, err) 458 require.Nil(t, result) 459 }) 460 } 461 }