watch-tool/parser/generic_parser_test.go

198 lines
4.8 KiB
Go

package parser
import (
"log/slog"
"os"
"path/filepath"
"testing"
"watch-tool/patterns"
"codeberg.org/pata1704/drain3"
)
func setupPatterns(t *testing.T) {
content := `
patterns:
common:
extractors:
- name: "syslog_header"
regex: '^\w{3} \d{2} \d{2}:\d{2}:\d{2} (?P<hostname>\S+) .*'
fields:
hostname: "string"
test_service:
extractors:
- name: "data_line"
regex: 'Data: id=(?P<id>\d+) size=(?P<size_mb>[0-9.]+) active=(?P<is_active>true|false) empty=(?P<empty_val>\S+)'
fields:
id: "int"
size_mb: "float"
is_active: "bool"
empty_val: "int" # Testet Fallback bei "-"
`
tmpfile, err := os.CreateTemp("", "patterns_parser_test_*.yaml")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.Write([]byte(content)); err != nil {
t.Fatal(err)
}
tmpfile.Close()
if err := patterns.GetInstance().Load(tmpfile.Name()); err != nil {
t.Fatal(err)
}
}
func TestGenericParser_Parse_Regex(t *testing.T) {
setupPatterns(t)
p := NewGenericParser("test_service", "localhost", nil, "")
line := "Sep 28 10:00:00 myhost Data: id=42 size=12.5 active=true empty=-"
entry, err := p.Parse(line)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if entry.Host != "myhost" {
t.Errorf("Expected host 'myhost', got '%s'", entry.Host)
}
if val, ok := entry.Fields["id"].(int); !ok || val != 42 {
t.Errorf("Expected id=42 (int), got %v (%T)", entry.Fields["id"], entry.Fields["id"])
}
if val, ok := entry.Fields["size_mb"].(float64); !ok || val != 12.5 {
t.Errorf("Expected size_mb=12.5 (float), got %v", entry.Fields["size_mb"])
}
if val, ok := entry.Fields["is_active"].(bool); !ok || val != true {
t.Errorf("Expected is_active=true, got %v", entry.Fields["is_active"])
}
if val, ok := entry.Fields["empty_val"].(int); !ok || val != 0 {
t.Errorf("Expected empty_val=0 for '-', got %v", entry.Fields["empty_val"])
}
}
func TestGenericParser_Drain3_Integration(t *testing.T) {
setupPatterns(t)
opts := &slog.HandlerOptions{Level: slog.LevelDebug}
logger := slog.New(slog.NewTextHandler(os.Stdout, opts))
slog.SetDefault(logger)
tmpDir, err := os.MkdirTemp("", "drain_state_test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
drainCfg := &drain3.Config{
Depth: 4,
SimTh: 0.5,
MaxChildren: 100,
MaxClusters: 100,
}
serviceName := "test_service"
p := NewGenericParser(serviceName, "localhost", drainCfg, tmpDir)
log1 := "User admin logged in from 192.168.1.1"
log2 := "User guest logged in from 10.0.0.1"
entry1, _ := p.Parse(log1)
if entry1.Fields["drain_template_id"] == nil {
t.Error("Drain3 did not assign a template ID for log1")
}
entry2, _ := p.Parse(log2)
id1 := entry1.Fields["drain_template_id"]
id2 := entry2.Fields["drain_template_id"]
t.Logf("IDs: %v -> %v", id1, id2)
t.Logf("Template 2: %s", entry2.Fields["drain_template"])
if err := p.Close(); err != nil {
t.Fatalf("Close failed: %v", err)
}
expectedFile := filepath.Join(tmpDir, serviceName+".bin")
if info, err := os.Stat(expectedFile); os.IsNotExist(err) {
t.Errorf("Drain3 state file NOT found at: %s", expectedFile)
entries, _ := os.ReadDir(tmpDir)
t.Logf("Listing directory %s:", tmpDir)
for _, e := range entries {
t.Logf(" - Found file: %s", e.Name())
}
} else {
t.Logf("Success: State file found (%d bytes)", info.Size())
}
}
func TestGenericParser_Robustness(t *testing.T) {
setupPatterns(t)
p := NewGenericParser("test_service", "localhost", nil, "")
tests := []struct {
name string
log string
checkField string
expectedValue any
shouldFail bool
}{
{
name: "Empty Line",
log: "",
checkField: "",
expectedValue: nil,
shouldFail: false,
},
{
name: "Type Mismatch Int (Text instead of Int)",
log: "Data: id=abc size=12.5 active=true empty=-",
checkField: "_parse_status",
expectedValue: "failed",
},
{
name: "Value Missing (Dash) for Int",
log: "Data: id=1 size=1.0 active=true empty=-",
checkField: "empty_val",
expectedValue: 0,
},
{
name: "Value Missing (Dash) for Float",
log: "Data: id=1 size=1.0 active=true empty=0",
checkField: "size_mb",
expectedValue: 1.0,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
entry, err := p.Parse(tc.log)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if tc.checkField != "" {
val, exists := entry.Fields[tc.checkField]
if tc.expectedValue == "failed" {
if !exists || val != "failed" {
t.Errorf("Expected parse failure status, got %v", val)
}
} else {
if val != tc.expectedValue {
t.Errorf("Field %s: expected %v, got %v", tc.checkField, tc.expectedValue, val)
}
}
}
})
}
}