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  }