github.com/Jeffail/benthos/v3@v3.65.0/internal/docs/config_test.go (about)

     1  package docs_test
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/Jeffail/benthos/v3/internal/docs"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  	"gopkg.in/yaml.v3"
    11  )
    12  
    13  func TestInference(t *testing.T) {
    14  	docsProv := docs.NewMappedDocsProvider()
    15  	for _, t := range docs.Types() {
    16  		docsProv.RegisterDocs(docs.ComponentSpec{
    17  			Name: fmt.Sprintf("testfoo%v", string(t)),
    18  			Type: t,
    19  		})
    20  		docsProv.RegisterDocs(docs.ComponentSpec{
    21  			Name: fmt.Sprintf("testbar%v", string(t)),
    22  			Type: t,
    23  		})
    24  	}
    25  
    26  	type testCase struct {
    27  		inputType    docs.Type
    28  		inputConf    interface{}
    29  		inputDefault string
    30  
    31  		res string
    32  		err string
    33  	}
    34  
    35  	tests := []testCase{
    36  		{
    37  			inputType: docs.TypeInput,
    38  			inputConf: map[string]interface{}{
    39  				"processors": "yep",
    40  			},
    41  			inputDefault: "testfooinput",
    42  			res:          "testfooinput",
    43  		},
    44  		{
    45  			inputType: docs.TypeOutput,
    46  			inputConf: map[string]interface{}{
    47  				"foo":        "yep",
    48  				"bar":        "yep",
    49  				"processors": "yep",
    50  			},
    51  			err: "unable to infer output type, candidates were: [bar foo]",
    52  		},
    53  		{
    54  			inputType: docs.TypeInput,
    55  			inputConf: map[string]interface{}{
    56  				"foo":        "yep",
    57  				"bar":        "yep",
    58  				"processors": "yep",
    59  			},
    60  			err: "unable to infer input type, candidates were: [bar foo]",
    61  		},
    62  		{
    63  			inputType: docs.TypeTracer,
    64  			inputConf: map[string]interface{}{
    65  				"testbartracer": "baz",
    66  				"testbarbuffer": "baz",
    67  			},
    68  			res: "testbartracer",
    69  		},
    70  		{
    71  			inputType: docs.TypeRateLimit,
    72  			inputConf: map[string]interface{}{
    73  				"testbarrate_limit": "baz",
    74  				"testbarbuffer":     "baz",
    75  			},
    76  			res: "testbarrate_limit",
    77  		},
    78  		{
    79  			inputType: docs.TypeProcessor,
    80  			inputConf: map[string]interface{}{
    81  				"testbarprocessor": "baz",
    82  				"testbarbuffer":    "baz",
    83  			},
    84  			res: "testbarprocessor",
    85  		},
    86  		{
    87  			inputType: docs.TypeOutput,
    88  			inputConf: map[string]interface{}{
    89  				"testbaroutput": "baz",
    90  				"testbarbuffer": "baz",
    91  			},
    92  			res: "testbaroutput",
    93  		},
    94  		{
    95  			inputType: docs.TypeMetrics,
    96  			inputConf: map[string]interface{}{
    97  				"testfoometrics": "baz",
    98  				"testbarbuffer":  "baz",
    99  			},
   100  			res: "testfoometrics",
   101  		},
   102  		{
   103  			inputType: docs.TypeInput,
   104  			inputConf: map[string]interface{}{
   105  				"testfooinput":  "baz",
   106  				"testbarbuffer": "baz",
   107  			},
   108  			res: "testfooinput",
   109  		},
   110  		{
   111  			inputType: docs.TypeCache,
   112  			inputConf: map[string]interface{}{
   113  				"testfoocache":  "baz",
   114  				"testbarbuffer": "baz",
   115  			},
   116  			res: "testfoocache",
   117  		},
   118  		{
   119  			inputType: docs.TypeBuffer,
   120  			inputConf: map[string]interface{}{
   121  				"testfoobuffer": "baz",
   122  				"testbarbuffer": "baz",
   123  			},
   124  			err: "unable to infer buffer type, multiple candidates 'testbarbuffer' and 'testfoobuffer'",
   125  		},
   126  		{
   127  			inputType: docs.TypeBuffer,
   128  			inputConf: map[string]interface{}{
   129  				"testfoobuffer": "baz",
   130  			},
   131  			res: "testfoobuffer",
   132  		},
   133  		{
   134  			inputType: docs.TypeBuffer,
   135  			inputConf: map[string]interface{}{
   136  				"type":   "testfoobuffer",
   137  				"foobar": "baz",
   138  			},
   139  			res: "testfoobuffer",
   140  		},
   141  		{
   142  			inputType: docs.TypeBuffer,
   143  			inputConf: map[string]interface{}{
   144  				"type":   "notreal",
   145  				"foobar": "baz",
   146  			},
   147  			err: "buffer type 'notreal' was not recognised",
   148  		},
   149  		{
   150  			inputType: docs.TypeBuffer,
   151  			err:       "invalid config value <nil>, expected object",
   152  		},
   153  	}
   154  
   155  	for i, test := range tests {
   156  		res, spec, err := docs.GetInferenceCandidate(docsProv, test.inputType, test.inputDefault, test.inputConf)
   157  		if len(test.err) > 0 {
   158  			assert.EqualError(t, err, test.err, "test: %v", i)
   159  		} else {
   160  			assert.Equal(t, test.res, spec.Name)
   161  			assert.Equal(t, test.inputType, spec.Type)
   162  			assert.NoError(t, err)
   163  			assert.Equal(t, test.res, res, "test: %v", i)
   164  		}
   165  
   166  		var node yaml.Node
   167  		require.NoError(t, node.Encode(test.inputConf))
   168  		res, spec, err = docs.GetInferenceCandidateFromYAML(docsProv, test.inputType, test.inputDefault, &node)
   169  		if len(test.err) > 0 {
   170  			assert.Error(t, err)
   171  		} else {
   172  			assert.Equal(t, test.res, spec.Name)
   173  			assert.Equal(t, test.inputType, spec.Type)
   174  			assert.NoError(t, err)
   175  			assert.Equal(t, test.res, res, "test: %v", i)
   176  		}
   177  	}
   178  }
   179  
   180  func TestSanitation(t *testing.T) {
   181  	for _, t := range docs.Types() {
   182  		docs.RegisterDocs(docs.ComponentSpec{
   183  			Name: fmt.Sprintf("testsanitfoo%v", string(t)),
   184  			Type: t,
   185  			Config: docs.FieldComponent().WithChildren(
   186  				docs.FieldCommon("foo1", ""),
   187  				docs.FieldAdvanced("foo2", ""),
   188  				docs.FieldCommon("foo3", "").HasType(docs.FieldTypeProcessor),
   189  				docs.FieldAdvanced("foo4", "").Array().HasType(docs.FieldTypeProcessor),
   190  				docs.FieldCommon("foo5", "").Map().HasType(docs.FieldTypeProcessor),
   191  				docs.FieldDeprecated("foo6"),
   192  			),
   193  		})
   194  		docs.RegisterDocs(docs.ComponentSpec{
   195  			Name: fmt.Sprintf("testsanitbar%v", string(t)),
   196  			Type: t,
   197  			Config: docs.FieldComponent().Array().WithChildren(
   198  				docs.FieldCommon("bar1", ""),
   199  				docs.FieldAdvanced("bar2", ""),
   200  				docs.FieldCommon("bar3", "").HasType(docs.FieldTypeProcessor),
   201  			),
   202  		})
   203  		docs.RegisterDocs(docs.ComponentSpec{
   204  			Name: fmt.Sprintf("testsanitbaz%v", string(t)),
   205  			Type: t,
   206  			Config: docs.FieldComponent().Map().WithChildren(
   207  				docs.FieldCommon("baz1", ""),
   208  				docs.FieldAdvanced("baz2", ""),
   209  				docs.FieldCommon("baz3", "").HasType(docs.FieldTypeProcessor),
   210  			),
   211  		})
   212  	}
   213  
   214  	type testCase struct {
   215  		name        string
   216  		inputType   docs.Type
   217  		inputConf   interface{}
   218  		inputFilter func(f docs.FieldSpec) bool
   219  
   220  		res interface{}
   221  		err string
   222  	}
   223  
   224  	tests := []testCase{
   225  		{
   226  			name:      "input with processors",
   227  			inputType: docs.TypeInput,
   228  			inputConf: map[string]interface{}{
   229  				"testsanitfooinput": map[string]interface{}{
   230  					"foo1": "simple field",
   231  					"foo2": "advanced field",
   232  					"foo6": "deprecated field",
   233  				},
   234  				"someotherinput": map[string]interface{}{
   235  					"ignore": "me please",
   236  				},
   237  				"processors": []interface{}{
   238  					map[string]interface{}{
   239  						"testsanitbarprocessor": map[string]interface{}{
   240  							"bar1": "bar value",
   241  							"bar5": "undocumented field",
   242  						},
   243  						"someotherprocessor": map[string]interface{}{
   244  							"ignore": "me please",
   245  						},
   246  					},
   247  				},
   248  			},
   249  			res: map[string]interface{}{
   250  				"testsanitfooinput": map[string]interface{}{
   251  					"foo1": "simple field",
   252  					"foo2": "advanced field",
   253  					"foo6": "deprecated field",
   254  				},
   255  				"processors": []interface{}{
   256  					map[string]interface{}{
   257  						"testsanitbarprocessor": map[string]interface{}{
   258  							"bar1": "bar value",
   259  							"bar5": "undocumented field",
   260  						},
   261  					},
   262  				},
   263  			},
   264  		},
   265  		{
   266  			name:      "output array with nested map processor",
   267  			inputType: docs.TypeOutput,
   268  			inputConf: map[string]interface{}{
   269  				"testsanitbaroutput": []interface{}{
   270  					map[string]interface{}{
   271  						"bar1": "simple field",
   272  						"bar3": map[string]interface{}{
   273  							"testsanitbazprocessor": map[string]interface{}{
   274  								"customkey1": map[string]interface{}{
   275  									"baz1": "simple field",
   276  								},
   277  							},
   278  							"someotherprocessor": map[string]interface{}{
   279  								"ignore": "me please",
   280  							},
   281  						},
   282  					},
   283  					map[string]interface{}{
   284  						"bar2": "advanced field",
   285  					},
   286  				},
   287  			},
   288  			res: map[string]interface{}{
   289  				"testsanitbaroutput": []interface{}{
   290  					map[string]interface{}{
   291  						"bar1": "simple field",
   292  						"bar3": map[string]interface{}{
   293  							"testsanitbazprocessor": map[string]interface{}{
   294  								"customkey1": map[string]interface{}{
   295  									"baz1": "simple field",
   296  								},
   297  							},
   298  						},
   299  					},
   300  					map[string]interface{}{
   301  						"bar2": "advanced field",
   302  					},
   303  				},
   304  			},
   305  		},
   306  		{
   307  			name:      "metrics map with nested map processor",
   308  			inputType: docs.TypeMetrics,
   309  			inputConf: map[string]interface{}{
   310  				"testsanitbazmetrics": map[string]interface{}{
   311  					"customkey1": map[string]interface{}{
   312  						"baz1": "simple field",
   313  						"baz3": map[string]interface{}{
   314  							"testsanitbazprocessor": map[string]interface{}{
   315  								"customkey1": map[string]interface{}{
   316  									"baz1": "simple field",
   317  								},
   318  							},
   319  							"someotherprocessor": map[string]interface{}{
   320  								"ignore": "me please",
   321  							},
   322  						},
   323  					},
   324  					"customkey2": map[string]interface{}{
   325  						"baz2": "advanced field",
   326  					},
   327  				},
   328  			},
   329  			res: map[string]interface{}{
   330  				"testsanitbazmetrics": map[string]interface{}{
   331  					"customkey1": map[string]interface{}{
   332  						"baz1": "simple field",
   333  						"baz3": map[string]interface{}{
   334  							"testsanitbazprocessor": map[string]interface{}{
   335  								"customkey1": map[string]interface{}{
   336  									"baz1": "simple field",
   337  								},
   338  							},
   339  						},
   340  					},
   341  					"customkey2": map[string]interface{}{
   342  						"baz2": "advanced field",
   343  					},
   344  				},
   345  			},
   346  		},
   347  		{
   348  			name:      "ratelimit with array field processor",
   349  			inputType: docs.TypeRateLimit,
   350  			inputConf: map[string]interface{}{
   351  				"testsanitfoorate_limit": map[string]interface{}{
   352  					"foo1": "simple field",
   353  					"foo4": []interface{}{
   354  						map[string]interface{}{
   355  							"testsanitbazprocessor": map[string]interface{}{
   356  								"customkey1": map[string]interface{}{
   357  									"baz1": "simple field",
   358  								},
   359  							},
   360  							"someotherprocessor": map[string]interface{}{
   361  								"ignore": "me please",
   362  							},
   363  						},
   364  					},
   365  				},
   366  			},
   367  			res: map[string]interface{}{
   368  				"testsanitfoorate_limit": map[string]interface{}{
   369  					"foo1": "simple field",
   370  					"foo4": []interface{}{
   371  						map[string]interface{}{
   372  							"testsanitbazprocessor": map[string]interface{}{
   373  								"customkey1": map[string]interface{}{
   374  									"baz1": "simple field",
   375  								},
   376  							},
   377  						},
   378  					},
   379  				},
   380  			},
   381  		},
   382  		{
   383  			name:      "ratelimit with map field processor",
   384  			inputType: docs.TypeRateLimit,
   385  			inputConf: map[string]interface{}{
   386  				"testsanitfoorate_limit": map[string]interface{}{
   387  					"foo1": "simple field",
   388  					"foo5": map[string]interface{}{
   389  						"customkey1": map[string]interface{}{
   390  							"testsanitbazprocessor": map[string]interface{}{
   391  								"customkey1": map[string]interface{}{
   392  									"baz1": "simple field",
   393  								},
   394  							},
   395  							"someotherprocessor": map[string]interface{}{
   396  								"ignore": "me please",
   397  							},
   398  						},
   399  					},
   400  				},
   401  			},
   402  			res: map[string]interface{}{
   403  				"testsanitfoorate_limit": map[string]interface{}{
   404  					"foo1": "simple field",
   405  					"foo5": map[string]interface{}{
   406  						"customkey1": map[string]interface{}{
   407  							"testsanitbazprocessor": map[string]interface{}{
   408  								"customkey1": map[string]interface{}{
   409  									"baz1": "simple field",
   410  								},
   411  							},
   412  						},
   413  					},
   414  				},
   415  			},
   416  		},
   417  		{
   418  			name:        "input with processors no deprecated",
   419  			inputType:   docs.TypeInput,
   420  			inputFilter: docs.ShouldDropDeprecated(true),
   421  			inputConf: map[string]interface{}{
   422  				"testsanitfooinput": map[string]interface{}{
   423  					"foo1": "simple field",
   424  					"foo2": "advanced field",
   425  					"foo6": "deprecated field",
   426  				},
   427  				"someotherinput": map[string]interface{}{
   428  					"ignore": "me please",
   429  				},
   430  				"processors": []interface{}{
   431  					map[string]interface{}{
   432  						"testsanitfooprocessor": map[string]interface{}{
   433  							"foo1": "simple field",
   434  							"foo2": "advanced field",
   435  							"foo6": "deprecated field",
   436  						},
   437  						"someotherprocessor": map[string]interface{}{
   438  							"ignore": "me please",
   439  						},
   440  					},
   441  				},
   442  			},
   443  			res: map[string]interface{}{
   444  				"testsanitfooinput": map[string]interface{}{
   445  					"foo1": "simple field",
   446  					"foo2": "advanced field",
   447  				},
   448  				"processors": []interface{}{
   449  					map[string]interface{}{
   450  						"testsanitfooprocessor": map[string]interface{}{
   451  							"foo1": "simple field",
   452  							"foo2": "advanced field",
   453  						},
   454  					},
   455  				},
   456  			},
   457  		},
   458  	}
   459  
   460  	for _, test := range tests {
   461  		test := test
   462  		t.Run(test.name, func(t *testing.T) {
   463  			err := docs.SanitiseComponentConfig(test.inputType, test.inputConf, test.inputFilter)
   464  			if len(test.err) > 0 {
   465  				assert.EqualError(t, err, test.err)
   466  			} else {
   467  				assert.NoError(t, err)
   468  				assert.Equal(t, test.res, test.inputConf)
   469  			}
   470  		})
   471  	}
   472  }