Files
onix/pkg/log/log_test.go
MohitKatare-protean c70ff10843 update on the review comments
1. change ContextKeyModelId to ContextKeyModuleId
2. added ContextKeyTxnId to the constants.
2025-04-08 15:05:07 +05:30

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.ContextKeyModuleID,
},
}
// 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)
}
})
}
}