/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ package server import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "git.zabbix.com/ZT/kafka-connector/kafka" "git.zabbix.com/ap/plugin-support/errs" "git.zabbix.com/ap/plugin-support/zbxnet" "github.com/google/go-cmp/cmp" ) var _ kafka.Producer = &mockProducer{} var _ http.ResponseWriter = &mockWriter{} type mockProducer struct { called int ids []string messages []string } type mockWriter struct { code int data []byte header http.Header writeErr error } func (w *mockWriter) Header() http.Header { return w.header } func (w *mockWriter) Write(in []byte) (int, error) { if w.writeErr != nil { return 0, w.writeErr } w.data = in return len(in), nil } func (w *mockWriter) WriteHeader(statusCode int) { w.code = statusCode } func (mp *mockProducer) ProduceItem(key, message string) { mp.called++ mp.ids = append(mp.ids, key) mp.messages = append(mp.messages, message) } func (mp *mockProducer) ProduceEvent(key, message string) { mp.called++ mp.ids = append(mp.ids, key) mp.messages = append(mp.messages, message) } func (mp *mockProducer) Close() error { return nil } func TestBufferedResponseWriter_Write(t *testing.T) { t.Parallel() type fields struct { prevData []byte } type args struct { data []byte } tests := []struct { name string fields fields args args wantCount int wantData string wantErr bool }{ { "+valid", fields{ nil, }, args{ []byte("body write data"), }, 15, "body write data", false, }, { "+prevData", fields{ []byte("some data, already in buffer\n"), }, args{ []byte("body write data"), }, 15, "some data, already in buffer\nbody write data", false, }, { "-empty_string", fields{ nil, }, args{ []byte(""), }, 0, "", false, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() b := &BufferedResponseWriter{ buffer: bytes.Buffer{}, } if tt.fields.prevData != nil { _, err := b.buffer.Write(tt.fields.prevData) if err != nil { t.Fatalf("failed to prepare byte buffer with previous data: %s", err.Error()) } } got, err := b.Write(tt.args.data) if (err != nil) != tt.wantErr { t.Fatalf("BufferedResponseWriter.Write() error = %v, wantErr %v", err, tt.wantErr) } if diff := cmp.Diff(tt.wantCount, got); diff != "" { t.Fatalf("BufferedResponseWriter.Write() = %s", diff) } if diff := cmp.Diff(tt.wantData, b.buffer.String()); diff != "" { t.Fatalf("BufferedResponseWriter.Write() = %s", diff) } }) } } func TestBufferedResponseWriter_WriteResponse(t *testing.T) { t.Parallel() type fields struct { writeErr error } type args struct { header http.Header data string code int } tests := []struct { name string fields fields args args wantCode int wantData string wantHeader http.Header }{ { "+valid", fields{nil}, args{ map[string][]string{ "Custom": {"header"}, }, "write data", http.StatusOK, }, http.StatusOK, "write data", map[string][]string{ "Custom": {"header"}, "Content-Type": {"application/json"}, "X-Content-Type-Options": {"nosniff"}, }, }, { "+noCustomHeader", fields{nil}, args{ nil, "write data", http.StatusOK, }, http.StatusOK, "write data", map[string][]string{ "Content-Type": {"application/json"}, "X-Content-Type-Options": {"nosniff"}, }, }, { "-writeErr", fields{errs.New("write error")}, args{ nil, "write data", http.StatusOK, }, http.StatusOK, "", map[string][]string{ "Content-Type": {"application/json"}, "X-Content-Type-Options": {"nosniff"}, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := mockWriter{ header: http.Header{}, writeErr: tt.fields.writeErr, } b := &BufferedResponseWriter{ w: &w, buffer: bytes.Buffer{}, code: tt.args.code, header: tt.args.header, } _, err := b.buffer.Write([]byte(tt.args.data)) if err != nil { t.Fatalf("failed to write to buf: %s", err.Error()) } b.WriteResponse() if diff := cmp.Diff(tt.wantHeader, b.w.Header()); diff != "" { t.Fatalf("BufferedResponseWriter.WriteResponse() = %s", diff) } if w.code != tt.wantCode { t.Fatalf( "BufferedResponseWriter.WriteResponse() expected status code: %d, but got: %d", tt.wantCode, w.code, ) } if diff := cmp.Diff(tt.wantData, string(w.data)); diff != "" { t.Fatalf("BufferedResponseWriter.WriteResponse() = %s", diff) } }) } } func Test_handler_accessMW(t *testing.T) { t.Parallel() type fields struct { authToken string producer kafka.Producer allowedPeers string } type args struct { reqIp string headers map[string][]string } tests := []struct { name string fields fields args args wantResponse string wantErrString string wantCode int wantHandler bool }{ { "+valid", fields{ "", nil, "127.0.0.1", }, args{ "127.0.0.1:80", map[string][]string{ "Authorization": {""}, "Content-Type": {"application/x-ndjson"}, }, }, "", // successful execution this function does not set a response body "", http.StatusOK, // successful execution this function does not set a status code, but the default one is 200 true, }, { "+withAuth", fields{ "foobar", nil, "127.0.0.1", }, args{ "127.0.0.1:80", map[string][]string{ "Authorization": {"Bearer foobar"}, "Content-Type": {"application/x-ndjson"}, }, }, "", "", http.StatusOK, true, }, { "+authHeaderIgnored", fields{ "", nil, "127.0.0.1", }, args{ "127.0.0.1:80", map[string][]string{ "Authorization": {"Bearer foobar"}, "Content-Type": {"application/x-ndjson"}, }, }, "", "", http.StatusOK, true, }, { "-ipCheckErr", fields{ "", nil, "127.0.0.13", }, args{ "127.0.0.1:80", map[string][]string{ "Authorization": {"Bearer foobar"}, "Content-Type": {"application/x-ndjson"}, }, }, "fail", "ip validation failed", http.StatusForbidden, false, }, { "-wrongBearerToken", fields{ "barfoo", nil, "127.0.0.1", }, args{ "127.0.0.1:80", map[string][]string{ "Authorization": {"Bearer foobar"}, "Content-Type": {"application/jibber-jabber"}, }, }, "fail", "bearer token validation failed", http.StatusUnauthorized, false, }, { "-unsupportedContentType", fields{ "", nil, "127.0.0.1", }, args{ "127.0.0.1:80", map[string][]string{ "Authorization": {"Bearer foobar"}, "Content-Type": {"application/jibber-jabber"}, }, }, "fail", "header must contain", http.StatusUnsupportedMediaType, false, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodPost, "/some/path", nil) r.Header = tt.args.headers r.RemoteAddr = tt.args.reqIp ips, err := zbxnet.GetAllowedPeers(tt.fields.allowedPeers) if err != nil { t.Fatalf("failed to get allowed peers: %s", err.Error()) } h := &handler{ authToken: tt.fields.authToken, producer: tt.fields.producer, allowedPeers: ips, } handlerCalled := false handlerFunc := func(http.ResponseWriter, *http.Request) { handlerCalled = true } h.accessMW(handlerFunc)(w, r) if w.Code != tt.wantCode { t.Fatalf( "handler.accessMW() handler expected status code: %d, but got: %d\nresponse body: %s", tt.wantCode, w.Code, w.Body, ) } if tt.wantResponse != "" && tt.wantResponse != unmarshalResponse(w.Body)["response"] { t.Fatalf( "handler.accessMW() handler expected response to contain: '%s', but got full response: '%s'", tt.wantResponse, w.Body.String(), ) } if tt.wantErrString != "" && !strings.Contains(w.Body.String(), tt.wantErrString) { t.Fatalf( "handler.accessMW() handler expected response error to contain: '%s', but got: '%s'", tt.wantErrString, w.Body.String(), ) } if tt.wantHandler != handlerCalled { t.Fatalf( "handler.accessMW() handler called status: %t, but got: '%t'", tt.wantHandler, handlerCalled, ) } }) } } func Test_handler_checkIP(t *testing.T) { t.Parallel() type fields struct { allowedPeers string } type args struct { reqIp string } tests := []struct { name string fields fields args args wantErr bool }{ { "+valid", fields{"127.0.0.1"}, args{"127.0.0.1:80"}, false, }, { "+multipleIps", fields{"127.0.0.1,::1,127.0.0.3"}, args{"127.0.0.1:80"}, false, }, { "-noRequestPort", fields{"127.0.0.1"}, args{"127.0.0.1"}, true, }, { "-ipNotAllowed", fields{"127.0.0.3"}, args{"127.0.0.1:80"}, true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() r := httptest.NewRequest(http.MethodPost, "/some/path", nil) r.RemoteAddr = tt.args.reqIp ips, err := zbxnet.GetAllowedPeers(tt.fields.allowedPeers) if err != nil { t.Fatalf("failed to get allowed peers: %s", err.Error()) } h := &handler{ allowedPeers: ips, } if err := h.checkIP(r); (err != nil) != tt.wantErr { t.Fatalf("handler.checkIP() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_handler_validateBearerToken(t *testing.T) { t.Parallel() type fields struct { authToken string } type args struct { headers map[string][]string } tests := []struct { name string fields fields args args want int wantErr bool }{ { "+valid", fields{"foobar"}, args{ map[string][]string{ "Authorization": {"Bearer foobar"}, }, }, 0, false, }, { "-incorrectlyFormattedHeader", fields{"foobar"}, args{ map[string][]string{ "Authorization": {"Bearerfoobar"}, }, }, http.StatusBadRequest, true, }, { "-incorrectToken", fields{"foobar"}, args{ map[string][]string{ "Authorization": {"Bearer barfoo"}, }, }, http.StatusUnauthorized, true, }, { "-missingHeader", fields{"foobar"}, args{ map[string][]string{}, }, http.StatusBadRequest, true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() r := httptest.NewRequest(http.MethodPost, "/some/path", nil) r.Header = tt.args.headers h := &handler{ authToken: tt.fields.authToken, } got, err := h.validateBearerToken(r) if (err != nil) != tt.wantErr { t.Fatalf("handler.validateBearerToken() error = %v, wantErr %v", err, tt.wantErr) } if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("handler.validateBearerToken() = %s", diff) } }) } } //nolint:dupl //easier to read and handle the test when duplicated func Test_handler_events(t *testing.T) { t.Parallel() type fields struct { producer *mockProducer } type args struct { r *http.Request } tests := []struct { name string fields fields args args wantResponse string wantCode int wantProducerCallTimes int wantIds []string wantMessages []string wantErr bool }{ { "+valid", fields{&mockProducer{}}, args{ httptest.NewRequest( http.MethodPost, "/some/path", strings.NewReader( getRequestString( []map[string]any{ {"eventid": 23}, {"eventid": 24}, {"eventid": 25}, }, ), ), ), }, "success", http.StatusCreated, 3, []string{"23", "24", "25"}, []string{ "{\"eventid\":23}", "{\"eventid\":24}", "{\"eventid\":25}"}, false, }, { "+validSingle", fields{&mockProducer{}}, args{ httptest.NewRequest( http.MethodPost, "/some/path", strings.NewReader( getRequestString( []map[string]any{ {"eventid": 25}, }, ), ), ), }, "success", http.StatusCreated, 1, []string{"25"}, []string{"{\"eventid\":25}"}, false, }, { "-emptyBody", fields{&mockProducer{}}, args{ httptest.NewRequest(http.MethodPost, "/some/path", nil), }, "", // body not set on fails http.StatusOK, // default, function does not set code on fail 0, nil, nil, true, }, { "-invalidBody", fields{&mockProducer{}}, args{ httptest.NewRequest(http.MethodPost, "/some/path", strings.NewReader("{eventid:wqe}")), }, "", http.StatusOK, 0, nil, nil, true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() h := handler{ producer: tt.fields.producer, } if err := h.events(w, tt.args.r); (err != nil) != tt.wantErr { t.Fatalf("handler.events() error = %v, wantErr %v", err, tt.wantErr) } if w.Code != tt.wantCode { t.Fatalf("handler.events() expected status code: `%d`, but got: `%d`", tt.wantCode, w.Code) } if tt.wantResponse != "" && tt.wantResponse != unmarshalResponse(w.Body)["response"] { t.Fatalf( "handler.events() handler expected response to contain: '%s', but got full response: '%s'", tt.wantResponse, w.Body.String(), ) } if tt.wantProducerCallTimes != tt.fields.producer.called { t.Fatalf( "handler.events() expected handler called time: `%d`, but got called: '%d' times", tt.wantProducerCallTimes, tt.fields.producer.called, ) } if diff := cmp.Diff(tt.wantIds, tt.fields.producer.ids); diff != "" { t.Fatalf( "handler.events() expected handler called with: `%s` ids, but got called with: '%s' ids", tt.wantIds, tt.fields.producer.ids, ) } if diff := cmp.Diff(tt.wantMessages, tt.fields.producer.messages); diff != "" { t.Fatalf( "handler.events() expected handler called with: `%s` messages, but got called with: '%s' messages", tt.wantMessages, tt.fields.producer.messages, ) } }) } } //nolint:dupl //easier to read and handle the test when duplicated func Test_handler_items(t *testing.T) { t.Parallel() type fields struct { producer *mockProducer } type args struct { r *http.Request } tests := []struct { name string fields fields args args wantResponse string wantCode int wantProducerCallTimes int wantIds []string wantMessages []string wantErr bool }{ { "+valid", fields{&mockProducer{}}, args{ httptest.NewRequest( http.MethodPost, "/some/path", strings.NewReader( getRequestString( []map[string]any{ {"itemid": 23}, {"itemid": 24}, {"itemid": 25}, }, ), ), ), }, "success", http.StatusCreated, 3, []string{"23", "24", "25"}, []string{ "{\"itemid\":23}", "{\"itemid\":24}", "{\"itemid\":25}", }, false, }, { "+validSingle", fields{&mockProducer{}}, args{ httptest.NewRequest( http.MethodPost, "/some/path", strings.NewReader( getRequestString( []map[string]any{ {"itemid": 25}, }, ), ), ), }, "success", http.StatusCreated, 1, []string{"25"}, []string{"{\"itemid\":25}"}, false, }, { "-emptyBody", fields{&mockProducer{}}, args{ httptest.NewRequest(http.MethodPost, "/some/path", nil), }, "", // body not set on fails http.StatusOK, // default, function does not set code on fail 0, nil, nil, true, }, { "-invalidBody", fields{&mockProducer{}}, args{ httptest.NewRequest(http.MethodPost, "/some/path", strings.NewReader("{itemid:wqe}")), }, "", http.StatusOK, 0, nil, nil, true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() h := handler{ producer: tt.fields.producer, } if err := h.items(w, tt.args.r); (err != nil) != tt.wantErr { t.Fatalf("handler.items() error = %v, wantErr %v", err, tt.wantErr) } if w.Code != tt.wantCode { t.Fatalf("handler.items() expected status code: `%d`, but got: `%d`", tt.wantCode, w.Code) } if tt.wantResponse != "" && tt.wantResponse != unmarshalResponse(w.Body)["response"] { t.Fatalf( "handler.items() handler expected response to contain: '%s', but got full response: '%s'", tt.wantResponse, w.Body.String(), ) } if tt.wantProducerCallTimes != tt.fields.producer.called { t.Fatalf( "handler.items() expected handler called time: `%d`, but got called: '%d' times", tt.wantProducerCallTimes, tt.fields.producer.called, ) } if diff := cmp.Diff(tt.wantIds, tt.fields.producer.ids); diff != "" { t.Fatalf( "handler.items() expected handler called with: `%s` ids, but got called with: '%s' ids", tt.wantIds, tt.fields.producer.ids, ) } if diff := cmp.Diff(tt.wantMessages, tt.fields.producer.messages); diff != "" { t.Fatalf( "handler.items() expected handler called with: `%s` messages, but got called with: '%s' messages", tt.wantMessages, tt.fields.producer.messages, ) } }) } } func Test_notFoundMW(t *testing.T) { t.Parallel() type args struct { code int } tests := []struct { name string args args wantResponse string wantErrString string wantCode int }{ { "+valid", args{http.StatusOK}, "", "", http.StatusOK, }, { "-notFound", args{http.StatusNotFound}, "fail", "Not Found", http.StatusNotFound, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodPost, "/some/path", nil) notFoundMW(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(tt.args.code) _, err := w.Write([]byte("success")) if err != nil { panic(fmt.Sprintf("failed to write on response: %s", err.Error())) } }, )).ServeHTTP(w, r) if w.Code != tt.wantCode { t.Fatalf( "notFoundMW().ServeHTTP() expected status code: %d, but got: %d\nresponse body: %s", tt.wantCode, w.Code, w.Body, ) } if tt.wantResponse != "" && tt.wantResponse != unmarshalResponse(w.Body)["response"] { t.Fatalf( "notFoundMW().ServeHTTP() handler expected response to contain: '%s', but got full response: '%s'", tt.wantResponse, w.Body.String(), ) } if tt.wantErrString != "" && !strings.Contains(w.Body.String(), tt.wantErrString) { t.Fatalf( "notFoundMW().ServeHTTP() expected response error to contain: '%s', but got: '%s'", tt.wantErrString, w.Body.String(), ) } }) } } func Test_allowedMethodsMW(t *testing.T) { t.Parallel() type args struct { allowedMethods []string } tests := []struct { name string args args wantResponse string wantErrString string wantCode int wantHandler bool }{ { "+valid", args{[]string{"POST"}}, "", "", http.StatusOK, true, }, { "+multipleMethods", args{[]string{"POST", "GET", "PATCH", "DELETE", "PUT"}}, "", "", http.StatusOK, true, }, { "-methodNowAllowed", args{[]string{"GET"}}, "fail", "Method Not Allowed", http.StatusMethodNotAllowed, false, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodPost, "/some/path", nil) handlerCalled := false handlerFunc := func(http.ResponseWriter, *http.Request) { handlerCalled = true } allowedMethodsMW(tt.args.allowedMethods, handlerFunc)(w, r) if w.Code != tt.wantCode { t.Fatalf( "allowedMethodsMW()() expected status code: %d, but got: %d\nresponse body: %s", tt.wantCode, w.Code, w.Body, ) } if tt.wantResponse != "" && tt.wantResponse != unmarshalResponse(w.Body)["response"] { t.Fatalf( "allowedMethodsMW()() handler expected response to contain: '%s', but got full response: '%s'", tt.wantResponse, w.Body.String(), ) } if tt.wantErrString != "" && !strings.Contains(w.Body.String(), tt.wantErrString) { t.Fatalf( "allowedMethodsMW()() expected response error to contain: '%s', but got: '%s'", tt.wantErrString, w.Body.String(), ) } if tt.wantHandler != handlerCalled { t.Fatalf( "allowedMethodsMW()() expected handler called status: %t, but got: '%t'", tt.wantHandler, handlerCalled, ) } }) } } func Test_errorHandlingMW(t *testing.T) { t.Parallel() type args struct { returnErr bool } tests := []struct { name string args args wantResponse string wantErrString string wantCode int }{ { "+valid", args{false}, "", "", http.StatusOK, }, { "-errorResponse", args{true}, "fail", "Handler error.", http.StatusInternalServerError, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodPost, "/some/path", nil) handlerFunc := func(http.ResponseWriter, *http.Request) error { if tt.args.returnErr { return errs.New("handler error") } return nil } errorHandlingMW(handlerFunc)(w, r) if w.Code != tt.wantCode { t.Fatalf( "errorHandlingMW()() expected status code: %d, but got: %d\nresponse body: %s", tt.wantCode, w.Code, w.Body, ) } if tt.wantResponse != "" && tt.wantResponse != unmarshalResponse(w.Body)["response"] { t.Fatalf( "errorHandlingMW()() handler expected response to contain: '%s', but got full response: '%s'", tt.wantResponse, w.Body.String(), ) } if tt.wantErrString != "" && !strings.Contains(w.Body.String(), tt.wantErrString) { t.Fatalf( "errorHandlingMW()() expected response error to contain: '%s', but got: '%s'", tt.wantErrString, w.Body.String(), ) } }) } } func Test_write(t *testing.T) { t.Parallel() type args struct { status int message string } tests := []struct { name string args args wantCode int wantResponse string }{ { "+valid", args{ http.StatusOK, "success", }, http.StatusOK, "success", }, { "-empty", args{ http.StatusBadRequest, "", }, http.StatusBadRequest, "", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() write(w, tt.args.status, tt.args.message) if w.Code != tt.wantCode { t.Fatalf( "write() expected status code: %d, but got: %d\nresponse body: %s", tt.wantCode, w.Code, w.Body, ) } if tt.wantResponse != "" && !strings.Contains(w.Body.String(), tt.wantResponse) { t.Fatalf( "write() expected response to contain: '%s', but got: '%s'", tt.wantResponse, w.Body.String(), ) } }) } } func Test_decodeEvents(t *testing.T) { t.Parallel() type args struct { events string } tests := []struct { name string args args want []event wantErr bool }{ { "+valid", args{ getRequestString( []map[string]any{ { "host": []string{"host_one", "host__two"}, "eventid": 23, "name": "event_one", }, { "host": []string{"host_three", "host_four"}, "eventid": 24, "name": "event_two", }, { "host": []string{"host_five", "host_six"}, "eventid": 25, "name": "event_three", }, }, ), }, []event{ {23, `{"eventid":23,"host":["host_one","host__two"],"name":"event_one"}`}, {24, `{"eventid":24,"host":["host_three","host_four"],"name":"event_two"}`}, {25, `{"eventid":25,"host":["host_five","host_six"],"name":"event_three"}`}, }, false, }, { "+single", args{ getRequestString( []map[string]any{ { "host": []string{"foo", "bar"}, "eventid": 23, "name": "Foobar", }, }, ), }, []event{ {23, `{"eventid":23,"host":["foo","bar"],"name":"Foobar"}`}, }, false, }, { "-malformedBody", args{"\"malformed"}, nil, true, }, { "-unmarshalErr", args{`"invalid":21`}, nil, true, }, { "-empty", args{}, nil, false, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := decodeEvents(strings.NewReader(tt.args.events)) if (err != nil) != tt.wantErr { t.Fatalf("decodeEvents() error = %v, wantErr %v", err, tt.wantErr) } if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("decodeEvents() = %s", diff) } }) } } func Test_decodeItems(t *testing.T) { t.Parallel() type args struct { items string } tests := []struct { name string args args want []item wantErr bool }{ { "+valid", args{ getRequestString( []map[string]any{ { "host": []string{"host_one", "host_two"}, "itemid": 23, "name": "item_one", }, { "host": []string{"host_three", "host_four"}, "itemid": 24, "name": "item_two", }, { "host": []string{"host_five", "host_six"}, "itemid": 25, "name": "item_three", }, }, ), }, []item{ {23, `{"host":["host_one","host_two"],"itemid":23,"name":"item_one"}`}, {24, `{"host":["host_three","host_four"],"itemid":24,"name":"item_two"}`}, {25, `{"host":["host_five","host_six"],"itemid":25,"name":"item_three"}`}, }, false, }, { "+single", args{ getRequestString( []map[string]any{ { "itemid": 23, "host": []string{"foo", "bar"}, "name": "Foobar", }, }, ), }, []item{ {23, `{"host":["foo","bar"],"itemid":23,"name":"Foobar"}`}, }, false, }, { "-malformedBody", args{"\"malformed"}, nil, true, }, { "-unmarshalErr", args{`"invalid":21`}, nil, true, }, { "-empty", args{}, nil, false, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := decodeItems(strings.NewReader(tt.args.items)) if (err != nil) != tt.wantErr { t.Fatalf("decodeItems() error = %v, wantErr %v", err, tt.wantErr) } if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("decodeItems() = %s", diff) } }) } } func Test_validateTLS(t *testing.T) { t.Parallel() type args struct { certPath string keyPath string } tests := []struct { name string args args wantErr bool }{ { "+valid", args{ "path/to/cert", "path/to/key", }, false, }, { "-missingKey", args{ "path/to/cert", "", }, true, }, { "-missingCert", args{ "", "path/to/key", }, true, }, { "-missingBoth", args{ "", "", }, true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := validateTLS(tt.args.certPath, tt.args.keyPath); (err != nil) != tt.wantErr { t.Fatalf("validateTLS() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_jsonResponse(t *testing.T) { t.Parallel() type args struct { msg map[string]string } tests := []struct { name string args args want string }{ { "+valid", args{ map[string]string{ "single": "message", }, }, `{"single":"message"}`, }, { "+multiple", args{ map[string]string{ "first": "foo", "second": "bar", }, }, `{"first":"foo","second":"bar"}`, }, { "-nil", args{ nil, }, "null", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got := jsonResponse(tt.args.msg) if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("jsonResponse() = %s", diff) } }) } } func getRequestString(data []map[string]any) string { ndjson := new(bytes.Buffer) encoder := json.NewEncoder(ndjson) for _, e := range data { err := encoder.Encode(e) if err != nil { panic(fmt.Sprintf("failed to prepare unit test values: %s", err.Error())) } } return ndjson.String() } func unmarshalResponse(b *bytes.Buffer) map[string]string { out := map[string]string{} err := json.Unmarshal(b.Bytes(), &out) if err != nil { panic(fmt.Sprintf("failed to unmarshal response: %s", err.Error())) } return out }