github.com/Jeffail/benthos/v3@v3.65.0/lib/util/text/function_vars_test.go (about) 1 package text 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "strings" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/Jeffail/benthos/v3/lib/message" 13 "github.com/Jeffail/benthos/v3/lib/types" 14 ) 15 16 func TestFunctionVarDetection(t *testing.T) { 17 tests := map[string]bool{ 18 "foo ${!foo_bar} baz": true, 19 "foo ${!foo_bar} baz ${!foo_baz}": true, 20 "foo $!foo} baz $!but_not_this}": false, 21 "foo ${!baz ${!or_this": false, 22 "foo ${baz} ${or_this}": false, 23 "nothing $ here boss {!}": false, 24 "foo ${!foo_bar:arg1} baz": true, 25 "foo ${!foo_bar:} baz": false, 26 "foo ${!foo_bar:arg1} baz ${!foo_baz:arg2}": true, 27 "foo $!foo:arg2} baz $!but_not_this:}": false, 28 "nothing $ here boss {!:argnope}": false, 29 "foo ${{!foo_bar}} baz": true, 30 "foo ${{!foo_bar:default}} baz": true, 31 "foo ${{!foo_bar:default} baz": false, 32 "foo {{!foo_bar:default}} baz": false, 33 "foo {{!}} baz": false, 34 "foo ${!foo_bar:arg1,} baz ${!foo_baz:arg2,5}": true, 35 } 36 37 for in, exp := range tests { 38 act := ContainsFunctionVariables([]byte(in)) 39 if act != exp { 40 t.Errorf("Wrong result for '%v': %v != %v", in, act, exp) 41 } 42 } 43 } 44 45 func TestMetadataFunction(t *testing.T) { 46 msg := message.New([][]byte{[]byte("foo")}) 47 msg.Get(0).Metadata().Set("foo", "bar") 48 msg.Get(0).Metadata().Set("baz", "qux") 49 msg.Get(0).Metadata().Set("duck,1", "quack") 50 51 act := string(ReplaceFunctionVariables( 52 msg, []byte(`foo ${!metadata:foo} baz`), 53 )) 54 if exp := "foo bar baz"; act != exp { 55 t.Errorf("Wrong result: %v != %v", act, exp) 56 } 57 58 act = string(ReplaceFunctionVariables( 59 msg, []byte(`foo ${!metadata:bar} baz`), 60 )) 61 if exp := "foo baz"; act != exp { 62 t.Errorf("Wrong result: %v != %v", act, exp) 63 } 64 65 act = string(ReplaceFunctionVariables( 66 msg, []byte(`foo ${!metadata} bar`), 67 )) 68 if exp := `foo bar`; act != exp { 69 t.Errorf("Wrong result: %v != %v", act, exp) 70 } 71 72 act = string(ReplaceFunctionVariables( 73 msg, []byte(`foo ${!metadata:duck,1,} baz`), 74 )) 75 if exp := "foo quack baz"; act != exp { 76 t.Errorf("Wrong result: %v != %v", act, exp) 77 } 78 } 79 80 func TestErrorFunction(t *testing.T) { 81 msg := message.New([][]byte{[]byte("foo"), []byte("bar")}) 82 msg.Get(0).Metadata().Set(types.FailFlagKey, "test error") 83 84 act := string(ReplaceFunctionVariables( 85 msg, []byte(`foo ${!error} baz`), 86 )) 87 if exp := "foo test error baz"; act != exp { 88 t.Errorf("Wrong result: %v != %v", act, exp) 89 } 90 91 act = string(ReplaceFunctionVariables( 92 msg, []byte(`foo ${!error:1} baz`), 93 )) 94 if exp := "foo baz"; act != exp { 95 t.Errorf("Wrong result: %v != %v", act, exp) 96 } 97 } 98 99 func TestFunctionEscaped(t *testing.T) { 100 msg := message.New([][]byte{[]byte(`{}`)}) 101 msg.Get(0).Metadata().Set("foo", `{"foo":"bar"}`) 102 103 act := string(ReplaceFunctionVariablesEscaped( 104 msg, []byte(`{"metadata":"${!metadata:foo}"}`), 105 )) 106 if exp := `{"metadata":"{\"foo\":\"bar\"}"}`; act != exp { 107 t.Errorf("Wrong result: %v != %v", act, exp) 108 } 109 110 act = string(ReplaceFunctionVariablesEscaped( 111 msg, []byte(`"${!metadata:foo}"`), 112 )) 113 if exp := `"{\"foo\":\"bar\"}"`; act != exp { 114 t.Errorf("Wrong result: %v != %v", act, exp) 115 } 116 117 act = string(ReplaceFunctionVariablesEscaped( 118 msg, []byte(`"${!metadata_json_object}"`), 119 )) 120 if exp := `"{\"foo\":\"{\\\"foo\\\":\\\"bar\\\"}\"}"`; act != exp { 121 t.Errorf("Wrong result: %v != %v", act, exp) 122 } 123 124 act = string(ReplaceFunctionVariablesEscaped( 125 msg, []byte(`"${!metadata:bar}"`), 126 )) 127 if exp := `""`; act != exp { 128 t.Errorf("Wrong result: %v != %v", act, exp) 129 } 130 } 131 132 func TestMetadataFunctionIndex(t *testing.T) { 133 msg := message.New([][]byte{ 134 []byte("foo"), 135 []byte("bar"), 136 }) 137 msg.Get(0).Metadata().Set("foo", "bar") 138 msg.Get(1).Metadata().Set("foo", "bar2") 139 140 act := string(ReplaceFunctionVariables( 141 msg, []byte(`foo ${!metadata:foo,0} baz`), 142 )) 143 if exp := "foo bar baz"; act != exp { 144 t.Errorf("Wrong result: %v != %v", act, exp) 145 } 146 147 act = string(ReplaceFunctionVariables( 148 msg, []byte(`foo ${!metadata:foo,1} baz`), 149 )) 150 if exp := "foo bar2 baz"; act != exp { 151 t.Errorf("Wrong result: %v != %v", act, exp) 152 } 153 } 154 155 func TestMetadataMapFunction(t *testing.T) { 156 msg := message.New([][]byte{ 157 []byte("foo"), 158 []byte("bar"), 159 }) 160 msg.Get(0).Metadata().Set("foo", "bar") 161 msg.Get(0).Metadata().Set("bar", "baz") 162 msg.Get(1).Metadata().Set("foo", "bar2") 163 164 act := string(ReplaceFunctionVariables( 165 msg, []byte(`foo ${!metadata_json_object} baz`), 166 )) 167 if exp := `foo {"bar":"baz","foo":"bar"} baz`; act != exp { 168 t.Errorf("Wrong result: %v != %v", act, exp) 169 } 170 171 act = string(ReplaceFunctionVariables( 172 msg, []byte(`foo ${!metadata_json_object:0} baz`), 173 )) 174 if exp := `foo {"bar":"baz","foo":"bar"} baz`; act != exp { 175 t.Errorf("Wrong result: %v != %v", act, exp) 176 } 177 178 act = string(ReplaceFunctionVariables( 179 msg, []byte(`foo ${!metadata_json_object:1} baz`), 180 )) 181 if exp := `foo {"foo":"bar2"} baz`; act != exp { 182 t.Errorf("Wrong result: %v != %v", act, exp) 183 } 184 } 185 186 func TestContentFunctionIndex(t *testing.T) { 187 msg := message.New([][]byte{ 188 []byte("foo"), 189 []byte("bar"), 190 }) 191 192 act := string(ReplaceFunctionVariables( 193 msg, []byte(`${!content:0} bar baz`), 194 )) 195 if exp := "foo bar baz"; act != exp { 196 t.Errorf("Wrong result: %v != %v", act, exp) 197 } 198 199 act = string(ReplaceFunctionVariables( 200 msg, []byte(`foo ${!content:1} baz`), 201 )) 202 if exp := "foo bar baz"; act != exp { 203 t.Errorf("Wrong result: %v != %v", act, exp) 204 } 205 } 206 207 func TestBatchSizeFunction(t *testing.T) { 208 act := string(ReplaceFunctionVariables( 209 message.New(make([][]byte, 0)), []byte(`${!batch_size} bar baz`), 210 )) 211 if exp := "0 bar baz"; act != exp { 212 t.Errorf("Wrong result: %v != %v", act, exp) 213 } 214 215 act = string(ReplaceFunctionVariables( 216 message.New(make([][]byte, 1)), []byte(`${!batch_size} bar baz`), 217 )) 218 if exp := "1 bar baz"; act != exp { 219 t.Errorf("Wrong result: %v != %v", act, exp) 220 } 221 222 act = string(ReplaceFunctionVariables( 223 message.New(make([][]byte, 2)), []byte(`${!batch_size} bar baz`), 224 )) 225 if exp := "2 bar baz"; act != exp { 226 t.Errorf("Wrong result: %v != %v", act, exp) 227 } 228 } 229 230 func TestJSONFunction(t *testing.T) { 231 type testCase struct { 232 name string 233 input []string 234 arg string 235 result string 236 } 237 238 tests := []testCase{ 239 { 240 name: "json func 1", 241 input: []string{ 242 `{"foo":{"bar":"baz"}}`, 243 }, 244 arg: "foo ${!json_field:foo.bar,0} baz", 245 result: "foo baz baz", 246 }, 247 { 248 name: "json func 2", 249 input: []string{ 250 `{"foo":{"bar":"baz"}}`, 251 }, 252 arg: "foo ${!json_field:foo.bar,1} baz", 253 result: "foo null baz", 254 }, 255 { 256 name: "json func 3", 257 input: []string{ 258 `{"foo":{"bar":"baz"}}`, 259 }, 260 arg: "foo ${!json_field:foo.baz,0} baz", 261 result: "foo null baz", 262 }, 263 { 264 name: "json func 4", 265 input: []string{ 266 `{"foo":{"bar":{"baz":1}}}`, 267 }, 268 arg: "foo ${!json_field:foo.bar,0} baz", 269 result: `foo {"baz":1} baz`, 270 }, 271 { 272 name: "json func 5", 273 input: []string{ 274 `{"foo":{"bar":{"baz":1}}}`, 275 }, 276 arg: "foo ${!json_field:foo.bar,0} baz", 277 result: `foo {"baz":1} baz`, 278 }, 279 { 280 name: "json func 6", 281 input: []string{ 282 `{"foo":{"bar":5}}`, 283 }, 284 arg: "foo ${!json_field:foo.bar} baz", 285 result: `foo 5 baz`, 286 }, 287 { 288 name: "json func 7", 289 input: []string{ 290 `{"foo":{"bar":false}}`, 291 }, 292 arg: "foo ${!json_field:foo.bar} baz", 293 result: `foo false baz`, 294 }, 295 { 296 name: "json func 8", 297 input: []string{ 298 `{"foo":{"bar,1":false}}`, 299 }, 300 arg: "foo ${!json_field:foo.bar,1,} baz", 301 result: `foo false baz`, 302 }, 303 { 304 name: "json func 9", 305 input: []string{ 306 `{"foo":{"bar,1,":false}}`, 307 }, 308 arg: "foo ${!json_field:foo.bar,1,,} baz", 309 result: `foo false baz`, 310 }, 311 { 312 name: "json func 10", 313 input: []string{ 314 `{"foo":{"bar,1,test":false}}`, 315 }, 316 arg: "foo ${!json_field:foo.bar,1,test,} baz", 317 result: `foo false baz`, 318 }, 319 } 320 321 for _, test := range tests { 322 exp := test.result 323 parts := [][]byte{} 324 for _, input := range test.input { 325 parts = append(parts, []byte(input)) 326 } 327 act := string(ReplaceFunctionVariables( 328 message.New(parts), 329 []byte(test.arg), 330 )) 331 if act != exp { 332 t.Errorf("Wrong result for test '%v': %v != %v", test.name, act, exp) 333 } 334 } 335 } 336 337 func TestJSONFunctionFor(t *testing.T) { 338 type testCase struct { 339 name string 340 input []string 341 index int 342 arg string 343 result string 344 } 345 346 tests := []testCase{ 347 { 348 name: "json func 1", 349 input: []string{ 350 `{"foo":{"bar":"baz1"}}`, 351 `{"foo":{"bar":"baz2"}}`, 352 `{"foo":{"bar":"baz3"}}`, 353 }, 354 index: 0, 355 arg: "foo.bar: ${!json_field:foo.bar}", 356 result: "foo.bar: baz1", 357 }, 358 { 359 name: "json func 2", 360 input: []string{ 361 `{"foo":{"bar":"baz1"}}`, 362 `{"foo":{"bar":"baz2"}}`, 363 `{"foo":{"bar":"baz3"}}`, 364 }, 365 index: 1, 366 arg: "foo.bar: ${!json_field:foo.bar}", 367 result: "foo.bar: baz2", 368 }, 369 { 370 name: "json func 3", 371 input: []string{ 372 `{"foo":{"bar":"baz1"}}`, 373 `{"foo":{"bar":"baz2"}}`, 374 `{"foo":{"bar":"baz3"}}`, 375 }, 376 index: 2, 377 arg: "foo.bar: ${!json_field:foo.bar}", 378 result: "foo.bar: baz3", 379 }, 380 { 381 name: "json func 4", 382 input: []string{ 383 `{"foo":{"bar":"baz1"}}`, 384 `{"foo":{"bar":"baz2"}}`, 385 `{"foo":{"bar":"baz3"}}`, 386 }, 387 index: 3, 388 arg: "foo.bar: ${!json_field:foo.bar}", 389 result: "foo.bar: null", 390 }, 391 { 392 name: "json func 5", 393 input: []string{ 394 `{"foo":{"bar":"baz1"}}`, 395 `{"foo":{"bar":"baz2"}}`, 396 `{"foo":{"bar":"baz3"}}`, 397 }, 398 index: -1, 399 arg: "foo.bar: ${!json_field:foo.bar}", 400 result: "foo.bar: baz3", 401 }, 402 { 403 name: "json func 6", 404 input: []string{ 405 `{"foo":{"bar":"baz1"}}`, 406 `{"foo":{"bar":"baz2"}}`, 407 `{"foo":{"bar":"baz3"}}`, 408 }, 409 index: 1, 410 arg: "foo.bar: ${!json_field:foo.bar,0}", 411 result: "foo.bar: baz1", 412 }, 413 } 414 415 for _, test := range tests { 416 exp := test.result 417 parts := [][]byte{} 418 for _, input := range test.input { 419 parts = append(parts, []byte(input)) 420 } 421 act := string(ReplaceFunctionVariablesFor( 422 message.New(parts), 423 test.index, 424 []byte(test.arg), 425 )) 426 if act != exp { 427 t.Errorf("Wrong result for test '%v': %v != %v", test.name, act, exp) 428 } 429 } 430 } 431 432 func TestFunctionSwapping(t *testing.T) { 433 hostname, _ := os.Hostname() 434 435 exp := fmt.Sprintf("foo %v baz", hostname) 436 act := string(ReplaceFunctionVariables(nil, []byte("foo ${!hostname} baz"))) 437 if act != exp { 438 t.Errorf("Wrong result: %v != %v", act, exp) 439 } 440 441 exp = "foo ${!} baz" 442 act = string(ReplaceFunctionVariables(nil, []byte("foo ${!} baz"))) 443 if act != exp { 444 t.Errorf("Wrong result: %v != %v", act, exp) 445 } 446 447 exp = "foo ${!does_not_exist} baz" 448 act = string(ReplaceFunctionVariables(nil, []byte("foo ${!does_not_exist} baz"))) 449 if act != exp { 450 t.Errorf("Wrong result: %v != %v", act, exp) 451 } 452 453 now := time.Now() 454 tStamp := string(ReplaceFunctionVariables(nil, []byte("${!timestamp_unix_nano}"))) 455 456 nanoseconds, err := strconv.ParseInt(tStamp, 10, 64) 457 if err != nil { 458 t.Fatal(err) 459 } 460 tThen := time.Unix(0, nanoseconds) 461 462 if tThen.Sub(now).Seconds() > 5.0 { 463 t.Errorf("Timestamps too far out of sync: %v and %v", tThen, now) 464 } 465 466 now = time.Now() 467 tStamp = string(ReplaceFunctionVariables(nil, []byte("${!timestamp_unix}"))) 468 469 seconds, err := strconv.ParseInt(tStamp, 10, 64) 470 if err != nil { 471 t.Fatal(err) 472 } 473 tThen = time.Unix(seconds, 0) 474 475 if tThen.Sub(now).Seconds() > 5.0 { 476 t.Errorf("Timestamps too far out of sync: %v and %v", tThen, now) 477 } 478 479 now = time.Now() 480 tStamp = string(ReplaceFunctionVariables(nil, []byte("${!timestamp_unix:10}"))) 481 482 var secondsF float64 483 secondsF, err = strconv.ParseFloat(tStamp, 64) 484 if err != nil { 485 t.Fatal(err) 486 } 487 tThen = time.Unix(int64(secondsF), 0) 488 489 if tThen.Sub(now).Seconds() > 5.0 { 490 t.Errorf("Timestamps too far out of sync: %v and %v", tThen, now) 491 } 492 493 now = time.Now() 494 tStamp = string(ReplaceFunctionVariables(nil, []byte("${!timestamp}"))) 495 496 tThen, err = time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", tStamp) 497 if err != nil { 498 t.Fatal(err) 499 } 500 501 if tThen.Sub(now).Seconds() > 5.0 { 502 t.Errorf("Timestamps too far out of sync: %v and %v", tThen, now) 503 } 504 505 now = time.Now() 506 tStamp = string(ReplaceFunctionVariables(nil, []byte("${!timestamp_utc}"))) 507 508 tThen, err = time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", tStamp) 509 if err != nil { 510 t.Fatal(err) 511 } 512 513 if tThen.Sub(now).Seconds() > 5.0 { 514 t.Errorf("Timestamps too far out of sync: %v and %v", tThen, now) 515 } 516 if !strings.Contains(tStamp, "UTC") { 517 t.Errorf("Non-UTC timezone detected: %v", tStamp) 518 } 519 } 520 521 func TestFunctionEscape(t *testing.T) { 522 tests := map[string]string{ 523 "foo ${{!echo:bar}} bar": "foo ${!echo:bar} bar", 524 "foo ${{!notafunction}} bar": "foo ${!notafunction} bar", 525 "foo ${{{!notafunction}} bar": "foo ${{{!notafunction}} bar", 526 "foo ${!notafunction}} bar": "foo ${!notafunction}} bar", 527 } 528 529 for input, exp := range tests { 530 act := string(ReplaceFunctionVariables(nil, []byte(input))) 531 if exp != act { 532 t.Errorf("Wrong results for input (%v): %v != %v", input, act, exp) 533 } 534 } 535 } 536 537 func TestEchoFunction(t *testing.T) { 538 tests := map[string]string{ 539 "foo ${!echo:bar}": "foo bar", 540 "foo ${!echo}": "foo ", 541 "foo ${!echo:bar} ${!echo:baz}": "foo bar baz", 542 } 543 544 for input, exp := range tests { 545 act := string(ReplaceFunctionVariables(nil, []byte(input))) 546 if exp != act { 547 t.Errorf("Wrong results for input (%v): %v != %v", input, act, exp) 548 } 549 } 550 } 551 552 func TestCountersFunction(t *testing.T) { 553 tests := [][2]string{ 554 {"foo1: ${!count:foo}", "foo1: 1"}, 555 {"bar1: ${!count:bar}", "bar1: 1"}, 556 {"foo2: ${!count:foo} ${!count:foo}", "foo2: 2 3"}, 557 {"bar2: ${!count:bar} ${!count:bar}", "bar2: 2 3"}, 558 {"foo3: ${!count:foo} ${!count:foo}", "foo3: 4 5"}, 559 {"bar3: ${!count:bar} ${!count:bar}", "bar3: 4 5"}, 560 } 561 562 for _, test := range tests { 563 input := test[0] 564 exp := test[1] 565 act := string(ReplaceFunctionVariables(nil, []byte(input))) 566 if exp != act { 567 t.Errorf("Wrong results for input (%v): %v != %v", input, act, exp) 568 } 569 } 570 } 571 572 func TestCountersSharedFunction(t *testing.T) { 573 tests := []string{ 574 "foo1: ${!count:foo}", 575 "bar1: ${!count:bar}", 576 "foo2: ${!count:foo} ${!count:foo}", 577 "bar2: ${!count:bar} ${!count:bar}", 578 "foo3: ${!count:foo} ${!count:foo}", 579 "bar3: ${!count:bar} ${!count:bar}", 580 } 581 582 startChan := make(chan struct{}) 583 wg := sync.WaitGroup{} 584 for i := 0; i < 10; i++ { 585 wg.Add(1) 586 go func() { 587 defer wg.Done() 588 <-startChan 589 for _, input := range tests { 590 act := string(ReplaceFunctionVariables(nil, []byte(input))) 591 if act == input { 592 t.Errorf("Unchanged input: %v", act) 593 } 594 } 595 }() 596 } 597 close(startChan) 598 wg.Wait() 599 } 600 601 func TestUUIDV4Function(t *testing.T) { 602 results := map[string]struct{}{} 603 604 for i := 0; i < 100; i++ { 605 result := string(ReplaceFunctionVariables(nil, []byte(`${!uuid_v4}`))) 606 if _, exists := results[result]; exists { 607 t.Errorf("Duplicate UUID generated: %v", result) 608 } 609 results[result] = struct{}{} 610 } 611 }