github.com/Jeffail/benthos/v3@v3.65.0/internal/impl/confluent/processor_schema_registry_decode_test.go (about)

     1  package confluent
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/Jeffail/benthos/v3/public/service"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestSchemaRegistryDecoderConfigParse(t *testing.T) {
    18  	configTests := []struct {
    19  		name            string
    20  		config          string
    21  		errContains     string
    22  		expectedBaseURL string
    23  	}{
    24  		{
    25  			name: "bad url",
    26  			config: `
    27  url: huh#%#@$u*not////::example.com
    28  `,
    29  			errContains: `failed to parse url`,
    30  		},
    31  		{
    32  			name: "url with base path",
    33  			config: `
    34  url: http://example.com/v1
    35  `,
    36  			expectedBaseURL: "http://example.com/v1",
    37  		},
    38  	}
    39  
    40  	spec := schemaRegistryDecoderConfig()
    41  	env := service.NewEnvironment()
    42  	for _, test := range configTests {
    43  		t.Run(test.name, func(t *testing.T) {
    44  			conf, err := spec.ParseYAML(test.config, env)
    45  			require.NoError(t, err)
    46  
    47  			e, err := newSchemaRegistryDecoderFromConfig(conf, nil)
    48  
    49  			if e != nil {
    50  				assert.Equal(t, test.expectedBaseURL, e.schemaRegistryBaseURL.String())
    51  			}
    52  
    53  			if err == nil {
    54  				_ = e.Close(context.Background())
    55  			}
    56  			if test.errContains == "" {
    57  				require.NoError(t, err)
    58  			} else {
    59  				require.Error(t, err)
    60  				assert.Contains(t, err.Error(), test.errContains)
    61  			}
    62  		})
    63  	}
    64  }
    65  
    66  func runSchemaRegistryServer(t *testing.T, fn func(path string) ([]byte, error)) string {
    67  	t.Helper()
    68  
    69  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    70  		b, err := fn(r.URL.Path)
    71  		if err != nil {
    72  			http.Error(w, err.Error(), http.StatusBadRequest)
    73  			return
    74  		}
    75  		if len(b) == 0 {
    76  			http.Error(w, "not found", http.StatusNotFound)
    77  			return
    78  		}
    79  		w.Write(b)
    80  	}))
    81  	t.Cleanup(ts.Close)
    82  
    83  	return ts.URL
    84  }
    85  
    86  const testSchema = `{
    87  	"namespace": "foo.namespace.com",
    88  	"type": "record",
    89  	"name": "identity",
    90  	"fields": [
    91  		{ "name": "Name", "type": "string"},
    92  		{ "name": "Address", "type": ["null",{
    93  			"namespace": "my.namespace.com",
    94  			"type":	"record",
    95  			"name": "address",
    96  			"fields": [
    97  				{ "name": "City", "type": "string" },
    98  				{ "name": "State", "type": "string" }
    99  			]
   100  		}],"default":null},
   101  		{"name": "MaybeHobby", "type": ["null","string"] }
   102  	]
   103  }`
   104  
   105  func TestSchemaRegistryDecodeAvro(t *testing.T) {
   106  	payload3, err := json.Marshal(struct {
   107  		Schema string `json:"schema"`
   108  	}{
   109  		Schema: testSchema,
   110  	})
   111  	require.NoError(t, err)
   112  
   113  	returnedSchema3 := false
   114  	urlStr := runSchemaRegistryServer(t, func(path string) ([]byte, error) {
   115  		switch path {
   116  		case "/schemas/ids/3":
   117  			assert.False(t, returnedSchema3)
   118  			returnedSchema3 = true
   119  			return payload3, nil
   120  		case "/schemas/ids/5":
   121  			return nil, fmt.Errorf("nope")
   122  		}
   123  		return nil, nil
   124  	})
   125  
   126  	decoder, err := newSchemaRegistryDecoder(urlStr, nil, false, nil)
   127  	require.NoError(t, err)
   128  
   129  	tests := []struct {
   130  		name        string
   131  		input       string
   132  		output      string
   133  		errContains string
   134  	}{
   135  		{
   136  			name:   "successful message",
   137  			input:  "\x00\x00\x00\x00\x03\x06foo\x02\x06foo\x06bar\x02\x0edancing",
   138  			output: `{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"MaybeHobby":{"string":"dancing"},"Name":"foo"}`,
   139  		},
   140  		{
   141  			name:   "successful message with null hobby",
   142  			input:  "\x00\x00\x00\x00\x03\x06foo\x02\x06foo\x06bar\x00",
   143  			output: `{"Address":{"my.namespace.com.address":{"City":"foo","State":"bar"}},"MaybeHobby":null,"Name":"foo"}`,
   144  		},
   145  		{
   146  			name:        "non-empty magic byte",
   147  			input:       "\x06\x00\x00\x00\x03\x06foo\x02\x06foo\x06bar",
   148  			errContains: "version number 6 not supported",
   149  		},
   150  		{
   151  			name:        "non-existing schema",
   152  			input:       "\x00\x00\x00\x00\x04\x06foo\x02\x06foo\x06bar",
   153  			errContains: "schema '4' not found by registry",
   154  		},
   155  		{
   156  			name:        "server fails",
   157  			input:       "\x00\x00\x00\x00\x05\x06foo\x02\x06foo\x06bar",
   158  			errContains: "request failed for schema '5'",
   159  		},
   160  	}
   161  
   162  	for _, test := range tests {
   163  		test := test
   164  		t.Run(test.name, func(t *testing.T) {
   165  			outMsgs, err := decoder.Process(context.Background(), service.NewMessage([]byte(test.input)))
   166  			if test.errContains != "" {
   167  				require.Error(t, err)
   168  				assert.Contains(t, err.Error(), test.errContains)
   169  			} else {
   170  				require.NoError(t, err)
   171  				require.Len(t, outMsgs, 1)
   172  
   173  				b, err := outMsgs[0].AsBytes()
   174  				require.NoError(t, err)
   175  				assert.Equal(t, test.output, string(b))
   176  			}
   177  
   178  		})
   179  	}
   180  
   181  	require.NoError(t, decoder.Close(context.Background()))
   182  	decoder.cacheMut.Lock()
   183  	assert.Len(t, decoder.schemas, 0)
   184  	decoder.cacheMut.Unlock()
   185  }
   186  
   187  func TestSchemaRegistryDecodeClearExpired(t *testing.T) {
   188  	urlStr := runSchemaRegistryServer(t, func(path string) ([]byte, error) {
   189  		return nil, fmt.Errorf("nope")
   190  	})
   191  
   192  	decoder, err := newSchemaRegistryDecoder(urlStr, nil, false, nil)
   193  	require.NoError(t, err)
   194  	require.NoError(t, decoder.Close(context.Background()))
   195  
   196  	tStale := time.Now().Add(-time.Hour).Unix()
   197  	tNotStale := time.Now().Unix()
   198  	tNearlyStale := time.Now().Add(-(schemaStaleAfter / 2)).Unix()
   199  
   200  	decoder.cacheMut.Lock()
   201  	decoder.schemas = map[int]*cachedSchemaDecoder{
   202  		5:  {lastUsedUnixSeconds: tStale},
   203  		10: {lastUsedUnixSeconds: tNotStale},
   204  		15: {lastUsedUnixSeconds: tNearlyStale},
   205  	}
   206  	decoder.cacheMut.Unlock()
   207  
   208  	decoder.clearExpired()
   209  
   210  	decoder.cacheMut.Lock()
   211  	assert.Equal(t, map[int]*cachedSchemaDecoder{
   212  		10: {lastUsedUnixSeconds: tNotStale},
   213  		15: {lastUsedUnixSeconds: tNearlyStale},
   214  	}, decoder.schemas)
   215  	decoder.cacheMut.Unlock()
   216  }