1. added logs for contextKey parsing and unmarshaling yml file. 2. resolved router test case issue. 2. resolved logger test case issues.
675 lines
16 KiB
Go
675 lines
16 KiB
Go
package log
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/beckn/beckn-onix/pkg/model"
|
|
)
|
|
|
|
type ctxKey any
|
|
|
|
var requestID ctxKey = "requestID"
|
|
var userID ctxKey = "userID"
|
|
|
|
const testLogFilePath = "./test_logs/test.log"
|
|
|
|
func TestMain(m *testing.M) {
|
|
// Create a single temporary directory for all tests
|
|
var err error
|
|
dir := filepath.Dir(testLogFilePath)
|
|
err = os.MkdirAll(dir, os.ModePerm)
|
|
if err != nil {
|
|
panic("failed to create test log directory: " + err.Error())
|
|
}
|
|
|
|
// Run all tests
|
|
code := m.Run()
|
|
|
|
// Cleanup: Remove the log directory after all tests finish
|
|
err = os.RemoveAll(dir)
|
|
if err != nil {
|
|
println("failed to clean up test log directory: ", err.Error())
|
|
}
|
|
|
|
// Exit with the appropriate exit code
|
|
os.Exit(code)
|
|
}
|
|
|
|
func setupLogger(t *testing.T, l level) string {
|
|
t.Helper()
|
|
|
|
// Create a temporary directory for logs.
|
|
|
|
config := Config{
|
|
Level: l,
|
|
Destinations: []destination{
|
|
{
|
|
Type: File,
|
|
Config: map[string]string{
|
|
"path": testLogFilePath,
|
|
"maxSize": "1",
|
|
"maxAge": "1",
|
|
"maxBackup": "1",
|
|
"compress": "false",
|
|
},
|
|
},
|
|
},
|
|
ContextKeys: []model.ContextKey{
|
|
model.ContextKeyTxnID,
|
|
model.ContextKeyMsgID,
|
|
model.ContextKeySubscriberID,
|
|
model.ContextKeyModelID,
|
|
},
|
|
}
|
|
|
|
// Initialize logger with the given config
|
|
err := InitLogger(config)
|
|
if err != nil {
|
|
t.Fatalf("failed to initialize logger: %v", err)
|
|
}
|
|
|
|
return testLogFilePath
|
|
}
|
|
|
|
func readLogFile(t *testing.T, logPath string) []string {
|
|
t.Helper()
|
|
b, err := os.ReadFile(logPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read log file: %v", err)
|
|
}
|
|
return strings.Split(string(b), "\n")
|
|
}
|
|
|
|
func parseLogLine(t *testing.T, line string) map[string]interface{} {
|
|
t.Helper()
|
|
var logEntry map[string]interface{}
|
|
err := json.Unmarshal([]byte(line), &logEntry)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse log line: %v", err)
|
|
}
|
|
return logEntry
|
|
}
|
|
|
|
func TestDebug(t *testing.T) {
|
|
t.Helper()
|
|
logPath := setupLogger(t, DebugLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Debug(ctx, "Debug message")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "debug",
|
|
"subscriber_id": "12345",
|
|
"message": "Debug message",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Errorf("Expected Debug message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestInfo(t *testing.T) {
|
|
logPath := setupLogger(t, InfoLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Info(ctx, "Info message")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "info",
|
|
"subscriber_id": "12345",
|
|
"message": "Info message",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Errorf("expected Info message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestWarn(t *testing.T) {
|
|
logPath := setupLogger(t, WarnLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Warn(ctx, "Warning message")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "warn",
|
|
"subscriber_id": "12345",
|
|
"message": "Warning message",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
delete(logEntry, "time")
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Warning message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestError(t *testing.T) {
|
|
logPath := setupLogger(t, ErrorLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Error(ctx, fmt.Errorf("test error"), "Error message")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "error",
|
|
"subscriber_id": "12345",
|
|
"message": "Error message",
|
|
"error": "test error",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Error message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestRequest(t *testing.T) {
|
|
logPath := setupLogger(t, InfoLevel)
|
|
ctx := context.WithValue(context.Background(), requestID, "abc-123")
|
|
req, _ := http.NewRequest("POST", "/api/test", bytes.NewBuffer([]byte(`{"key":"value"}`)))
|
|
req.RemoteAddr = "127.0.0.1:8080"
|
|
Request(ctx, req, []byte(`{"key":"value"}`))
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
if logEntry["message"] == "HTTP Request" || logEntry["method"] == "POST" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected formatted debug message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestResponse(t *testing.T) {
|
|
logPath := setupLogger(t, InfoLevel)
|
|
ctx := context.WithValue(context.Background(), requestID, "abc-123")
|
|
req, _ := http.NewRequest("GET", "/api/test", nil)
|
|
Response(ctx, req, 200, time.Millisecond*123)
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
if logEntry["message"] == "HTTP Response" {
|
|
if logEntry["message"] == "HTTP Response" {
|
|
value, ok := logEntry["statusCode"]
|
|
if !ok {
|
|
t.Fatalf("Expected key 'statusCode' not found in log entry")
|
|
}
|
|
statusCode, ok := value.(float64)
|
|
if !ok {
|
|
t.Fatalf("Value for 'statusCode' is not a float64, found: %T", value)
|
|
}
|
|
if statusCode == 200 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestFatal(t *testing.T) {
|
|
logPath := setupLogger(t, FatalLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Fatal(ctx, fmt.Errorf("fatal error"), "Fatal message")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "fatal",
|
|
"subscriber_id": "12345",
|
|
"message": "Fatal message",
|
|
"error": "fatal error",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Fatal message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestPanic(t *testing.T) {
|
|
logPath := setupLogger(t, PanicLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Panic(ctx, fmt.Errorf("panic error"), "Panic message")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "panic",
|
|
"subscriber_id": "12345",
|
|
"message": "Panic message",
|
|
"error": "panic error",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Panic message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestDebugf(t *testing.T) {
|
|
logPath := setupLogger(t, DebugLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Debugf(ctx, "Debugf message: %s", "test")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "debug",
|
|
"subscriber_id": "12345",
|
|
"message": "Debugf message: test",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
t.Log(line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected formatted debug message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestInfof(t *testing.T) {
|
|
logPath := setupLogger(t, InfoLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Infof(ctx, "Infof message: %s", "test")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "info",
|
|
"subscriber_id": "12345",
|
|
"message": "Infof message: test",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Infof message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestWarnf(t *testing.T) {
|
|
logPath := setupLogger(t, WarnLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
Warnf(ctx, "Warnf message: %s", "test")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "warn",
|
|
"subscriber_id": "12345",
|
|
"message": "Warnf message: test",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Warnf message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestErrorf(t *testing.T) {
|
|
logPath := setupLogger(t, ErrorLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
err := fmt.Errorf("error message")
|
|
Errorf(ctx, err, "Errorf message: %s", "test")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "error",
|
|
"subscriber_id": "12345",
|
|
"message": "Errorf message: test",
|
|
"error": "error message",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Errorf message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestFatalf(t *testing.T) {
|
|
logPath := setupLogger(t, FatalLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
err := fmt.Errorf("fatal error")
|
|
Fatalf(ctx, err, "Fatalf message: %s", "test")
|
|
lines := readLogFile(t, logPath)
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "fatal",
|
|
"subscriber_id": "12345",
|
|
"message": "Fatalf message: test",
|
|
"error": "fatal error",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected Fatalf message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestPanicf(t *testing.T) {
|
|
logPath := setupLogger(t, PanicLevel)
|
|
ctx := context.WithValue(context.Background(), model.ContextKeySubscriberID, "12345")
|
|
err := fmt.Errorf("panic error")
|
|
Panicf(ctx, err, "Panicf message: %s", "test")
|
|
lines := readLogFile(t, logPath)
|
|
|
|
if len(lines) == 0 {
|
|
t.Fatal("No logs were written.")
|
|
}
|
|
expected := map[string]interface{}{
|
|
"level": "panic",
|
|
"subscriber_id": "12345",
|
|
"message": "Panicf message: test",
|
|
"error": "panic error",
|
|
}
|
|
|
|
var found bool
|
|
for _, line := range lines {
|
|
logEntry := parseLogLine(t, line)
|
|
// Ignore 'time' while comparing
|
|
delete(logEntry, "time")
|
|
|
|
if reflect.DeepEqual(expected, logEntry) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Errorf("expected Panicf message, but it was not found in logs")
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config Config
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "Valid config with Stdout",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{
|
|
{Type: Stdout},
|
|
},
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "Valid config with File destination and valid path",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{
|
|
{
|
|
Type: File,
|
|
Config: map[string]string{
|
|
"path": "./logs/app.log",
|
|
"maxSize": "10",
|
|
"maxBackups": "5",
|
|
"maxAge": "7",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "Error: Invalid log level",
|
|
config: Config{
|
|
Level: "invalid",
|
|
Destinations: []destination{
|
|
{Type: Stdout},
|
|
},
|
|
},
|
|
wantErr: ErrInvalidLogLevel,
|
|
},
|
|
{
|
|
name: "Error: No destinations provided",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{},
|
|
},
|
|
wantErr: ErrLogDestinationNil,
|
|
},
|
|
{
|
|
name: "Error: Invalid destination type",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{
|
|
{Type: "unknown"},
|
|
},
|
|
},
|
|
wantErr: fmt.Errorf("invalid destination type 'unknown'"),
|
|
},
|
|
{
|
|
name: "Error: Missing file path for file destination",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{
|
|
{
|
|
Type: File,
|
|
Config: map[string]string{
|
|
"maxSize": "10",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: ErrMissingFilePath,
|
|
},
|
|
{
|
|
name: "Error: Invalid maxSize value in file destination",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{
|
|
{
|
|
Type: File,
|
|
Config: map[string]string{
|
|
"path": "./logs/app.log",
|
|
"maxSize": "invalid",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: errors.New(`invalid maxSize: strconv.Atoi: parsing "invalid": invalid syntax`),
|
|
},
|
|
{
|
|
name: "Error: Invalid maxBackups value in file destination",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{
|
|
{
|
|
Type: File,
|
|
Config: map[string]string{
|
|
"path": "./logs/app.log",
|
|
"maxBackups": "invalid",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: errors.New(`invalid maxBackups: strconv.Atoi: parsing "invalid": invalid syntax`),
|
|
},
|
|
{
|
|
name: "Error: Invalid maxAge value in file destination",
|
|
config: Config{
|
|
Level: InfoLevel,
|
|
Destinations: []destination{
|
|
{
|
|
Type: File,
|
|
Config: map[string]string{
|
|
"path": "./logs/app.log",
|
|
"maxAge": "invalid",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: errors.New(`invalid maxAge: strconv.Atoi: parsing "invalid": invalid syntax`),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.config.validate()
|
|
if (err == nil) != (tt.wantErr == nil) {
|
|
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
if err != nil && tt.wantErr != nil && err.Error() != tt.wantErr.Error() {
|
|
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|