github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/builtin/transformer/restructure_test.go (about) 1 package transformer 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 "testing" 8 "time" 9 10 "github.com/antonmedv/expr" 11 "github.com/antonmedv/expr/vm" 12 "github.com/observiq/carbon/entry" 13 "github.com/observiq/carbon/operator" 14 "github.com/observiq/carbon/operator/helper" 15 "github.com/observiq/carbon/testutil" 16 "github.com/stretchr/testify/mock" 17 "github.com/stretchr/testify/require" 18 "go.uber.org/zap" 19 yaml "gopkg.in/yaml.v2" 20 ) 21 22 func NewFakeRestructureOperator() (*RestructureOperator, *testutil.Operator) { 23 mock := testutil.Operator{} 24 logger, _ := zap.NewProduction() 25 return &RestructureOperator{ 26 TransformerOperator: helper.TransformerOperator{ 27 WriterOperator: helper.WriterOperator{ 28 BasicOperator: helper.BasicOperator{ 29 OperatorID: "test", 30 OperatorType: "restructure", 31 SugaredLogger: logger.Sugar(), 32 }, 33 OutputOperators: []operator.Operator{&mock}, 34 }, 35 }, 36 }, &mock 37 } 38 39 func TestRestructureOperator(t *testing.T) { 40 os.Setenv("TEST_RESTRUCTURE_PLUGIN_ENV", "foo") 41 defer os.Unsetenv("TEST_RESTRUCTURE_PLUGIN_ENV") 42 43 newTestEntry := func() *entry.Entry { 44 e := entry.New() 45 e.Timestamp = time.Unix(1586632809, 0) 46 e.Record = map[string]interface{}{ 47 "key": "val", 48 "nested": map[string]interface{}{ 49 "nestedkey": "nestedval", 50 }, 51 } 52 return e 53 } 54 55 cases := []struct { 56 name string 57 ops []Op 58 input *entry.Entry 59 output *entry.Entry 60 }{ 61 { 62 name: "Nothing", 63 input: newTestEntry(), 64 output: newTestEntry(), 65 }, 66 { 67 name: "AddValue", 68 ops: []Op{ 69 { 70 &OpAdd{ 71 Field: entry.NewRecordField("new"), 72 Value: "message", 73 }, 74 }, 75 }, 76 input: newTestEntry(), 77 output: func() *entry.Entry { 78 e := newTestEntry() 79 e.Record.(map[string]interface{})["new"] = "message" 80 return e 81 }(), 82 }, 83 { 84 name: "AddValueExpr", 85 ops: []Op{ 86 { 87 &OpAdd{ 88 Field: entry.NewRecordField("new"), 89 program: func() *vm.Program { 90 vm, err := expr.Compile(`$.key + "_suffix"`) 91 require.NoError(t, err) 92 return vm 93 }(), 94 }, 95 }, 96 }, 97 input: newTestEntry(), 98 output: func() *entry.Entry { 99 e := newTestEntry() 100 e.Record.(map[string]interface{})["new"] = "val_suffix" 101 return e 102 }(), 103 }, 104 { 105 name: "AddValueExprEnv", 106 ops: []Op{ 107 { 108 &OpAdd{ 109 Field: entry.NewRecordField("new"), 110 program: func() *vm.Program { 111 vm, err := expr.Compile(`env("TEST_RESTRUCTURE_PLUGIN_ENV")`) 112 require.NoError(t, err) 113 return vm 114 }(), 115 }, 116 }, 117 }, 118 input: newTestEntry(), 119 output: func() *entry.Entry { 120 e := newTestEntry() 121 e.Record.(map[string]interface{})["new"] = "foo" 122 return e 123 }(), 124 }, 125 { 126 name: "Remove", 127 ops: []Op{ 128 { 129 &OpRemove{entry.NewRecordField("nested")}, 130 }, 131 }, 132 input: newTestEntry(), 133 output: func() *entry.Entry { 134 e := newTestEntry() 135 e.Record = map[string]interface{}{ 136 "key": "val", 137 } 138 return e 139 }(), 140 }, 141 { 142 name: "Retain", 143 ops: []Op{ 144 { 145 &OpRetain{[]entry.Field{entry.NewRecordField("key")}}, 146 }, 147 }, 148 input: newTestEntry(), 149 output: func() *entry.Entry { 150 e := newTestEntry() 151 e.Record = map[string]interface{}{ 152 "key": "val", 153 } 154 return e 155 }(), 156 }, 157 { 158 name: "Move", 159 ops: []Op{ 160 { 161 &OpMove{ 162 From: entry.NewRecordField("key"), 163 To: entry.NewRecordField("newkey"), 164 }, 165 }, 166 }, 167 input: newTestEntry(), 168 output: func() *entry.Entry { 169 e := newTestEntry() 170 e.Record = map[string]interface{}{ 171 "newkey": "val", 172 "nested": map[string]interface{}{ 173 "nestedkey": "nestedval", 174 }, 175 } 176 return e 177 }(), 178 }, 179 { 180 name: "Flatten", 181 ops: []Op{ 182 { 183 &OpFlatten{ 184 Field: entry.RecordField{ 185 Keys: []string{"nested"}, 186 }, 187 }, 188 }, 189 }, 190 input: newTestEntry(), 191 output: func() *entry.Entry { 192 e := newTestEntry() 193 e.Record = map[string]interface{}{ 194 "key": "val", 195 "nestedkey": "nestedval", 196 } 197 return e 198 }(), 199 }, 200 } 201 202 for _, tc := range cases { 203 t.Run(tc.name, func(t *testing.T) { 204 205 operator, mockOutput := NewFakeRestructureOperator() 206 operator.ops = tc.ops 207 208 mockOutput.On("Process", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { 209 require.Equal(t, tc.output, args[1].(*entry.Entry)) 210 }).Return(nil) 211 212 err := operator.Process(context.Background(), tc.input) 213 require.NoError(t, err) 214 }) 215 } 216 } 217 218 func TestRestructureSerializeRoundtrip(t *testing.T) { 219 cases := []struct { 220 name string 221 op Op 222 }{ 223 { 224 name: "AddValue", 225 op: Op{&OpAdd{ 226 Field: entry.NewRecordField("new"), 227 Value: "message", 228 }}, 229 }, 230 { 231 name: "AddValueExpr", 232 op: Op{&OpAdd{ 233 Field: entry.NewRecordField("new"), 234 ValueExpr: func() *string { 235 s := `$.key + "_suffix"` 236 return &s 237 }(), 238 program: func() *vm.Program { 239 vm, err := expr.Compile(`$.key + "_suffix"`) 240 require.NoError(t, err) 241 return vm 242 }(), 243 }}, 244 }, 245 { 246 name: "Remove", 247 op: Op{&OpRemove{entry.NewRecordField("nested")}}, 248 }, 249 { 250 name: "Retain", 251 op: Op{&OpRetain{[]entry.Field{entry.NewRecordField("key")}}}, 252 }, 253 { 254 name: "Move", 255 op: Op{&OpMove{ 256 From: entry.NewRecordField("key"), 257 To: entry.NewRecordField("newkey"), 258 }}, 259 }, 260 { 261 name: "Flatten", 262 op: Op{&OpFlatten{ 263 Field: entry.RecordField{ 264 Keys: []string{"nested"}, 265 }, 266 }}, 267 }, 268 } 269 270 for _, tc := range cases { 271 t.Run(tc.name, func(t *testing.T) { 272 jsonBytes, err := json.Marshal(tc.op) 273 require.NoError(t, err) 274 275 var jsonOp Op 276 err = json.Unmarshal(jsonBytes, &jsonOp) 277 require.NoError(t, err) 278 279 require.Equal(t, tc.op, jsonOp) 280 281 yamlBytes, err := yaml.Marshal(tc.op) 282 require.NoError(t, err) 283 284 var yamlOp Op 285 err = yaml.UnmarshalStrict(yamlBytes, &yamlOp) 286 require.NoError(t, err) 287 288 require.Equal(t, tc.op, yamlOp) 289 }) 290 } 291 } 292 293 func TestUnmarshalAll(t *testing.T) { 294 configYAML := ` 295 type: restructure 296 id: my_restructure 297 output: test_output 298 ops: 299 - add: 300 field: "message" 301 value: "val" 302 - add: 303 field: "message_suffix" 304 value_expr: "$.message + \"_suffix\"" 305 - remove: "message" 306 - retain: 307 - "message_retain" 308 - flatten: "message_flatten" 309 - move: 310 from: "message1" 311 to: "message2" 312 ` 313 314 configJSON := ` 315 { 316 "type": "restructure", 317 "id": "my_restructure", 318 "output": "test_output", 319 "ops": [{ 320 "add": { 321 "field": "message", 322 "value": "val" 323 } 324 },{ 325 "add": { 326 "field": "message_suffix", 327 "value_expr": "$.message + \"_suffix\"" 328 } 329 },{ 330 "remove": "message" 331 },{ 332 "retain": [ 333 "message_retain" 334 ] 335 },{ 336 "flatten": "message_flatten" 337 },{ 338 "move": { 339 "from": "message1", 340 "to": "message2" 341 } 342 }] 343 }` 344 345 expected := operator.Config(operator.Config{ 346 Builder: &RestructureOperatorConfig{ 347 TransformerConfig: helper.TransformerConfig{ 348 WriterConfig: helper.WriterConfig{ 349 BasicConfig: helper.BasicConfig{ 350 OperatorID: "my_restructure", 351 OperatorType: "restructure", 352 }, 353 OutputIDs: []string{"test_output"}, 354 }, 355 OnError: helper.SendOnError, 356 }, 357 Ops: []Op{ 358 {&OpAdd{ 359 Field: entry.NewRecordField("message"), 360 Value: "val", 361 }}, 362 {&OpAdd{ 363 Field: entry.NewRecordField("message_suffix"), 364 ValueExpr: func() *string { 365 s := `$.message + "_suffix"` 366 return &s 367 }(), 368 program: func() *vm.Program { 369 vm, err := expr.Compile(`$.message + "_suffix"`) 370 require.NoError(t, err) 371 return vm 372 }(), 373 }}, 374 {&OpRemove{ 375 Field: entry.NewRecordField("message"), 376 }}, 377 {&OpRetain{ 378 Fields: []entry.Field{ 379 entry.NewRecordField("message_retain"), 380 }, 381 }}, 382 {&OpFlatten{ 383 Field: entry.RecordField{ 384 Keys: []string{"message_flatten"}, 385 }, 386 }}, 387 {&OpMove{ 388 From: entry.NewRecordField("message1"), 389 To: entry.NewRecordField("message2"), 390 }}, 391 }, 392 }, 393 }) 394 395 var unmarshalledYAML operator.Config 396 err := yaml.UnmarshalStrict([]byte(configYAML), &unmarshalledYAML) 397 require.NoError(t, err) 398 require.Equal(t, expected, unmarshalledYAML) 399 400 var unmarshalledJSON operator.Config 401 err = json.Unmarshal([]byte(configJSON), &unmarshalledJSON) 402 require.NoError(t, err) 403 require.Equal(t, expected, unmarshalledJSON) 404 } 405 406 func TestOpType(t *testing.T) { 407 cases := []struct { 408 op OpApplier 409 expectedType string 410 }{ 411 { 412 &OpAdd{}, 413 "add", 414 }, 415 { 416 &OpRemove{}, 417 "remove", 418 }, 419 { 420 &OpRetain{}, 421 "retain", 422 }, 423 { 424 &OpMove{}, 425 "move", 426 }, 427 { 428 &OpFlatten{}, 429 "flatten", 430 }, 431 } 432 433 for _, tc := range cases { 434 t.Run(tc.expectedType, func(t *testing.T) { 435 require.Equal(t, tc.expectedType, tc.op.Type()) 436 }) 437 } 438 439 t.Run("InvalidOpType", func(t *testing.T) { 440 raw := "- unknown: test" 441 var ops []Op 442 err := yaml.UnmarshalStrict([]byte(raw), &ops) 443 require.Error(t, err) 444 require.Contains(t, err.Error(), "unknown op type") 445 }) 446 }