test: add comprehensive tests to improve coverage
- observability: added tests for LogLevel.String(), Warn(), Warnf(), Infof(), Debugf(), Error(), WithFields(), and concurrent access patterns - file: added tests for Reopen(), Close(), empty/whitespace paths, validateFilePath allowed/rejected paths, concurrent writes, Flush(), and marshal errors - config: added tests for TimeWindowConfig.GetDuration(), CorrelationConfig getters, validation scenarios (no inputs, no outputs, duplicate sockets, ClickHouse validation), and LogConfig.GetLevel() Coverage improvements: - observability: 57.7% → 79.5% - file: 68.6% → 78.6% - config: 69.8% → 97.7% - total: 68.6% → 74.4% Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@ -216,3 +216,213 @@ func TestFileSink_InvalidPath(t *testing.T) {
|
||||
t.Error("expected error for invalid path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_Reopen(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
sink, err := NewFileSink(Config{Path: testPath})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create sink: %v", err)
|
||||
}
|
||||
|
||||
// Write initial data
|
||||
log := domain.CorrelatedLog{SrcIP: "192.168.1.1", SrcPort: 8080}
|
||||
if err := sink.Write(context.Background(), log); err != nil {
|
||||
t.Fatalf("failed to write: %v", err)
|
||||
}
|
||||
|
||||
// Reopen should close and reopen the file
|
||||
err = sink.Reopen()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error on Reopen, got %v", err)
|
||||
}
|
||||
|
||||
// Write after reopen
|
||||
log2 := domain.CorrelatedLog{SrcIP: "192.168.1.2", SrcPort: 8081}
|
||||
if err := sink.Write(context.Background(), log2); err != nil {
|
||||
t.Fatalf("failed to write after reopen: %v", err)
|
||||
}
|
||||
|
||||
sink.Close()
|
||||
|
||||
// Verify both writes are present
|
||||
data, err := os.ReadFile(testPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read file: %v", err)
|
||||
}
|
||||
|
||||
lines := 0
|
||||
for _, b := range data {
|
||||
if b == '\n' {
|
||||
lines++
|
||||
}
|
||||
}
|
||||
|
||||
if lines != 2 {
|
||||
t.Errorf("expected 2 lines after reopen, got %d", lines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_Close(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
sink, err := NewFileSink(Config{Path: testPath})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create sink: %v", err)
|
||||
}
|
||||
|
||||
// Close should succeed
|
||||
err = sink.Close()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error on Close, got %v", err)
|
||||
}
|
||||
|
||||
// Write after close should fail or reopen
|
||||
log := domain.CorrelatedLog{SrcIP: "192.168.1.1", SrcPort: 8080}
|
||||
err = sink.Write(context.Background(), log)
|
||||
if err != nil {
|
||||
// Expected - file was closed
|
||||
t.Logf("write after close returned error (expected): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_EmptyPath(t *testing.T) {
|
||||
_, err := NewFileSink(Config{Path: ""})
|
||||
if err == nil {
|
||||
t.Error("expected error for empty path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_WhitespacePath(t *testing.T) {
|
||||
_, err := NewFileSink(Config{Path: " "})
|
||||
if err == nil {
|
||||
t.Error("expected error for whitespace-only path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_ValidateFilePath_AllowedRoots(t *testing.T) {
|
||||
// Test paths under allowed roots
|
||||
allowedPaths := []string{
|
||||
"/var/log/logcorrelator/correlated.log",
|
||||
"/var/log/test.log",
|
||||
"/tmp/test.log",
|
||||
"/tmp/subdir/test.log",
|
||||
"relative/path/test.log",
|
||||
"./test.log",
|
||||
}
|
||||
|
||||
for _, path := range allowedPaths {
|
||||
err := validateFilePath(path)
|
||||
if err != nil {
|
||||
t.Errorf("validateFilePath(%q) unexpected error: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_ValidateFilePath_RejectedPaths(t *testing.T) {
|
||||
// Test paths that should be rejected
|
||||
rejectedPaths := []string{
|
||||
"",
|
||||
" ",
|
||||
"/etc/passwd",
|
||||
"/etc/logcorrelator/test.log",
|
||||
"/root/test.log",
|
||||
"/home/user/test.log",
|
||||
"/var/logevil/test.log",
|
||||
}
|
||||
|
||||
for _, path := range rejectedPaths {
|
||||
err := validateFilePath(path)
|
||||
if err == nil {
|
||||
t.Errorf("validateFilePath(%q) should have been rejected", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_ConcurrentWrites(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
sink, err := NewFileSink(Config{Path: testPath})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create sink: %v", err)
|
||||
}
|
||||
defer sink.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func(n int) {
|
||||
log := domain.CorrelatedLog{SrcIP: "192.168.1.1", SrcPort: 8080 + n}
|
||||
sink.Write(context.Background(), log)
|
||||
done <- true
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
// Verify all writes completed
|
||||
data, err := os.ReadFile(testPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read file: %v", err)
|
||||
}
|
||||
|
||||
lines := 0
|
||||
for _, b := range data {
|
||||
if b == '\n' {
|
||||
lines++
|
||||
}
|
||||
}
|
||||
|
||||
if lines != 10 {
|
||||
t.Errorf("expected 10 lines from concurrent writes, got %d", lines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_Flush(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
sink, err := NewFileSink(Config{Path: testPath})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create sink: %v", err)
|
||||
}
|
||||
defer sink.Close()
|
||||
|
||||
log := domain.CorrelatedLog{SrcIP: "192.168.1.1", SrcPort: 8080}
|
||||
if err := sink.Write(context.Background(), log); err != nil {
|
||||
t.Fatalf("failed to write: %v", err)
|
||||
}
|
||||
|
||||
// Flush should succeed
|
||||
err = sink.Flush(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("expected no error on Flush, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSink_MarshalError(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
sink, err := NewFileSink(Config{Path: testPath})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create sink: %v", err)
|
||||
}
|
||||
defer sink.Close()
|
||||
|
||||
// Create a log with unmarshalable data (channel)
|
||||
log := domain.CorrelatedLog{
|
||||
SrcIP: "192.168.1.1",
|
||||
SrcPort: 8080,
|
||||
Fields: map[string]any{"chan": make(chan int)},
|
||||
}
|
||||
|
||||
err = sink.Write(context.Background(), log)
|
||||
if err == nil {
|
||||
t.Error("expected error when marshaling unmarshalable data")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user