github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/builtin/parser/severity_test.go (about)

     1  package parser
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/observiq/carbon/entry"
     9  	"github.com/observiq/carbon/operator"
    10  	"github.com/observiq/carbon/operator/helper"
    11  	"github.com/observiq/carbon/testutil"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  type severityTestCase struct {
    17  	name       string
    18  	sample     interface{}
    19  	mappingSet string
    20  	mapping    map[interface{}]interface{}
    21  	buildErr   bool
    22  	parseErr   bool
    23  	expected   entry.Severity
    24  }
    25  
    26  func TestSeverityParser(t *testing.T) {
    27  
    28  	testCases := []severityTestCase{
    29  		{
    30  			name:     "unknown",
    31  			sample:   "blah",
    32  			mapping:  nil,
    33  			expected: entry.Default,
    34  		},
    35  		{
    36  			name:     "error",
    37  			sample:   "error",
    38  			mapping:  nil,
    39  			expected: entry.Error,
    40  		},
    41  		{
    42  			name:     "error-capitalized",
    43  			sample:   "Error",
    44  			mapping:  nil,
    45  			expected: entry.Error,
    46  		},
    47  		{
    48  			name:     "error-all-caps",
    49  			sample:   "ERROR",
    50  			mapping:  nil,
    51  			expected: entry.Error,
    52  		},
    53  		{
    54  			name:     "custom-string",
    55  			sample:   "NOOOOOOO",
    56  			mapping:  map[interface{}]interface{}{"error": "NOOOOOOO"},
    57  			expected: entry.Error,
    58  		},
    59  		{
    60  			name:     "custom-string-caps-key",
    61  			sample:   "NOOOOOOO",
    62  			mapping:  map[interface{}]interface{}{"ErRoR": "NOOOOOOO"},
    63  			expected: entry.Error,
    64  		},
    65  		{
    66  			name:     "custom-int",
    67  			sample:   1234,
    68  			mapping:  map[interface{}]interface{}{"error": 1234},
    69  			expected: entry.Error,
    70  		},
    71  		{
    72  			name:     "mixed-list-string",
    73  			sample:   "ThiS Is BaD",
    74  			mapping:  map[interface{}]interface{}{"error": []interface{}{"NOOOOOOO", "this is bad", 1234}},
    75  			expected: entry.Error,
    76  		},
    77  		{
    78  			name:     "mixed-list-int",
    79  			sample:   1234,
    80  			mapping:  map[interface{}]interface{}{"error": []interface{}{"NOOOOOOO", "this is bad", 1234}},
    81  			expected: entry.Error,
    82  		},
    83  		{
    84  			name:     "overload-int-key",
    85  			sample:   "E",
    86  			mapping:  map[interface{}]interface{}{60: "E"},
    87  			expected: entry.Error, // 60
    88  		},
    89  		{
    90  			name:     "overload-native",
    91  			sample:   "E",
    92  			mapping:  map[interface{}]interface{}{int(entry.Error): "E"},
    93  			expected: entry.Error, // 60
    94  		},
    95  		{
    96  			name:     "custom-level",
    97  			sample:   "weird",
    98  			mapping:  map[interface{}]interface{}{12: "weird"},
    99  			expected: 12,
   100  		},
   101  		{
   102  			name:     "custom-level-list",
   103  			sample:   "hey!",
   104  			mapping:  map[interface{}]interface{}{16: []interface{}{"hey!", 1234}},
   105  			expected: 16,
   106  		},
   107  		{
   108  			name:     "custom-level-list-unfound",
   109  			sample:   "not-in-the-list-but-thats-ok",
   110  			mapping:  map[interface{}]interface{}{16: []interface{}{"hey!", 1234}},
   111  			expected: entry.Default,
   112  		},
   113  		{
   114  			name:     "custom-level-unbuildable",
   115  			sample:   "not-in-the-list-but-thats-ok",
   116  			mapping:  map[interface{}]interface{}{16: []interface{}{"hey!", 1234, 12.34}},
   117  			buildErr: true,
   118  		},
   119  		{
   120  			name:     "custom-level-list-unparseable",
   121  			sample:   12.34,
   122  			mapping:  map[interface{}]interface{}{16: []interface{}{"hey!", 1234}},
   123  			parseErr: true,
   124  		},
   125  		{
   126  			name:     "in-range",
   127  			sample:   123,
   128  			mapping:  map[interface{}]interface{}{"error": map[interface{}]interface{}{"min": 120, "max": 125}},
   129  			expected: entry.Error,
   130  		},
   131  		{
   132  			name:     "in-range-min",
   133  			sample:   120,
   134  			mapping:  map[interface{}]interface{}{"error": map[interface{}]interface{}{"min": 120, "max": 125}},
   135  			expected: entry.Error,
   136  		},
   137  		{
   138  			name:     "in-range-max",
   139  			sample:   125,
   140  			mapping:  map[interface{}]interface{}{"error": map[interface{}]interface{}{"min": 120, "max": 125}},
   141  			expected: entry.Error,
   142  		},
   143  		{
   144  			name:     "out-of-range-min-minus",
   145  			sample:   119,
   146  			mapping:  map[interface{}]interface{}{"error": map[interface{}]interface{}{"min": 120, "max": 125}},
   147  			expected: entry.Default,
   148  		},
   149  		{
   150  			name:     "out-of-range-max-plus",
   151  			sample:   126,
   152  			mapping:  map[interface{}]interface{}{"error": map[interface{}]interface{}{"min": 120, "max": 125}},
   153  			expected: entry.Default,
   154  		},
   155  		{
   156  			name:     "range-out-of-order",
   157  			sample:   123,
   158  			mapping:  map[interface{}]interface{}{"error": map[interface{}]interface{}{"min": 125, "max": 120}},
   159  			expected: entry.Error,
   160  		},
   161  		{
   162  			name:     "Http2xx-hit",
   163  			sample:   201,
   164  			mapping:  map[interface{}]interface{}{"error": "2xx"},
   165  			expected: entry.Error,
   166  		},
   167  		{
   168  			name:     "Http2xx-miss",
   169  			sample:   301,
   170  			mapping:  map[interface{}]interface{}{"error": "2xx"},
   171  			expected: entry.Default,
   172  		},
   173  		{
   174  			name:     "Http3xx-hit",
   175  			sample:   301,
   176  			mapping:  map[interface{}]interface{}{"error": "3xx"},
   177  			expected: entry.Error,
   178  		},
   179  		{
   180  			name:     "Http4xx-hit",
   181  			sample:   "404",
   182  			mapping:  map[interface{}]interface{}{"error": "4xx"},
   183  			expected: entry.Error,
   184  		},
   185  		{
   186  			name:     "Http5xx-hit",
   187  			sample:   555,
   188  			mapping:  map[interface{}]interface{}{"error": "5xx"},
   189  			expected: entry.Error,
   190  		},
   191  		{
   192  			name:     "Http-All",
   193  			sample:   "301",
   194  			mapping:  map[interface{}]interface{}{20: "2xx", 30: "3xx", 40: "4xx", 50: "5xx"},
   195  			expected: 30,
   196  		},
   197  		{
   198  			name:   "all-the-things-midrange",
   199  			sample: 1234,
   200  			mapping: map[interface{}]interface{}{
   201  				"30":             "3xx",
   202  				int(entry.Error): "4xx",
   203  				"critical":       "5xx",
   204  				int(entry.Trace): []interface{}{
   205  					"ttttttracer",
   206  					[]byte{100, 100, 100},
   207  					map[interface{}]interface{}{"min": 1111, "max": 1234},
   208  				},
   209  				77: "",
   210  			},
   211  			expected: entry.Trace,
   212  		},
   213  		{
   214  			name:   "all-the-things-bytes",
   215  			sample: []byte{100, 100, 100},
   216  			mapping: map[interface{}]interface{}{
   217  				"30":             "3xx",
   218  				int(entry.Error): "4xx",
   219  				"critical":       "5xx",
   220  				int(entry.Trace): []interface{}{
   221  					"ttttttracer",
   222  					[]byte{100, 100, 100},
   223  					map[interface{}]interface{}{"min": 1111, "max": 1234},
   224  				},
   225  				77: "",
   226  			},
   227  			expected: entry.Trace,
   228  		},
   229  		{
   230  			name:   "all-the-things-empty",
   231  			sample: "",
   232  			mapping: map[interface{}]interface{}{
   233  				"30":             "3xx",
   234  				int(entry.Error): "4xx",
   235  				"critical":       "5xx",
   236  				int(entry.Trace): []interface{}{
   237  					"ttttttracer",
   238  					[]byte{100, 100, 100},
   239  					map[interface{}]interface{}{"min": 1111, "max": 1234},
   240  				},
   241  				77: "",
   242  			},
   243  			expected: 77,
   244  		},
   245  		{
   246  			name:   "all-the-things-3xx",
   247  			sample: "399",
   248  			mapping: map[interface{}]interface{}{
   249  				"30":             "3xx",
   250  				int(entry.Error): "4xx",
   251  				"critical":       "5xx",
   252  				int(entry.Trace): []interface{}{
   253  					"ttttttracer",
   254  					[]byte{100, 100, 100},
   255  					map[interface{}]interface{}{"min": 1111, "max": 1234},
   256  				},
   257  				77: "",
   258  			},
   259  			expected: 30,
   260  		},
   261  		{
   262  			name:   "all-the-things-miss",
   263  			sample: "miss",
   264  			mapping: map[interface{}]interface{}{
   265  				"30":             "3xx",
   266  				int(entry.Error): "4xx",
   267  				"critical":       "5xx",
   268  				int(entry.Trace): []interface{}{
   269  					"ttttttracer",
   270  					[]byte{100, 100, 100},
   271  					map[interface{}]interface{}{"min": 1111, "max": 2000},
   272  				},
   273  				77: "",
   274  			},
   275  			expected: entry.Default,
   276  		},
   277  		{
   278  			name:       "base-mapping-none",
   279  			sample:     "error",
   280  			mappingSet: "none",
   281  			mapping:    nil,
   282  			expected:   entry.Default, // not error
   283  		},
   284  	}
   285  
   286  	rootField := entry.NewRecordField()
   287  	someField := entry.NewRecordField("some_field")
   288  
   289  	for _, tc := range testCases {
   290  		t.Run(tc.name, func(t *testing.T) {
   291  			rootCfg := parseSeverityTestConfig(rootField, tc.mappingSet, tc.mapping)
   292  			rootEntry := makeTestEntry(rootField, tc.sample)
   293  			t.Run("root", runSeverityParseTest(t, rootCfg, rootEntry, tc.buildErr, tc.parseErr, tc.expected))
   294  
   295  			nonRootCfg := parseSeverityTestConfig(someField, tc.mappingSet, tc.mapping)
   296  			nonRootEntry := makeTestEntry(someField, tc.sample)
   297  			t.Run("non-root", runSeverityParseTest(t, nonRootCfg, nonRootEntry, tc.buildErr, tc.parseErr, tc.expected))
   298  		})
   299  	}
   300  }
   301  
   302  func runSeverityParseTest(t *testing.T, cfg *SeverityParserConfig, ent *entry.Entry, buildErr bool, parseErr bool, expected entry.Severity) func(*testing.T) {
   303  
   304  	return func(t *testing.T) {
   305  		buildContext := testutil.NewBuildContext(t)
   306  
   307  		severityOperator, err := cfg.Build(buildContext)
   308  		if buildErr {
   309  			require.Error(t, err, "expected error when configuring operator")
   310  			return
   311  		}
   312  		require.NoError(t, err, "unexpected error when configuring operator")
   313  
   314  		mockOutput := &testutil.Operator{}
   315  		resultChan := make(chan *entry.Entry, 1)
   316  		mockOutput.On("Process", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
   317  			resultChan <- args.Get(1).(*entry.Entry)
   318  		}).Return(nil)
   319  
   320  		severityParser := severityOperator.(*SeverityParserOperator)
   321  		severityParser.OutputOperators = []operator.Operator{mockOutput}
   322  
   323  		err = severityParser.Process(context.Background(), ent)
   324  		if parseErr {
   325  			require.Error(t, err, "expected error when parsing sample")
   326  			return
   327  		}
   328  		require.NoError(t, err)
   329  
   330  		select {
   331  		case e := <-resultChan:
   332  			require.Equal(t, expected, e.Severity)
   333  		case <-time.After(time.Second):
   334  			require.FailNow(t, "Timed out waiting for entry to be processed")
   335  		}
   336  	}
   337  }
   338  
   339  func parseSeverityTestConfig(parseFrom entry.Field, preset string, mapping map[interface{}]interface{}) *SeverityParserConfig {
   340  	cfg := NewSeverityParserConfig("test_operator_id")
   341  	cfg.OutputIDs = []string{"output1"}
   342  	cfg.SeverityParserConfig = helper.SeverityParserConfig{
   343  		ParseFrom: &parseFrom,
   344  		Preset:    preset,
   345  		Mapping:   mapping,
   346  	}
   347  	return cfg
   348  }