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  }