diff --git a/core/logger/api_client_adapter_test.go b/core/logger/api_client_adapter_test.go index 4ad51e7..eb2e6ca 100644 --- a/core/logger/api_client_adapter_test.go +++ b/core/logger/api_client_adapter_test.go @@ -10,7 +10,7 @@ import ( ) func TestAPIClientAdapter(t *testing.T) { - log := newJSONBufferedLogger() + log := newJSONBufferedLogger(nil) client := retailcrm.New("https://example.com", "test_key").WithLogger(APIClientAdapter(log.Logger())) client.Debug = true diff --git a/core/logger/attrs_test.go b/core/logger/attrs_test.go new file mode 100644 index 0000000..c3fdcf5 --- /dev/null +++ b/core/logger/attrs_test.go @@ -0,0 +1,155 @@ +package logger + +import ( + "bytes" + "errors" + "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "io" + "net/http" + "testing" +) + +func TestErr(t *testing.T) { + var cases = []struct { + source interface{} + expected string + }{ + { + source: nil, + expected: "", + }, + { + source: errors.New("untimely error"), + expected: "untimely error", + }, + } + + for _, c := range cases { + val := Err(c.source) + assert.Equal(t, c.expected, func() string { + if val.String != "" { + return val.String + } + if val.Interface != nil { + return fmt.Sprintf("%s", val.Interface) + } + return "" + }()) + assert.Equal(t, ErrorAttr, val.Key) + } +} + +func TestHandler(t *testing.T) { + val := Handler("handlerName") + assert.Equal(t, HandlerAttr, val.Key) + assert.Equal(t, "handlerName", val.String) +} + +func TestHTTPStatusCode(t *testing.T) { + val := HTTPStatusCode(http.StatusOK) + assert.Equal(t, HTTPStatusAttr, val.Key) + assert.Equal(t, http.StatusOK, int(val.Integer)) +} + +func TestHTTPStatusName(t *testing.T) { + val := HTTPStatusName(http.StatusOK) + assert.Equal(t, HTTPStatusNameAttr, val.Key) + assert.Equal(t, http.StatusText(http.StatusOK), val.String) +} + +func TestBody(t *testing.T) { + var cases = []struct { + input interface{} + result interface{} + }{ + { + input: "", + result: nil, + }, + { + input: nil, + result: nil, + }, + { + input: "ooga booga", + result: "ooga booga", + }, + { + input: `{"success":true}`, + result: map[string]interface{}{"success": true}, + }, + { + input: []byte{}, + result: nil, + }, + { + input: nil, + result: nil, + }, + { + input: []byte("ooga booga"), + result: "ooga booga", + }, + { + input: []byte(`{"success":true}`), + result: map[string]interface{}{"success": true}, + }, + { + input: newReaderMock(func(p []byte) (n int, err error) { + return 0, io.EOF + }), + result: nil, + }, + { + input: newReaderMockData([]byte{}), + result: nil, + }, + { + input: newReaderMockData([]byte("ooga booga")), + result: "ooga booga", + }, + + { + input: newReaderMockData([]byte(`{"success":true}`)), + result: map[string]interface{}{"success": true}, + }, + } + for _, c := range cases { + val := Body(c.input) + assert.Equal(t, BodyAttr, val.Key) + + switch assertion := c.result.(type) { + case string: + assert.Equal(t, assertion, val.String) + case int: + assert.Equal(t, assertion, int(val.Integer)) + default: + assert.Equal(t, c.result, val.Interface) + } + } +} + +type readerMock struct { + mock.Mock +} + +func newReaderMock(cb func(p []byte) (n int, err error)) io.Reader { + r := &readerMock{} + r.On("Read", mock.Anything).Return(cb) + return r +} + +func newReaderMockData(data []byte) io.Reader { + return newReaderMock(bytes.NewReader(data).Read) +} + +func (m *readerMock) Read(p []byte) (n int, err error) { + args := m.Called(p) + out := args.Get(0) + if cb, ok := out.(func(p []byte) (n int, err error)); ok { + return cb(p) + } + return args.Int(0), args.Error(1) +} diff --git a/core/logger/buffer_logger_test.go b/core/logger/buffer_logger_test.go index 7c0b193..a36e5db 100644 --- a/core/logger/buffer_logger_test.go +++ b/core/logger/buffer_logger_test.go @@ -29,8 +29,10 @@ type jSONRecordScanner struct { buf *bufferLogger } -func newJSONBufferedLogger() *jSONRecordScanner { - buf := newBufferLogger() +func newJSONBufferedLogger(buf *bufferLogger) *jSONRecordScanner { + if buf == nil { + buf = newBufferLogger() + } return &jSONRecordScanner{scan: bufio.NewScanner(buf), buf: buf} } diff --git a/core/logger/default_test.go b/core/logger/default_test.go new file mode 100644 index 0000000..acd4f47 --- /dev/null +++ b/core/logger/default_test.go @@ -0,0 +1,89 @@ +package logger + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "go.uber.org/zap" + "testing" +) + +type TestDefaultSuite struct { + suite.Suite +} + +func TestDefault(t *testing.T) { + suite.Run(t, new(TestDefaultSuite)) +} + +func (s *TestDefaultSuite) TestNewDefault_OK() { + jsonLog := NewDefault("json", false) + consoleLog := NewDefault("console", true) + + s.Assert().NotNil(jsonLog) + s.Assert().NotNil(consoleLog) +} + +func (s *TestDefaultSuite) TestNewDefault_Panic() { + s.Assert().PanicsWithValue("unknown logger format: rar", func() { + NewDefault("rar", false) + }) +} + +func (s *TestDefaultSuite) TestWith() { + log := newBufferLogger() + log.With(zap.String(HandlerAttr, "Handler")).Info("test") + items, err := newJSONBufferedLogger(log).ScanAll() + + s.Require().NoError(err) + s.Require().Len(items, 1) + s.Assert().Equal("Handler", items[0].Handler) +} + +func (s *TestDefaultSuite) TestWithLazy() { + log := newBufferLogger() + log.WithLazy(zap.String(HandlerAttr, "Handler")).Info("test") + items, err := newJSONBufferedLogger(log).ScanAll() + + s.Require().NoError(err) + s.Require().Len(items, 1) + s.Assert().Equal("Handler", items[0].Handler) +} + +func (s *TestDefaultSuite) TestForHandler() { + log := newBufferLogger() + log.ForHandler("Handler").Info("test") + items, err := newJSONBufferedLogger(log).ScanAll() + + s.Require().NoError(err) + s.Require().Len(items, 1) + s.Assert().Equal("Handler", items[0].Handler) +} + +func (s *TestDefaultSuite) TestForConnection() { + log := newBufferLogger() + log.ForConnection("connection").Info("test") + items, err := newJSONBufferedLogger(log).ScanAll() + + s.Require().NoError(err) + s.Require().Len(items, 1) + s.Assert().Equal("connection", items[0].Connection) +} + +func (s *TestDefaultSuite) TestForAccount() { + log := newBufferLogger() + log.ForAccount("account").Info("test") + items, err := newJSONBufferedLogger(log).ScanAll() + + s.Require().NoError(err) + s.Require().Len(items, 1) + s.Assert().Equal("account", items[0].Account) +} + +func TestAnyZapFields(t *testing.T) { + fields := AnyZapFields([]interface{}{zap.String("k0", "v0"), "ooga", "booga"}) + require.Len(t, fields, 3) + assert.Equal(t, zap.String("k0", "v0"), fields[0]) + assert.Equal(t, zap.String("arg1", "ooga"), fields[1]) + assert.Equal(t, zap.String("arg2", "booga"), fields[2]) +} diff --git a/core/logger/gin_test.go b/core/logger/gin_test.go new file mode 100644 index 0000000..3c04067 --- /dev/null +++ b/core/logger/gin_test.go @@ -0,0 +1,37 @@ +package logger + +import ( + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "testing" +) + +func TestGinMiddleware(t *testing.T) { + log := newBufferLogger() + rr := httptest.NewRecorder() + r := gin.New() + r.Use(GinMiddleware(log)) + r.GET("/mine", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{}) + }) + r.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/mine", nil)) + + require.Equal(t, http.StatusOK, rr.Code) + items, err := newJSONBufferedLogger(log).ScanAll() + require.NoError(t, err) + require.Len(t, items, 1) + require.NotEmpty(t, items[0].Context) + assert.NotEmpty(t, items[0].Context["startTime"]) + assert.NotEmpty(t, items[0].Context["endTime"]) + assert.True(t, func() bool { + _, ok := items[0].Context["latency"] + return ok + }()) + assert.NotEmpty(t, items[0].Context["remoteAddress"]) + assert.NotEmpty(t, items[0].Context[HTTPMethodAttr]) + assert.NotEmpty(t, items[0].Context["path"]) + assert.NotEmpty(t, items[0].Context["bodySize"]) +} diff --git a/core/logger/mg_transport_client_adapter_test.go b/core/logger/mg_transport_client_adapter_test.go index 262e389..e88aec0 100644 --- a/core/logger/mg_transport_client_adapter_test.go +++ b/core/logger/mg_transport_client_adapter_test.go @@ -11,7 +11,7 @@ import ( func TestMGTransportClientAdapter(t *testing.T) { httpClient := &http.Client{} - log := newJSONBufferedLogger() + log := newJSONBufferedLogger(nil) client := v1.NewWithClient("https://mg.dev", "test_token", httpClient). WithLogger(MGTransportClientAdapter(log.Logger())) client.Debug = true diff --git a/core/logger/pool_test.go b/core/logger/pool_test.go new file mode 100644 index 0000000..5509847 --- /dev/null +++ b/core/logger/pool_test.go @@ -0,0 +1,18 @@ +package logger + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPool(t *testing.T) { + p := NewPool[*uint8](func() *uint8 { + item := uint8(22) + return &item + }) + + val := p.Get() + assert.Equal(t, uint8(22), *val) + assert.Equal(t, uint8(22), *p.Get()) + p.Put(val) +} diff --git a/core/logger/writer_adapter_test.go b/core/logger/writer_adapter_test.go new file mode 100644 index 0000000..8007e02 --- /dev/null +++ b/core/logger/writer_adapter_test.go @@ -0,0 +1,24 @@ +package logger + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "testing" +) + +func TestWriterAdapter(t *testing.T) { + log := newBufferLogger() + adapter := WriterAdapter(log, zap.InfoLevel) + + msg := []byte("hello world") + total, err := adapter.Write(msg) + require.NoError(t, err) + require.Equal(t, total, len(msg)) + + items, err := newJSONBufferedLogger(log).ScanAll() + require.NoError(t, err) + require.Len(t, items, 1) + assert.Equal(t, "hello world", items[0].Message) + assert.Equal(t, "INFO", items[0].LevelName) +} diff --git a/core/logger/zabbix_collector_adapter.go b/core/logger/zabbix_collector_adapter.go index e81345e..f33face 100644 --- a/core/logger/zabbix_collector_adapter.go +++ b/core/logger/zabbix_collector_adapter.go @@ -13,10 +13,10 @@ type zabbixCollectorAdapter struct { func (a *zabbixCollectorAdapter) Errorf(format string, args ...interface{}) { baseMsg := "cannot send metrics to Zabbix" switch format { - case "cannot send metrics to Zabbix: %v": - baseMsg = "cannot stop collector" - fallthrough case "cannot stop collector: %s": + baseMsg = "cannot stop Zabbix collector" + fallthrough + case "cannot send metrics to Zabbix: %v": var err interface{} if len(args) > 0 { err = args[0] diff --git a/core/logger/zabbix_collector_adapter_test.go b/core/logger/zabbix_collector_adapter_test.go new file mode 100644 index 0000000..bd3633d --- /dev/null +++ b/core/logger/zabbix_collector_adapter_test.go @@ -0,0 +1,25 @@ +package logger + +import ( + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestZabbixCollectorAdapter(t *testing.T) { + log := newBufferLogger() + adapter := ZabbixCollectorAdapter(log) + adapter.Errorf("highly unexpected error: %s", "unexpected error") + adapter.Errorf("cannot stop collector: %s", "app error") + adapter.Errorf("cannot send metrics to Zabbix: %v", errors.New("send error")) + + items, err := newJSONBufferedLogger(log).ScanAll() + require.NoError(t, err) + require.Len(t, items, 3) + assert.Equal(t, "highly unexpected error: unexpected error", items[0].Message) + assert.Equal(t, "cannot stop Zabbix collector", items[1].Message) + assert.Equal(t, "app error", items[1].Context[ErrorAttr]) + assert.Equal(t, "cannot send metrics to Zabbix", items[2].Message) + assert.Equal(t, "send error", items[2].Context[ErrorAttr]) +} diff --git a/core/util/testutil/buffer_logger.go b/core/util/testutil/buffer_logger.go index 9df5871..5659ede 100644 --- a/core/util/testutil/buffer_logger.go +++ b/core/util/testutil/buffer_logger.go @@ -26,6 +26,16 @@ type BufferedLogger interface { } // BufferLogger is an implementation of the BufferedLogger. +// +// BufferLogger can be used in tests to match specific log messages. It uses JSON by default (hardcoded for now). +// It implements fmt.Stringer and provides an adapter to the underlying buffer, which means it can also return +// Bytes(), can be used like io.Reader and can be cleaned using Reset() method. +// +// Usage: +// +// log := NewBufferedLogger() +// // Some other code that works with logger. +// fmt.Println(log.String()) type BufferLogger struct { logger.Default buf LockableBuffer