v0.1.8 tests converted to standard library from testify
This commit is contained in:
+112
-83
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user