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 }