v0.6.0 auth restructuring, scram auth added, more tests added

This commit is contained in:
2025-10-02 17:16:43 -04:00
parent 3047e556f7
commit 490fb777ab
37 changed files with 2283 additions and 888 deletions

View File

@ -28,7 +28,7 @@ func NewFormatter(name string, options map[string]any, logger *log.Logger) (Form
switch name {
case "json":
return NewJSONFormatter(options, logger)
case "text":
case "txt":
return NewTextFormatter(options, logger)
case "raw":
return NewRawFormatter(options, logger)

View File

@ -0,0 +1,65 @@
// FILE: logwisp/src/internal/format/format_test.go
package format
import (
"testing"
"github.com/lixenwraith/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestLogger() *log.Logger {
return log.NewLogger()
}
func TestNewFormatter(t *testing.T) {
logger := newTestLogger()
testCases := []struct {
name string
formatName string
expected string
expectError bool
}{
{
name: "JSONFormatter",
formatName: "json",
expected: "json",
},
{
name: "TextFormatter",
formatName: "txt",
expected: "txt",
},
{
name: "RawFormatter",
formatName: "raw",
expected: "raw",
},
{
name: "DefaultToRaw",
formatName: "",
expected: "raw",
},
{
name: "UnknownFormatter",
formatName: "xml",
expectError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
formatter, err := NewFormatter(tc.formatName, nil, logger)
if tc.expectError {
assert.Error(t, err)
assert.Nil(t, formatter)
} else {
require.NoError(t, err)
require.NotNil(t, formatter)
assert.Equal(t, tc.expected, formatter.Name())
}
})
}
}

View File

@ -0,0 +1,129 @@
// FILE: logwisp/src/internal/format/json_test.go
package format
import (
"encoding/json"
"strings"
"testing"
"time"
"logwisp/src/internal/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestJSONFormatter_Format(t *testing.T) {
logger := newTestLogger()
testTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)
entry := core.LogEntry{
Time: testTime,
Source: "test-app",
Level: "INFO",
Message: "this is a test",
}
t.Run("BasicFormatting", func(t *testing.T) {
formatter, err := NewJSONFormatter(nil, logger)
require.NoError(t, err)
output, err := formatter.Format(entry)
require.NoError(t, err)
var result map[string]interface{}
err = json.Unmarshal(output, &result)
require.NoError(t, err, "Output should be valid JSON")
assert.Equal(t, testTime.Format(time.RFC3339Nano), result["timestamp"])
assert.Equal(t, "INFO", result["level"])
assert.Equal(t, "test-app", result["source"])
assert.Equal(t, "this is a test", result["message"])
assert.True(t, strings.HasSuffix(string(output), "\n"), "Output should end with a newline")
})
t.Run("PrettyFormatting", func(t *testing.T) {
formatter, err := NewJSONFormatter(map[string]any{"pretty": true}, logger)
require.NoError(t, err)
output, err := formatter.Format(entry)
require.NoError(t, err)
assert.Contains(t, string(output), ` "level": "INFO"`)
assert.True(t, strings.HasSuffix(string(output), "\n"))
})
t.Run("MessageIsJSON", func(t *testing.T) {
jsonMessageEntry := entry
jsonMessageEntry.Message = `{"user":"test","request_id":"abc-123"}`
formatter, err := NewJSONFormatter(nil, logger)
require.NoError(t, err)
output, err := formatter.Format(jsonMessageEntry)
require.NoError(t, err)
var result map[string]interface{}
err = json.Unmarshal(output, &result)
require.NoError(t, err)
assert.Equal(t, "test", result["user"])
assert.Equal(t, "abc-123", result["request_id"])
_, messageExists := result["message"]
assert.False(t, messageExists, "message field should not exist when message is merged JSON")
})
t.Run("MessageIsJSONWithConflicts", func(t *testing.T) {
jsonMessageEntry := entry
jsonMessageEntry.Level = "INFO" // top-level
jsonMessageEntry.Message = `{"level":"DEBUG","msg":"hello"}`
formatter, err := NewJSONFormatter(nil, logger)
require.NoError(t, err)
output, err := formatter.Format(jsonMessageEntry)
require.NoError(t, err)
var result map[string]interface{}
err = json.Unmarshal(output, &result)
require.NoError(t, err)
assert.Equal(t, "INFO", result["level"], "Top-level LogEntry field should take precedence")
})
t.Run("CustomFieldNames", func(t *testing.T) {
options := map[string]any{"timestamp_field": "@timestamp"}
formatter, err := NewJSONFormatter(options, logger)
require.NoError(t, err)
output, err := formatter.Format(entry)
require.NoError(t, err)
var result map[string]interface{}
err = json.Unmarshal(output, &result)
require.NoError(t, err)
_, defaultExists := result["timestamp"]
assert.False(t, defaultExists)
assert.Equal(t, testTime.Format(time.RFC3339Nano), result["@timestamp"])
})
}
func TestJSONFormatter_FormatBatch(t *testing.T) {
logger := newTestLogger()
formatter, err := NewJSONFormatter(nil, logger)
require.NoError(t, err)
entries := []core.LogEntry{
{Time: time.Now(), Level: "INFO", Message: "First message"},
{Time: time.Now(), Level: "WARN", Message: "Second message"},
}
output, err := formatter.FormatBatch(entries)
require.NoError(t, err)
var result []map[string]interface{}
err = json.Unmarshal(output, &result)
require.NoError(t, err, "Batch output should be a valid JSON array")
require.Len(t, result, 2)
assert.Equal(t, "First message", result[0]["message"])
assert.Equal(t, "WARN", result[1]["level"])
}

View File

@ -0,0 +1,29 @@
// FILE: logwisp/src/internal/format/raw_test.go
package format
import (
"testing"
"time"
"logwisp/src/internal/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRawFormatter_Format(t *testing.T) {
logger := newTestLogger()
formatter, err := NewRawFormatter(nil, logger)
require.NoError(t, err)
entry := core.LogEntry{
Time: time.Now(),
Message: "This is a raw log line.",
}
output, err := formatter.Format(entry)
require.NoError(t, err)
expected := "This is a raw log line.\n"
assert.Equal(t, expected, string(output))
}

View File

@ -104,5 +104,5 @@ func (f *TextFormatter) Format(entry core.LogEntry) ([]byte, error) {
// Returns the formatter name
func (f *TextFormatter) Name() string {
return "text"
return "txt"
}

View File

@ -0,0 +1,81 @@
// FILE: logwisp/src/internal/format/text_test.go
package format
import (
"fmt"
"strings"
"testing"
"time"
"logwisp/src/internal/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewTextFormatter(t *testing.T) {
logger := newTestLogger()
t.Run("InvalidTemplate", func(t *testing.T) {
options := map[string]any{"template": "{{ .Timestamp | InvalidFunc }}"}
_, err := NewTextFormatter(options, logger)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid template")
})
}
func TestTextFormatter_Format(t *testing.T) {
logger := newTestLogger()
testTime := time.Date(2023, 10, 27, 10, 30, 0, 0, time.UTC)
entry := core.LogEntry{
Time: testTime,
Source: "api",
Level: "WARN",
Message: "rate limit exceeded",
}
t.Run("DefaultTemplate", func(t *testing.T) {
formatter, err := NewTextFormatter(nil, logger)
require.NoError(t, err)
output, err := formatter.Format(entry)
require.NoError(t, err)
expected := fmt.Sprintf("[%s] [WARN] api - rate limit exceeded\n", testTime.Format(time.RFC3339))
assert.Equal(t, expected, string(output))
})
t.Run("CustomTemplate", func(t *testing.T) {
options := map[string]any{"template": "{{.Level}}:{{.Source}}:{{.Message}}"}
formatter, err := NewTextFormatter(options, logger)
require.NoError(t, err)
output, err := formatter.Format(entry)
require.NoError(t, err)
expected := "WARN:api:rate limit exceeded\n"
assert.Equal(t, expected, string(output))
})
t.Run("CustomTimestampFormat", func(t *testing.T) {
options := map[string]any{"timestamp_format": "2006-01-02"}
formatter, err := NewTextFormatter(options, logger)
require.NoError(t, err)
output, err := formatter.Format(entry)
require.NoError(t, err)
assert.True(t, strings.HasPrefix(string(output), "[2023-10-27]"))
})
t.Run("EmptyLevelDefaultsToInfo", func(t *testing.T) {
emptyLevelEntry := entry
emptyLevelEntry.Level = ""
formatter, err := NewTextFormatter(nil, logger)
require.NoError(t, err)
output, err := formatter.Format(emptyLevelEntry)
require.NoError(t, err)
assert.Contains(t, string(output), "[INFO]")
})
}