package parser import ( "testing" "time" ) func TestParseAccessLine_Valid(t *testing.T) { line := `192.168.1.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "http://www.example.com/start.html" "Mozilla/4.08 [en] (Win98; I ;Nav)"` entry, err := ParseAccessLine(line) if err != nil { t.Fatalf("unexpected error: %v", err) } if entry.RemoteAddr != "192.168.1.1" { t.Errorf("RemoteAddr = %q, want %q", entry.RemoteAddr, "192.168.1.1") } if entry.RemoteUser != "frank" { t.Errorf("RemoteUser = %q, want %q", entry.RemoteUser, "frank") } want := time.Date(2000, 10, 10, 13, 55, 36, 0, time.FixedZone("", -7*3600)) if !entry.Time.Equal(want) { t.Errorf("Time = %v, want %v", entry.Time, want) } if entry.Method != "GET" { t.Errorf("Method = %q, want %q", entry.Method, "GET") } if entry.Path != "/apache_pb.gif" { t.Errorf("Path = %q, want %q", entry.Path, "/apache_pb.gif") } if entry.Protocol != "HTTP/1.0" { t.Errorf("Protocol = %q, want %q", entry.Protocol, "HTTP/1.0") } if entry.Status != 200 { t.Errorf("Status = %d, want %d", entry.Status, 200) } if entry.BytesSent != 2326 { t.Errorf("BytesSent = %d, want %d", entry.BytesSent, 2326) } if entry.Referer != "http://www.example.com/start.html" { t.Errorf("Referer = %q, want %q", entry.Referer, "http://www.example.com/start.html") } } func TestParseAccessLine_DashFields(t *testing.T) { line := `10.0.0.1 - - [15/Jan/2024:08:23:41 +0000] "POST /api/users HTTP/1.1" 201 512 "-" "curl/7.68.0"` entry, err := ParseAccessLine(line) if err != nil { t.Fatalf("unexpected error: %v", err) } if entry.RemoteUser != "-" { t.Errorf("RemoteUser = %q, want %q", entry.RemoteUser, "-") } if entry.Referer != "-" { t.Errorf("Referer = %q, want %q", entry.Referer, "-") } if entry.BytesSent != 512 { t.Errorf("BytesSent = %d, want %d", entry.BytesSent, 512) } } func TestParseAccessLine_BytesDash(t *testing.T) { // bytes sent as "-" should parse as 0 line := `127.0.0.1 - - [22/Feb/2024:10:00:00 +0000] "GET /health HTTP/1.1" 200 - "-" "kube-probe/1.27"` entry, err := ParseAccessLine(line) if err != nil { t.Fatalf("unexpected error: %v", err) } if entry.BytesSent != 0 { t.Errorf("BytesSent = %d, want 0", entry.BytesSent) } } func TestParseAccessLine_IPv6(t *testing.T) { line := `2001:db8::1 - - [20/Feb/2024:16:45:00 +0000] "GET /index.html HTTP/2.0" 200 4096 "-" "Googlebot/2.1"` entry, err := ParseAccessLine(line) if err != nil { t.Fatalf("unexpected error: %v", err) } if entry.RemoteAddr != "2001:db8::1" { t.Errorf("RemoteAddr = %q, want %q", entry.RemoteAddr, "2001:db8::1") } } func TestParseAccessLine_Malformed(t *testing.T) { cases := []string{ "this is a malformed line", "", "incomplete line without proper format", } for _, c := range cases { _, err := ParseAccessLine(c) if err == nil { t.Errorf("expected error for malformed line %q, got nil", c) } } } func TestParseAccessLine_Status(t *testing.T) { line := `172.16.0.50 - admin [01/Mar/2024:12:00:00 +0100] "DELETE /resource/42 HTTP/1.1" 404 0 "https://example.org/page" "Mozilla/5.0"` entry, err := ParseAccessLine(line) if err != nil { t.Fatalf("unexpected error: %v", err) } if entry.Status != 404 { t.Errorf("Status = %d, want 404", entry.Status) } if entry.Method != "DELETE" { t.Errorf("Method = %q, want DELETE", entry.Method) } }