github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/avro_test.go (about)

     1  package processor
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"strconv"
     8  	"testing"
     9  
    10  	"github.com/Jeffail/benthos/v3/lib/log"
    11  	"github.com/Jeffail/benthos/v3/lib/message"
    12  	"github.com/Jeffail/benthos/v3/lib/metrics"
    13  	"github.com/Jeffail/benthos/v3/lib/types"
    14  )
    15  
    16  func TestAvroBasic(t *testing.T) {
    17  	schema := `{
    18  	"namespace": "foo.namespace.com",
    19  	"type":	"record",
    20  	"name": "identity",
    21  	"fields": [
    22  		{ "name": "Name", "type": "string"},
    23  		{ "name": "Address", "type": ["null",{
    24  			"namespace": "my.namespace.com",
    25  			"type":	"record",
    26  			"name": "address",
    27  			"fields": [
    28  				{ "name": "City", "type": "string" },
    29  				{ "name": "State", "type": "string" }
    30  			]
    31  		}],"default":null}
    32  	]
    33  }`
    34  
    35  	type testCase struct {
    36  		name     string
    37  		operator string
    38  		encoding string
    39  		input    []string
    40  		output   []string
    41  	}
    42  
    43  	tests := []testCase{
    44  		{
    45  			name:     "textual to json 1",
    46  			operator: "to_json",
    47  			encoding: "textual",
    48  			input: []string{
    49  				`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
    50  			},
    51  			output: []string{
    52  				`{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"Name":"foo"}`,
    53  			},
    54  		},
    55  		{
    56  			name:     "binary to json 1",
    57  			operator: "to_json",
    58  			encoding: "binary",
    59  			input: []string{
    60  				"\x06foo\x02\x06foo\x06bar",
    61  			},
    62  			output: []string{
    63  				`{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"Name":"foo"}`,
    64  			},
    65  		},
    66  		/*
    67  			{
    68  				name:     "single to json 1",
    69  				operator: "to_json",
    70  				encoding: "single",
    71  				input: []string{
    72  					"\xc3\x01\x84>\xe0\xee\xbb\xf1Nj\x06foo\x02\x06foo\x06bar",
    73  				},
    74  				output: []string{
    75  					`{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"Name":"foo"}`,
    76  				},
    77  			},
    78  		*/
    79  		/*
    80  			// TODO: Unfortunately this serialisation is non-deterministic
    81  			{
    82  				name:     "json to textual 1",
    83  				operator: "from_json",
    84  				encoding: "textual",
    85  				input: []string{
    86  					`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
    87  				},
    88  				output: []string{
    89  					`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
    90  				},
    91  			},
    92  		*/
    93  		{
    94  			name:     "json to binary 1",
    95  			operator: "from_json",
    96  			encoding: "binary",
    97  			input: []string{
    98  				`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
    99  			},
   100  			output: []string{
   101  				"\x06foo\x02\x06foo\x06bar",
   102  			},
   103  		},
   104  		/*
   105  			{
   106  				name:     "json to single 1",
   107  				operator: "from_json",
   108  				encoding: "single",
   109  				input: []string{
   110  					`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
   111  				},
   112  				output: []string{
   113  					"\xc3\x01\x84>\xe0\xee\xbb\xf1Nj\x06foo\x02\x06foo\x06bar",
   114  				},
   115  			},
   116  		*/
   117  	}
   118  
   119  	for _, test := range tests {
   120  		t.Run(test.name, func(tt *testing.T) {
   121  			conf := NewConfig()
   122  			conf.Type = TypeAvro
   123  			conf.Avro.Operator = test.operator
   124  			conf.Avro.Encoding = test.encoding
   125  			conf.Avro.Schema = schema
   126  
   127  			proc, err := New(conf, nil, log.Noop(), metrics.Noop())
   128  			if err != nil {
   129  				tt.Fatal(err)
   130  			}
   131  
   132  			input := message.New(nil)
   133  			for _, p := range test.input {
   134  				input.Append(message.NewPart([]byte(p)))
   135  			}
   136  
   137  			exp := make([][]byte, len(test.output))
   138  			for i, p := range test.output {
   139  				exp[i] = []byte(p)
   140  			}
   141  
   142  			msgs, res := proc.ProcessMessage(input)
   143  			if res != nil {
   144  				tt.Fatal(res.Error())
   145  			}
   146  
   147  			if len(msgs) != 1 {
   148  				tt.Fatalf("Expected one message, received: %v", len(msgs))
   149  			}
   150  			if act := message.GetAllBytes(msgs[0]); !reflect.DeepEqual(act, exp) {
   151  				tt.Errorf("Unexpected output: %s != %s", exp, act)
   152  				tt.Logf("Part 0: %v", strconv.Quote(string(act[0])))
   153  			}
   154  			msgs[0].Iter(func(i int, part types.Part) error {
   155  				if fail := part.Metadata().Get(FailFlagKey); len(fail) > 0 {
   156  					tt.Error(fail)
   157  				}
   158  				return nil
   159  			})
   160  		})
   161  	}
   162  }
   163  
   164  func TestAvroSchemaPath(t *testing.T) {
   165  	schema := `{
   166  	"namespace": "foo.namespace.com",
   167  	"type":	"record",
   168  	"name": "identity",
   169  	"fields": [
   170  		{ "name": "Name", "type": "string"},
   171  		{ "name": "Address", "type": ["null",{
   172  			"namespace": "my.namespace.com",
   173  			"type":	"record",
   174  			"name": "address",
   175  			"fields": [
   176  				{ "name": "City", "type": "string" },
   177  				{ "name": "State", "type": "string" }
   178  			]
   179  		}],"default":null}
   180  	]
   181  }`
   182  
   183  	tmpSchemaFile, err := os.CreateTemp("", "benthos_avro_test")
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  	defer os.Remove(tmpSchemaFile.Name())
   188  
   189  	// write schema definition to tmpfile
   190  	if _, err := tmpSchemaFile.WriteString(schema); err != nil {
   191  		t.Fatal(err)
   192  	}
   193  
   194  	type testCase struct {
   195  		name     string
   196  		operator string
   197  		encoding string
   198  		input    []string
   199  		output   []string
   200  	}
   201  
   202  	tests := []testCase{
   203  		{
   204  			name:     "textual to json 1",
   205  			operator: "to_json",
   206  			encoding: "textual",
   207  			input: []string{
   208  				`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
   209  			},
   210  			output: []string{
   211  				`{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"Name":"foo"}`,
   212  			},
   213  		},
   214  		{
   215  			name:     "binary to json 1",
   216  			operator: "to_json",
   217  			encoding: "binary",
   218  			input: []string{
   219  				"\x06foo\x02\x06foo\x06bar",
   220  			},
   221  			output: []string{
   222  				`{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"Name":"foo"}`,
   223  			},
   224  		},
   225  		/*
   226  			{
   227  				name:     "single to json 1",
   228  				operator: "to_json",
   229  				encoding: "single",
   230  				input: []string{
   231  					"\xc3\x01\x84>\xe0\xee\xbb\xf1Nj\x06foo\x02\x06foo\x06bar",
   232  				},
   233  				output: []string{
   234  					`{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"Name":"foo"}`,
   235  				},
   236  			},
   237  		*/
   238  		/*
   239  			// TODO: Unfortunately this serialisation is non-deterministic
   240  			{
   241  				name:     "json to textual 1",
   242  				operator: "from_json",
   243  				encoding: "textual",
   244  				input: []string{
   245  					`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
   246  				},
   247  				output: []string{
   248  					`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
   249  				},
   250  			},
   251  		*/
   252  		{
   253  			name:     "json to binary 1",
   254  			operator: "from_json",
   255  			encoding: "binary",
   256  			input: []string{
   257  				`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
   258  			},
   259  			output: []string{
   260  				"\x06foo\x02\x06foo\x06bar",
   261  			},
   262  		},
   263  		/*
   264  			{
   265  				name:     "json to single 1",
   266  				operator: "from_json",
   267  				encoding: "single",
   268  				input: []string{
   269  					`{"Name":"foo","Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}}}`,
   270  				},
   271  				output: []string{
   272  					"\xc3\x01\x84>\xe0\xee\xbb\xf1Nj\x06foo\x02\x06foo\x06bar",
   273  				},
   274  			},
   275  		*/
   276  	}
   277  
   278  	for _, test := range tests {
   279  		t.Run(test.name, func(tt *testing.T) {
   280  			conf := NewConfig()
   281  			conf.Type = TypeAvro
   282  			conf.Avro.Operator = test.operator
   283  			conf.Avro.Encoding = test.encoding
   284  			conf.Avro.SchemaPath = fmt.Sprintf("file://%s", tmpSchemaFile.Name())
   285  
   286  			proc, err := New(conf, nil, log.Noop(), metrics.Noop())
   287  			if err != nil {
   288  				tt.Fatal(err)
   289  			}
   290  
   291  			input := message.New(nil)
   292  			for _, p := range test.input {
   293  				input.Append(message.NewPart([]byte(p)))
   294  			}
   295  
   296  			exp := make([][]byte, len(test.output))
   297  			for i, p := range test.output {
   298  				exp[i] = []byte(p)
   299  			}
   300  
   301  			msgs, res := proc.ProcessMessage(input)
   302  			if res != nil {
   303  				tt.Fatal(res.Error())
   304  			}
   305  
   306  			if len(msgs) != 1 {
   307  				tt.Fatalf("Expected one message, received: %v", len(msgs))
   308  			}
   309  			if act := message.GetAllBytes(msgs[0]); !reflect.DeepEqual(act, exp) {
   310  				tt.Errorf("Unexpected output: %s != %s", exp, act)
   311  				tt.Logf("Part 0: %v", strconv.Quote(string(act[0])))
   312  			}
   313  			msgs[0].Iter(func(i int, part types.Part) error {
   314  				if fail := part.Metadata().Get(FailFlagKey); len(fail) > 0 {
   315  					tt.Error(fail)
   316  				}
   317  				return nil
   318  			})
   319  		})
   320  	}
   321  }
   322  
   323  func TestAvroSchemaPathNotExist(t *testing.T) {
   324  	conf := NewConfig()
   325  	conf.Type = TypeAvro
   326  	conf.Avro.SchemaPath = "file://path_does_not_exist"
   327  
   328  	_, err := New(conf, nil, log.Noop(), metrics.Noop())
   329  	if err == nil {
   330  		t.Error("expected error from loading non existant schema file")
   331  	}
   332  }