From 31a0daf9616b49f831cfdd095e196a90a909d8c3 Mon Sep 17 00:00:00 2001 From: Mike Terhar Date: Thu, 19 Dec 2024 03:50:30 +0000 Subject: [PATCH] add unit tests for config, factory, and reciever --- receiver/libhoneyreceiver/config_test.go | 29 +++ receiver/libhoneyreceiver/factory_test.go | 47 ++++ receiver/libhoneyreceiver/go.mod | 24 +- receiver/libhoneyreceiver/go.sum | 10 +- receiver/libhoneyreceiver/receiver.go | 68 +++--- receiver/libhoneyreceiver/receiver_test.go | 272 +++++++++++++++++++++ 6 files changed, 400 insertions(+), 50 deletions(-) create mode 100644 receiver/libhoneyreceiver/config_test.go create mode 100644 receiver/libhoneyreceiver/factory_test.go create mode 100644 receiver/libhoneyreceiver/receiver_test.go diff --git a/receiver/libhoneyreceiver/config_test.go b/receiver/libhoneyreceiver/config_test.go new file mode 100644 index 000000000000..1d3f6b55dc71 --- /dev/null +++ b/receiver/libhoneyreceiver/config_test.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package libhoneyreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/libhoneyreceiver" + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, componenttest.CheckConfigStruct(cfg)) + + libhoneyCfg, ok := cfg.(*Config) + require.True(t, ok, "invalid Config type") + + assert.Equal(t, "localhost:8080", libhoneyCfg.HTTP.Endpoint) + assert.Equal(t, []string{"/events", "/event", "/batch"}, libhoneyCfg.HTTP.TracesURLPaths) + assert.Equal(t, "", libhoneyCfg.AuthAPI) + assert.Equal(t, "service.name", libhoneyCfg.FieldMapConfig.Resources.ServiceName) + assert.Equal(t, "library.name", libhoneyCfg.FieldMapConfig.Scopes.LibraryName) + assert.Equal(t, []string{"duration_ms"}, libhoneyCfg.FieldMapConfig.Attributes.DurationFields) +} diff --git a/receiver/libhoneyreceiver/factory_test.go b/receiver/libhoneyreceiver/factory_test.go new file mode 100644 index 000000000000..9e369d4fd17e --- /dev/null +++ b/receiver/libhoneyreceiver/factory_test.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package libhoneyreceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/libhoneyreceiver/internal/metadata" +) + +func TestCreateTracesReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + set := receivertest.NewNopSettings() + tReceiver, err := factory.CreateTraces(context.Background(), set, cfg, consumertest.NewNop()) + + assert.NoError(t, err, "receiver creation failed") + assert.NotNil(t, tReceiver, "receiver creation failed") + + assert.NoError(t, tReceiver.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, tReceiver.Shutdown(context.Background())) +} + +func TestCreateLogsReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + set := receivertest.NewNopSettings() + lReceiver, err := factory.CreateLogs(context.Background(), set, cfg, consumertest.NewNop()) + + assert.NoError(t, err, "receiver creation failed") + assert.NotNil(t, lReceiver, "receiver creation failed") + + assert.NoError(t, lReceiver.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, lReceiver.Shutdown(context.Background())) +} + +func TestType(t *testing.T) { + factory := NewFactory() + assert.Equal(t, metadata.Type, factory.Type()) +} diff --git a/receiver/libhoneyreceiver/go.mod b/receiver/libhoneyreceiver/go.mod index a041cf01e486..e3e9a5634fc2 100644 --- a/receiver/libhoneyreceiver/go.mod +++ b/receiver/libhoneyreceiver/go.mod @@ -5,26 +5,26 @@ go 1.22.0 require ( github.com/gogo/protobuf v1.3.2 github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.115.0 - github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.115.0 + github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.116.0 github.com/stretchr/testify v1.10.0 + github.com/vmihailenco/msgpack/v5 v5.4.1 + go.opentelemetry.io/collector/component v0.116.0 go.opentelemetry.io/collector/component/componenttest v0.116.0 go.opentelemetry.io/collector/config/confighttp v0.116.0 go.opentelemetry.io/collector/confmap v1.22.0 go.opentelemetry.io/collector/consumer v1.22.0 go.opentelemetry.io/collector/consumer/consumertest v0.116.0 - go.opentelemetry.io/collector/receiver/receivertest v0.116.0 - github.com/vmihailenco/msgpack/v5 v5.4.1 - go.opentelemetry.io/collector/component v0.116.0 - go.opentelemetry.io/collector/config/confighttp v0.116.0 go.opentelemetry.io/collector/pdata v1.22.0 go.opentelemetry.io/collector/receiver v0.116.0 + go.opentelemetry.io/collector/receiver/receivertest v0.116.0 go.opentelemetry.io/collector/semconv v0.116.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 ) require ( + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.opentelemetry.io/collector/consumer/xconsumer v0.116.0 // indirect go.opentelemetry.io/collector/receiver/xreceiver v0.116.0 // indirect ) @@ -47,12 +47,10 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.116.0 github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect go.opentelemetry.io/collector/client v1.22.0 // indirect - go.opentelemetry.io/collector/component v0.116.0 go.opentelemetry.io/collector/component/componentstatus v0.116.0 go.opentelemetry.io/collector/config/configauth v0.116.0 // indirect go.opentelemetry.io/collector/config/configcompression v1.22.0 // indirect @@ -63,10 +61,8 @@ require ( go.opentelemetry.io/collector/consumer/consumererror v0.116.0 // indirect go.opentelemetry.io/collector/extension v0.116.0 // indirect go.opentelemetry.io/collector/extension/auth v0.116.0 // indirect - go.opentelemetry.io/collector/pdata v1.22.0 // indirect go.opentelemetry.io/collector/pdata/pprofile v0.116.0 // indirect go.opentelemetry.io/collector/pipeline v0.116.0 // indirect - go.opentelemetry.io/collector/receiver v0.116.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect @@ -74,10 +70,10 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect - google.golang.org/grpc v1.68.1 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/grpc v1.69.0 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/receiver/libhoneyreceiver/go.sum b/receiver/libhoneyreceiver/go.sum index 720a73eaa882..32bb83e1775b 100644 --- a/receiver/libhoneyreceiver/go.sum +++ b/receiver/libhoneyreceiver/go.sum @@ -118,6 +118,8 @@ go.opentelemetry.io/collector/receiver/receivertest v0.116.0 h1:ZF4QVcots0OUiutb go.opentelemetry.io/collector/receiver/receivertest v0.116.0/go.mod h1:7GGvtHhW3o6457/wGtSWXJtCtlW6VGFUZSlf6wboNTw= go.opentelemetry.io/collector/receiver/xreceiver v0.116.0 h1:Kc+ixqgMjU2sHhzNrFn5TttVNiJlJwTLL3sQrM9uH6s= go.opentelemetry.io/collector/receiver/xreceiver v0.116.0/go.mod h1:H2YGSNFoMbWMIDvB8tzkReHSVqvogihjtet+ppHfYv8= +go.opentelemetry.io/collector/semconv v0.116.0 h1:63xCZomsKJAWmKGWD3lnORiE3WKW6AO4LjnzcHzGx3Y= +go.opentelemetry.io/collector/semconv v0.116.0/go.mod h1:N6XE8Q0JKgBN2fAhkUQtqK9LT7rEGR6+Wu/Rtbal1iI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= @@ -167,10 +169,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= -google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI= +google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/receiver/libhoneyreceiver/receiver.go b/receiver/libhoneyreceiver/receiver.go index ba6f28690d44..84ad68e4638a 100644 --- a/receiver/libhoneyreceiver/receiver.go +++ b/receiver/libhoneyreceiver/receiver.go @@ -96,38 +96,7 @@ func (r *libhoneyReceiver) startHTTPServer(ctx context.Context, host component.H if r.cfg.AuthAPI != "" { httpMux.HandleFunc("/1/auth", func(resp http.ResponseWriter, req *http.Request) { - authURL := fmt.Sprintf("%s/1/auth", r.cfg.AuthAPI) - authReq, err := http.NewRequest(http.MethodGet, authURL, nil) - if err != nil { - errJSON, _ := json.Marshal(`{"error": "failed to create AuthInfo request"}`) - writeResponse(resp, "json", http.StatusBadRequest, errJSON) - return - } - authReq.Header.Set("x-honeycomb-team", req.Header.Get("x-honeycomb-team")) - var authClient http.Client - authResp, err := authClient.Do(authReq) - if err != nil { - errJSON, _ := json.Marshal(fmt.Sprintf(`"error": "failed to send request to auth api endpoint", "message", "%s"}`, err.Error())) - writeResponse(resp, "json", http.StatusBadRequest, errJSON) - return - } - defer authResp.Body.Close() - - switch { - case authResp.StatusCode == http.StatusUnauthorized: - errJSON, _ := json.Marshal(`"error": "received 401 response for AuthInfo request from Honeycomb API - check your API key"}`) - writeResponse(resp, "json", http.StatusBadRequest, errJSON) - return - case authResp.StatusCode > 299: - errJSON, _ := json.Marshal(fmt.Sprintf(`"error": "bad response code from API", "status_code", %d}`, authResp.StatusCode)) - writeResponse(resp, "json", http.StatusBadRequest, errJSON) - return - } - authRawBody, _ := io.ReadAll(authResp.Body) - _, err = resp.Write(authRawBody) - if err != nil { - r.settings.Logger.Info("couldn't write http response") - } + r.handleAuth(resp, req) }) } @@ -181,6 +150,41 @@ func (r *libhoneyReceiver) registerLogConsumer(tc consumer.Logs) { r.nextLogs = tc } +func (r *libhoneyReceiver) handleAuth(resp http.ResponseWriter, req *http.Request) { + authURL := fmt.Sprintf("%s/1/auth", r.cfg.AuthAPI) + authReq, err := http.NewRequest(http.MethodGet, authURL, nil) + if err != nil { + errJSON, _ := json.Marshal(`{"error": "failed to create AuthInfo request"}`) + writeResponse(resp, "json", http.StatusBadRequest, errJSON) + return + } + authReq.Header.Set("x-honeycomb-team", req.Header.Get("x-honeycomb-team")) + var authClient http.Client + authResp, err := authClient.Do(authReq) + if err != nil { + errJSON, _ := json.Marshal(fmt.Sprintf(`"error": "failed to send request to auth api endpoint", "message", "%s"}`, err.Error())) + writeResponse(resp, "json", http.StatusBadRequest, errJSON) + return + } + defer authResp.Body.Close() + + switch { + case authResp.StatusCode == http.StatusUnauthorized: + errJSON, _ := json.Marshal(`"error": "received 401 response for AuthInfo request from Honeycomb API - check your API key"}`) + writeResponse(resp, "json", http.StatusBadRequest, errJSON) + return + case authResp.StatusCode > 299: + errJSON, _ := json.Marshal(fmt.Sprintf(`"error": "bad response code from API", "status_code", %d}`, authResp.StatusCode)) + writeResponse(resp, "json", http.StatusBadRequest, errJSON) + return + } + authRawBody, _ := io.ReadAll(authResp.Body) + _, err = resp.Write(authRawBody) + if err != nil { + r.settings.Logger.Info("couldn't write http response") + } +} + func (r *libhoneyReceiver) handleEvent(resp http.ResponseWriter, req *http.Request) { enc, ok := readContentType(resp, req) if !ok { diff --git a/receiver/libhoneyreceiver/receiver_test.go b/receiver/libhoneyreceiver/receiver_test.go new file mode 100644 index 000000000000..381785aa9916 --- /dev/null +++ b/receiver/libhoneyreceiver/receiver_test.go @@ -0,0 +1,272 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package libhoneyreceiver + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vmihailenco/msgpack/v5" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/libhoneyreceiver/internal/libhoneyevent" +) + +func TestNewLibhoneyReceiver(t *testing.T) { + defaultCfg := createDefaultConfig() + httpCfg := defaultCfg.(*Config).HTTP + tests := []struct { + name string + config *Config + wantError bool + }{ + { + name: "valid_config", + config: &Config{ + HTTP: httpCfg, + }, + }, + { + name: "nil_config", + config: nil, + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + set := receivertest.NewNopSettings() + r, err := newLibhoneyReceiver(tt.config, &set) + if tt.wantError { + assert.Error(t, err) + assert.Nil(t, r) + return + } + assert.NoError(t, err) + assert.NotNil(t, r) + }) + } +} + +func TestLibhoneyReceiver_Start(t *testing.T) { + cfg := createDefaultConfig() + + set := receivertest.NewNopSettings() + r, err := newLibhoneyReceiver(cfg.(*Config), &set) + require.NoError(t, err) + + r.registerTraceConsumer(consumertest.NewNop()) + r.registerLogConsumer(consumertest.NewNop()) + + err = r.Start(context.Background(), componenttest.NewNopHost()) + assert.NoError(t, err) + + err = r.Shutdown(context.Background()) + assert.NoError(t, err) +} + +func TestLibhoneyReceiver_HandleEvent(t *testing.T) { + now := time.Now() + tests := []struct { + name string + events []libhoneyevent.LibhoneyEvent + contentType string + expectedStatus int + wantError bool + }{ + { + name: "valid_json_event", + events: []libhoneyevent.LibhoneyEvent{ + { + Time: now.Format(time.RFC3339), + MsgPackTimestamp: &now, + Data: map[string]any{ + "message": "test event", + }, + Samplerate: 1, + }, + }, + contentType: "application/json", + expectedStatus: http.StatusAccepted, + }, + { + name: "valid_msgpack_event", + events: []libhoneyevent.LibhoneyEvent{ + { + Time: now.Format(time.RFC3339), + MsgPackTimestamp: &now, + Data: map[string]any{ + "message": "test event", + }, + Samplerate: 1, + }, + }, + contentType: "application/msgpack", + expectedStatus: http.StatusAccepted, + }, + { + name: "invalid_content_type", + events: []libhoneyevent.LibhoneyEvent{}, + contentType: "text/plain", + expectedStatus: http.StatusUnsupportedMediaType, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := createDefaultConfig() + set := receivertest.NewNopSettings() + r, err := newLibhoneyReceiver(cfg.(*Config), &set) + require.NoError(t, err) + + sink := &consumertest.LogsSink{} + r.registerLogConsumer(sink) + + var body []byte + switch tt.contentType { + case "application/json": + body, err = json.Marshal(tt.events) + case "application/msgpack": + body, err = msgpack.Marshal(tt.events) + default: + body = []byte("invalid content") + } + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodPost, "/1/events/test_dataset", bytes.NewReader(body)) + req.Header.Set("Content-Type", tt.contentType) + w := httptest.NewRecorder() + + r.handleEvent(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedStatus, resp.StatusCode) + + if !tt.wantError { + assert.Eventually(t, func() bool { + return sink.LogRecordCount() > 0 + }, time.Second, 10*time.Millisecond) + } + }) + } +} + +func TestLibhoneyReceiver_AuthEndpoint(t *testing.T) { + tests := []struct { + name string + authAPI string + apiKey string + mockResponse *http.Response + expectedStatus int + }{ + { + name: "valid_auth", + authAPI: "http://mock-auth-api", + apiKey: "test-key", + mockResponse: &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{ + "team": {"slug": "test-team"}, + "environment": {"slug": "test-env", "name": "Test Env"} + }`)), + }, + expectedStatus: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.AuthAPI = tt.authAPI + set := receivertest.NewNopSettings() + r, err := newLibhoneyReceiver(cfg, &set) + require.NoError(t, err) + + // Create test server to mock auth API + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tt.apiKey, r.Header.Get("x-honeycomb-team")) + w.WriteHeader(tt.mockResponse.StatusCode) + io.Copy(w, tt.mockResponse.Body) + })) + defer ts.Close() + + req := httptest.NewRequest(http.MethodGet, "/1/auth", nil) + req.Header.Set("x-honeycomb-team", tt.apiKey) + w := httptest.NewRecorder() + + r.server = &http.Server{Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + r.handleAuth(resp, req) + })} + + resp := w.Result() + assert.Equal(t, tt.expectedStatus, resp.StatusCode) + }) + } +} + +func TestReadContentType(t *testing.T) { + tests := []struct { + name string + method string + contentType string + expectedStatus int + wantEncoder bool + }{ + { + name: "valid_json", + method: http.MethodPost, + contentType: "application/json", + expectedStatus: http.StatusOK, + wantEncoder: true, + }, + { + name: "valid_msgpack", + method: http.MethodPost, + contentType: "application/msgpack", + expectedStatus: http.StatusOK, + wantEncoder: true, + }, + { + name: "invalid_method", + method: http.MethodGet, + contentType: "application/json", + expectedStatus: http.StatusMethodNotAllowed, + wantEncoder: false, + }, + { + name: "invalid_content_type", + method: http.MethodPost, + contentType: "text/plain", + expectedStatus: http.StatusUnsupportedMediaType, + wantEncoder: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(tt.method, "/test", nil) + req.Header.Set("Content-Type", tt.contentType) + w := httptest.NewRecorder() + + enc, ok := readContentType(w, req) + assert.Equal(t, tt.wantEncoder, ok) + if tt.wantEncoder { + assert.NotNil(t, enc) + } else { + assert.Equal(t, tt.expectedStatus, w.Code) + } + }) + } +}