package rfc3164 import ( "testing" "time" ) func TestPri(t *testing.T) { tests := []struct { input string expected int expectedErr string }{ {"<0>", 0, ""}, {"<19>", 19, ""}, {"<200>", 200, ""}, {"<4999>", 0, "PRI must be up to 3 characters long"}, {"<123", 0, "PRI must end with '>'"}, {"123>", 0, "PRI must start with '<'"}, {"", 0, "PRI must be a number"}, } for _, test := range tests { test := test t.Run(test.input, func(t *testing.T) { r := &RFC3164{} r.buf = []byte(test.input) r.len = len(r.buf) err := r.parsePRI() if err != nil { if test.expectedErr != "" { if err.Error() != test.expectedErr { t.Errorf("expected error %s, got %s", test.expectedErr, err) } } else { t.Errorf("unexpected error: %s", err) } } else { if test.expectedErr != "" { t.Errorf("expected error %s, got no error", test.expectedErr) } else { if r.PRI != test.expected { t.Errorf("expected %d, got %d", test.expected, r.PRI) } } } }) } } func TestTimestamp(t *testing.T) { tests := []struct { input string expected string expectedErr string currentYear bool }{ {"May 20 09:33:54", "0000-05-20T09:33:54Z", "", false}, {"May 20 09:33:54", "2022-05-20T09:33:54Z", "", true}, {"May 20 09:33:54 2022", "2022-05-20T09:33:54Z", "", false}, {"May 1 09:33:54 2022", "2022-05-01T09:33:54Z", "", false}, {"May 01 09:33:54 2021", "2021-05-01T09:33:54Z", "", true}, {"foobar", "", "timestamp is not valid", false}, } for _, test := range tests { test := test t.Run(test.input, func(t *testing.T) { opts := []RFC3164Option{} if test.currentYear { opts = append(opts, WithCurrentYear()) } r := NewRFC3164Parser(opts...) r.buf = []byte(test.input) r.len = len(r.buf) err := r.parseTimestamp() if err != nil { if test.expectedErr != "" { if err.Error() != test.expectedErr { t.Errorf("expected error %s, got %s", test.expectedErr, err) } } else { t.Errorf("unexpected error: %s", err) } } else { if test.expectedErr != "" { t.Errorf("expected error %s, got no error", test.expectedErr) } else { if r.Timestamp.Format(time.RFC3339) != test.expected { t.Errorf("expected %s, got %s", test.expected, r.Timestamp.Format(time.RFC3339)) } } } }) } } func TestHostname(t *testing.T) { tests := []struct { input string expected string expectedErr string strictHostname bool }{ {"127.0.0.1", "127.0.0.1", "", false}, {"::1", "::1", "", false}, {"foo.-bar", "", "hostname is not valid", true}, {"foo-.bar", "", "hostname is not valid", true}, {"foo123.bar", "foo123.bar", "", true}, {"a..", "", "hostname is not valid", true}, {"foo.bar", "foo.bar", "", false}, {"foo,bar", "foo,bar", "", false}, {"foo,bar", "", "hostname is not valid", true}, {"", "", "hostname is empty", false}, {".", ".", "", true}, {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "", "hostname is not valid", true}, {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bla", "", "hostname is not valid", true}, {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bla", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bla", "", false}, {"a.foo-", "", "hostname is not valid", true}, } for _, test := range tests { test := test t.Run(test.input, func(t *testing.T) { opts := []RFC3164Option{} if test.strictHostname { opts = append(opts, WithStrictHostname()) } r := NewRFC3164Parser(opts...) r.buf = []byte(test.input) r.len = len(r.buf) err := r.parseHostname() if err != nil { if test.expectedErr != "" { if err.Error() != test.expectedErr { t.Errorf("expected error %s, got %s", test.expectedErr, err) } } else { t.Errorf("unexpected error: %s", err) } } else { if test.expectedErr != "" { t.Errorf("expected error %s, got no error", test.expectedErr) } else { if r.Hostname != test.expected { t.Errorf("expected %s, got %s", test.expected, r.Hostname) } } } }) } } func TestTag(t *testing.T) { tests := []struct { input string expected string expectedPID string expectedErr string }{ {"foobar", "foobar", "", ""}, {"foobar[42]", "foobar", "42", ""}, {"", "", "", "tag is empty"}, {"foobar[", "", "", "pid inside tag must be closed with ']'"}, {"foobar[42", "", "", "pid inside tag must be closed with ']'"}, {"foobar[asd]", "foobar", "", "pid inside tag must be a number"}, } for _, test := range tests { test := test t.Run(test.input, func(t *testing.T) { r := &RFC3164{} r.buf = []byte(test.input) r.len = len(r.buf) err := r.parseTag() if err != nil { if test.expectedErr != "" { if err.Error() != test.expectedErr { t.Errorf("expected error %s, got %s", test.expectedErr, err) } } else { t.Errorf("unexpected error: %s", err) } } else { if test.expectedErr != "" { t.Errorf("expected error %s, got no error", test.expectedErr) } else { if r.Tag != test.expected { t.Errorf("expected %s, got %s", test.expected, r.Tag) } if r.PID != test.expectedPID { t.Errorf("expected %s, got %s", test.expected, r.Message) } } } }) } } func TestMessage(t *testing.T) { tests := []struct { input string expected string expectedErr string }{ {"foobar: pouet", "pouet", ""}, {"foobar[42]: test", "test", ""}, {"foobar[123]: this is a test", "this is a test", ""}, {"foobar[123]: ", "", "message is empty"}, {"foobar[123]:", "", "message is empty"}, } for _, test := range tests { test := test t.Run(test.input, func(t *testing.T) { r := &RFC3164{} r.buf = []byte(test.input) r.len = len(r.buf) err := r.parseMessage() if err != nil { if test.expectedErr != "" { if err.Error() != test.expectedErr { t.Errorf("expected error %s, got %s", test.expectedErr, err) } } else { t.Errorf("unexpected error: %s", err) } } else { if test.expectedErr != "" { t.Errorf("expected error %s, got no error", test.expectedErr) } else { if r.Message != test.expected { t.Errorf("expected message %s, got %s", test.expected, r.Tag) } } } }) } } func TestParse(t *testing.T) { type expected struct { Timestamp time.Time Hostname string Tag string PID string Message string PRI int } tests := []struct { input string expected expected expectedErr string opts []RFC3164Option }{ { "<12>May 20 09:33:54 UDMPRO,a2edd0c6ae48,udm-1.10.0.3686 kernel: foo", expected{ Timestamp: time.Date(0, time.May, 20, 9, 33, 54, 0, time.UTC), Hostname: "UDMPRO,a2edd0c6ae48,udm-1.10.0.3686", Tag: "kernel", PID: "", Message: "foo", PRI: 12, }, "", []RFC3164Option{}, }, { "<12>May 20 09:33:54 UDMPRO,a2edd0c6ae48,udm-1.10.0.3686 kernel: foo", expected{ Timestamp: time.Date(2022, time.May, 20, 9, 33, 54, 0, time.UTC), Hostname: "UDMPRO,a2edd0c6ae48,udm-1.10.0.3686", Tag: "kernel", PID: "", Message: "foo", PRI: 12, }, "", []RFC3164Option{WithCurrentYear()}, }, { "<12>May 20 09:33:54 UDMPRO,a2edd0c6ae48,udm-1.10.0.3686 kernel: foo", expected{}, "hostname is not valid", []RFC3164Option{WithStrictHostname()}, }, { "foobar", expected{}, "PRI must start with '<'", []RFC3164Option{}, }, { "<12>", expected{}, "timestamp is not valid", []RFC3164Option{}, }, { "<12 May 02 09:33:54 foo.bar", expected{}, "PRI must be a number", []RFC3164Option{}, }, { "<12>May 02 09:33:54", expected{}, "hostname is empty", []RFC3164Option{}, }, { "<12>May 02 09:33:54 foo.bar", expected{}, "tag is empty", []RFC3164Option{}, }, { "<12>May 02 09:33:54 foo.bar bla[42", expected{}, "pid inside tag must be closed with ']'", []RFC3164Option{}, }, { "<12>May 02 09:33:54 foo.bar bla[42]", expected{}, "message is empty", []RFC3164Option{}, }, { "<12>May 02 09:33:54 foo.bar bla[42]: ", expected{}, "message is empty", []RFC3164Option{}, }, { "<12>May 02 09:33:54 foo.bar bla", expected{}, "message is empty", []RFC3164Option{}, }, { "<12>May 02 09:33:54 foo.bar bla:", expected{}, "message is empty", []RFC3164Option{}, }, { "", expected{}, "message is empty", []RFC3164Option{}, }, { `<13>1 2021-05-18T11:58:40.828081+02:00 mantis sshd 49340 - [timeQuality isSynced="0" tzKnown="1"] blabla`, expected{}, "timestamp is not valid", []RFC3164Option{}, }, { `<46>Jun 2 06:55:39 localhost haproxy[27213]: Connect from 100.100.100.99:52611 to 100.100.100.99:443 (https_shared-merged/HTTP)\\n 10.0.0.1}`, expected{ Timestamp: time.Date(time.Now().Year(), time.June, 2, 6, 55, 39, 0, time.UTC), Hostname: "localhost", Tag: "haproxy", PID: "27213", Message: `Connect from 100.100.100.99:52611 to 100.100.100.99:443 (https_shared-merged/HTTP)\\n 10.0.0.1}`, PRI: 46, }, "", []RFC3164Option{WithCurrentYear()}, }, { `<46>Jun 2 06:55:39 2022 localhost haproxy[27213]: Connect from 100.100.100.99:52611 to 100.100.100.99:443 (https_shared-merged/HTTP)\\n 10.0.0.1}`, expected{ Timestamp: time.Date(2022, time.June, 2, 6, 55, 39, 0, time.UTC), Hostname: "localhost", Tag: "haproxy", PID: "27213", Message: `Connect from 100.100.100.99:52611 to 100.100.100.99:443 (https_shared-merged/HTTP)\\n 10.0.0.1}`, PRI: 46, }, "", []RFC3164Option{}, }, } for _, test := range tests { test := test t.Run(test.input, func(t *testing.T) { r := NewRFC3164Parser(test.opts...) err := r.Parse([]byte(test.input)) if err != nil { if test.expectedErr != "" { if err.Error() != test.expectedErr { t.Errorf("expected error '%s', got '%s'", test.expectedErr, err) } } else { t.Errorf("unexpected error: '%s'", err) } } else { if test.expectedErr != "" { t.Errorf("expected error '%s', got no error", test.expectedErr) } else { if r.Timestamp != test.expected.Timestamp { t.Errorf("expected timestamp '%s', got '%s'", test.expected.Timestamp, r.Timestamp) } if r.Hostname != test.expected.Hostname { t.Errorf("expected hostname '%s', got '%s'", test.expected.Hostname, r.Hostname) } if r.Tag != test.expected.Tag { t.Errorf("expected tag '%s', got '%s'", test.expected.Tag, r.Tag) } if r.PID != test.expected.PID { t.Errorf("expected pid '%s', got '%s'", test.expected.PID, r.PID) } if r.Message != test.expected.Message { t.Errorf("expected message '%s', got '%s'", test.expected.Message, r.Message) } if r.PRI != test.expected.PRI { t.Errorf("expected pri '%d', got '%d'", test.expected.PRI, r.PRI) } } } }) } }