v0.1.8 tests converted to standard library from testify

This commit is contained in:
2026-07-24 13:48:21 -04:00
parent 5553aeaec4
commit 24b7deebdb
19 changed files with 2399 additions and 1795 deletions
+112 -83
View File
@@ -1,46 +1,23 @@
// This file tests the integration between log package and formatter package
package log
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestLoggerFormatterIntegration verifies logger correctly uses the new formatter package
func TestLoggerFormatterIntegration(t *testing.T) {
// Tests the integration between the log package and the formatter/sanitizer packages.
// TestFormatterIntegration verifies each format reaches the file writer intact.
func TestFormatterIntegration(t *testing.T) {
tests := []struct {
name string
format string
check func(t *testing.T, content string)
checks []string
}{
{
name: "txt format",
format: "txt",
check: func(t *testing.T, content string) {
assert.Contains(t, content, `INFO "test message"`)
},
},
{
name: "json format",
format: "json",
check: func(t *testing.T, content string) {
assert.Contains(t, content, `"level":"INFO"`)
assert.Contains(t, content, `"fields":["test message"]`)
},
},
{
name: "raw format",
format: "raw",
check: func(t *testing.T, content string) {
assert.Contains(t, content, "test message")
},
},
{"txt", "txt", []string{`INFO "test message"`}},
{"json", "json", []string{`"level":"INFO"`, `"fields":["test message"]`}},
{"raw", "raw", []string{"test message"}},
}
for _, tt := range tests {
@@ -53,41 +30,69 @@ func TestLoggerFormatterIntegration(t *testing.T) {
cfg.Format = tt.format
cfg.ShowTimestamp = false
cfg.ShowLevel = true
cfg.EnableConsole = false
cfg.EnableFile = true
cfg.FlushIntervalMs = 10
err := logger.ApplyConfig(cfg)
require.NoError(t, err)
err = logger.Start()
require.NoError(t, err)
defer logger.Shutdown()
mustNoErr(t, logger.ApplyConfig(cfg), "ApplyConfig")
mustNoErr(t, logger.Start(), "Start")
t.Cleanup(func() { _ = logger.Shutdown() })
logger.Info("test message")
mustNoErr(t, logger.Flush(time.Second), "Flush")
err = logger.Flush(time.Second)
require.NoError(t, err)
mustEventually(t, time.Second, "record written", func() bool {
return len(readLog(t, tmpDir)) > 0
})
content, err := os.ReadFile(filepath.Join(tmpDir, "log.log"))
require.NoError(t, err)
tt.check(t, string(content))
content := readLog(t, tmpDir)
for _, want := range tt.checks {
contains(t, content, want, tt.format+" output")
}
})
}
}
// TestControlCharacterWriteWithFormatter verifies control character handling through formatter
func TestControlCharacterWriteWithFormatter(t *testing.T) {
logger, tmpDir := createTestLogger(t)
defer logger.Shutdown()
// TestStructuredJSONOutput verifies FlagStructuredJSON emits a message key and a
// marshaled field object rather than a positional fields array.
func TestStructuredJSONOutput(t *testing.T) {
logger, tmpDir := newTestLogger(t)
cfg := logger.GetConfig()
cfg.Format = "json"
cfg.ShowTimestamp = false
mustNoErr(t, logger.ApplyConfig(cfg), "ApplyConfig")
logger.LogStructured(LevelInfo, "structured log", map[string]any{
"user_id": 123,
"action": "login",
"success": true,
})
mustNoErr(t, logger.Flush(time.Second), "Flush")
mustEventually(t, time.Second, "record written", func() bool {
return strings.Contains(readLog(t, tmpDir), "structured log")
})
content := readLog(t, tmpDir)
contains(t, content, `"message":"structured log"`, "message key")
// json.Marshal orders map keys lexically
contains(t, content, `"fields":{"action":"login","success":true,"user_id":123}`, "field object")
notContains(t, content, `"fields":[`, "structured branch must not fall through to the array form")
}
// TestControlCharacterSanitization verifies PolicyTxt hex-encodes every
// non-printable rune on the raw output path. Tab and DEL are non-printable per
// strconv.IsPrint and are encoded like any other control byte.
func TestControlCharacterSanitization(t *testing.T) {
logger, tmpDir := newTestLogger(t)
cfg := logger.GetConfig()
cfg.Format = "raw"
cfg.ShowTimestamp = false
cfg.ShowLevel = false
cfg.Sanitization = PolicyTxt
err := logger.ApplyConfig(cfg)
require.NoError(t, err)
mustNoErr(t, logger.ApplyConfig(cfg), "ApplyConfig")
testCases := []struct {
name string
@@ -99,62 +104,86 @@ func TestControlCharacterWriteWithFormatter(t *testing.T) {
{"backspace", "back\x08space", "back<08>space"},
{"form feed", "page\x0Cbreak", "page<0c>break"},
{"vertical tab", "vertical\x0Btab", "vertical<0b>tab"},
{"tab", "col1\tcol2", "col1<09>col2"},
{"escape", "escape\x1B[31mcolor", "escape<1b>[31mcolor"},
{"del", "del\x7Fmark", "del<7f>mark"},
{"mixed", "\x00\x01\x02test\x1F\x7Fdata", "<00><01><02>test<1f><7f>data"},
// '<' is encoded so input cannot forge a hex marker
{"literal angle bracket", "a<00>b", "a<3c>00>b"},
{"utf8 untouched", "Hello │ 世界", "Hello │ 世界"},
}
for _, tc := range testCases {
logger.Message(tc.input)
}
mustNoErr(t, logger.Flush(time.Second), "Flush")
logger.Flush(time.Second)
time.Sleep(50 * time.Millisecond) // Small delay for file write
content, err := os.ReadFile(filepath.Join(tmpDir, "log.log"))
require.NoError(t, err)
// Records append in submission order; the last one gates the read
last := testCases[len(testCases)-1].expected
mustEventually(t, time.Second, "all records written", func() bool {
return strings.Contains(readLog(t, tmpDir), last)
})
content := readLog(t, tmpDir)
for _, tc := range testCases {
assert.Contains(t, string(content), tc.expected,
"Test case '%s' should produce hex-encoded control chars", tc.name)
contains(t, content, tc.expected, tc.name)
}
}
// TestRawSanitizedOutputWithFormatter verifies raw output sanitization through formatter
func TestRawSanitizedOutputWithFormatter(t *testing.T) {
logger, tmpDir := createTestLogger(t)
defer logger.Shutdown()
// TestRawSanitizedOutput verifies raw format emits space-joined arguments with
// no framing, and that sanitization applies per argument across string and []byte.
func TestRawSanitizedOutput(t *testing.T) {
logger, tmpDir := newTestLogger(t)
cfg := logger.GetConfig()
cfg.Format = "raw"
cfg.ShowTimestamp = false
cfg.ShowLevel = false
cfg.Format = "raw"
cfg.Sanitization = PolicyTxt
err := logger.ApplyConfig(cfg)
require.NoError(t, err)
mustNoErr(t, logger.ApplyConfig(cfg), "ApplyConfig")
utf8String := "Hello │ 世界"
stringWithControl := "start-\x07-end"
expectedStringOutput := "start-<07>-end"
bytesWithControl := []byte("data\x00with\x08bytes")
expectedBytesOutput := "data<00>with<08>bytes"
multiByteControl := "line1\u0085line2"
expectedMultiByteOutput := "line1<c285>line2"
const (
utf8String = "Hello │ 世界"
stringWithCtl = "start-\x07-end"
multiByteControl = "line1\u0085line2"
)
bytesWithCtl := []byte("data\x00with\x08bytes")
logger.Message(utf8String, stringWithControl, bytesWithControl, multiByteControl)
logger.Flush(time.Second)
content, err := os.ReadFile(filepath.Join(tmpDir, "log.log"))
require.NoError(t, err)
logOutput := string(content)
expectedOutput := strings.Join([]string{
// U+0085 is a single non-printable rune; its two UTF-8 bytes encode as one marker
want := strings.Join([]string{
utf8String,
expectedStringOutput,
expectedBytesOutput,
expectedMultiByteOutput,
"start-<07>-end",
"data<00>with<08>bytes",
"line1<c285>line2",
}, " ")
assert.Equal(t, expectedOutput, logOutput)
logger.Message(utf8String, stringWithCtl, bytesWithCtl, multiByteControl)
mustNoErr(t, logger.Flush(time.Second), "Flush")
mustEventually(t, time.Second, "record written", func() bool {
return len(readLog(t, tmpDir)) > 0
})
equal(t, readLog(t, tmpDir), want, "raw output must match exactly")
}
// TestPolicyRawPassthrough verifies the default policy performs no substitution.
func TestPolicyRawPassthrough(t *testing.T) {
logger, tmpDir := newTestLogger(t)
cfg := logger.GetConfig()
cfg.Format = "raw"
cfg.ShowTimestamp = false
cfg.ShowLevel = false
cfg.Sanitization = PolicyRaw
mustNoErr(t, logger.ApplyConfig(cfg), "ApplyConfig")
logger.Message("esc\x1b[31mred")
mustNoErr(t, logger.Flush(time.Second), "Flush")
mustEventually(t, time.Second, "record written", func() bool {
return len(readLog(t, tmpDir)) > 0
})
equal(t, readLog(t, tmpDir), "esc\x1b[31mred", "PolicyRaw must not transform input")
}