log_parser_parse_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. package nginx_log
  2. import (
  3. "testing"
  4. "time"
  5. )
  6. func TestLogParser_ParseLine(t *testing.T) {
  7. // Create mock user agent parser
  8. mockUA := NewMockUserAgentParser()
  9. // Add test responses
  10. mockUA.AddResponse("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", UserAgentInfo{
  11. Browser: "Chrome",
  12. BrowserVer: "91.0",
  13. OS: "Windows 10",
  14. OSVersion: "10.0",
  15. DeviceType: "Desktop",
  16. })
  17. parser := NewOptimizedLogParser(mockUA)
  18. testCases := []struct {
  19. name string
  20. logLine string
  21. expected *AccessLogEntry
  22. wantErr bool
  23. }{
  24. {
  25. name: "Combined log format",
  26. logLine: `192.168.1.1 - - [25/Dec/2023:10:00:00 +0000] "GET /api/test HTTP/1.1" 200 1024 "https://example.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" 0.123 0.050`,
  27. expected: &AccessLogEntry{
  28. IP: "192.168.1.1",
  29. Timestamp: time.Date(2023, 12, 25, 10, 0, 0, 0, time.UTC).Unix(),
  30. Method: "GET",
  31. Path: "/api/test",
  32. Protocol: "HTTP/1.1",
  33. Status: 200,
  34. BytesSent: 1024,
  35. Referer: "https://example.com",
  36. UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
  37. RequestTime: 0.123,
  38. UpstreamTime: func() *float64 { v := 0.050; return &v }(),
  39. Browser: "Chrome",
  40. BrowserVer: "91.0",
  41. OS: "Windows 10",
  42. OSVersion: "10.0",
  43. DeviceType: "Desktop",
  44. },
  45. wantErr: false,
  46. },
  47. {
  48. name: "Common log format (no user agent)",
  49. logLine: `10.0.0.1 - - [01/Jan/2023:12:00:00 +0000] "POST /submit HTTP/1.1" 201 512`,
  50. expected: &AccessLogEntry{
  51. IP: "10.0.0.1",
  52. Timestamp: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC).Unix(),
  53. Method: "POST",
  54. Path: "/submit",
  55. Protocol: "HTTP/1.1",
  56. Status: 201,
  57. BytesSent: 512,
  58. },
  59. wantErr: false,
  60. },
  61. {
  62. name: "Invalid log line",
  63. logLine: "invalid log line format",
  64. wantErr: true,
  65. },
  66. {
  67. name: "Empty log line",
  68. logLine: "",
  69. wantErr: true,
  70. },
  71. {
  72. name: "Log line with missing fields",
  73. logLine: `192.168.1.1 - - [25/Dec/2023:10:00:00 +0000] "GET /test"`,
  74. wantErr: true,
  75. },
  76. {
  77. name: "Log with special characters in path",
  78. logLine: `127.0.0.1 - - [01/Jan/2023:00:00:00 +0000] "GET /path%20with%20spaces?param=value HTTP/1.1" 200 0 "-" "-"`,
  79. expected: &AccessLogEntry{
  80. IP: "127.0.0.1",
  81. Timestamp: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
  82. Method: "GET",
  83. Path: "/path%20with%20spaces?param=value",
  84. Protocol: "HTTP/1.1",
  85. Status: 200,
  86. BytesSent: 0,
  87. Referer: "-",
  88. UserAgent: "-",
  89. },
  90. wantErr: false,
  91. },
  92. {
  93. name: "Log with IPv6 address",
  94. logLine: `2001:db8::1 - - [01/Jan/2023:00:00:00 +0000] "GET /ipv6 HTTP/1.1" 200 100 "-" "-"`,
  95. expected: &AccessLogEntry{
  96. IP: "2001:db8::1",
  97. Timestamp: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
  98. Method: "GET",
  99. Path: "/ipv6",
  100. Protocol: "HTTP/1.1",
  101. Status: 200,
  102. BytesSent: 100,
  103. Referer: "-",
  104. UserAgent: "-",
  105. },
  106. wantErr: false,
  107. },
  108. {
  109. name: "Log with high status code",
  110. logLine: `192.168.1.1 - - [01/Jan/2023:00:00:00 +0000] "GET /error HTTP/1.1" 500 0 "-" "-"`,
  111. expected: &AccessLogEntry{
  112. IP: "192.168.1.1",
  113. Timestamp: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
  114. Method: "GET",
  115. Path: "/error",
  116. Protocol: "HTTP/1.1",
  117. Status: 500,
  118. BytesSent: 0,
  119. Referer: "-",
  120. UserAgent: "-",
  121. },
  122. wantErr: false,
  123. },
  124. }
  125. for _, tc := range testCases {
  126. t.Run(tc.name, func(t *testing.T) {
  127. result, err := parser.ParseLine(tc.logLine)
  128. if tc.wantErr {
  129. if err == nil {
  130. t.Errorf("Expected error but got none")
  131. }
  132. return
  133. }
  134. if err != nil {
  135. t.Errorf("Unexpected error: %v", err)
  136. return
  137. }
  138. if result == nil {
  139. t.Errorf("Expected result but got nil")
  140. return
  141. }
  142. // Check each field
  143. if result.IP != tc.expected.IP {
  144. t.Errorf("IP mismatch. Expected: %s, Got: %s", tc.expected.IP, result.IP)
  145. }
  146. if result.Timestamp != tc.expected.Timestamp {
  147. t.Errorf("Timestamp mismatch. Expected: %v, Got: %v", tc.expected.Timestamp, result.Timestamp)
  148. }
  149. if result.Method != tc.expected.Method {
  150. t.Errorf("Method mismatch. Expected: %s, Got: %s", tc.expected.Method, result.Method)
  151. }
  152. if result.Path != tc.expected.Path {
  153. t.Errorf("Path mismatch. Expected: %s, Got: %s", tc.expected.Path, result.Path)
  154. }
  155. if result.Protocol != tc.expected.Protocol {
  156. t.Errorf("Protocol mismatch. Expected: %s, Got: %s", tc.expected.Protocol, result.Protocol)
  157. }
  158. if result.Status != tc.expected.Status {
  159. t.Errorf("Status mismatch. Expected: %d, Got: %d", tc.expected.Status, result.Status)
  160. }
  161. if result.BytesSent != tc.expected.BytesSent {
  162. t.Errorf("BytesSent mismatch. Expected: %d, Got: %d", tc.expected.BytesSent, result.BytesSent)
  163. }
  164. if result.Referer != tc.expected.Referer {
  165. t.Errorf("Referer mismatch. Expected: %s, Got: %s", tc.expected.Referer, result.Referer)
  166. }
  167. if result.UserAgent != tc.expected.UserAgent {
  168. t.Errorf("UserAgent mismatch. Expected: %s, Got: %s", tc.expected.UserAgent, result.UserAgent)
  169. }
  170. if result.RequestTime != tc.expected.RequestTime {
  171. t.Errorf("RequestTime mismatch. Expected: %f, Got: %f", tc.expected.RequestTime, result.RequestTime)
  172. }
  173. // Compare UpstreamTime values, not pointers
  174. if (result.UpstreamTime == nil) != (tc.expected.UpstreamTime == nil) {
  175. t.Errorf("UpstreamTime nil mismatch. Expected: %v, Got: %v", tc.expected.UpstreamTime, result.UpstreamTime)
  176. } else if result.UpstreamTime != nil && *result.UpstreamTime != *tc.expected.UpstreamTime {
  177. t.Errorf("UpstreamTime value mismatch. Expected: %v, Got: %v", *tc.expected.UpstreamTime, *result.UpstreamTime)
  178. }
  179. if result.Browser != tc.expected.Browser {
  180. t.Errorf("Browser mismatch. Expected: %s, Got: %s", tc.expected.Browser, result.Browser)
  181. }
  182. if result.BrowserVer != tc.expected.BrowserVer {
  183. t.Errorf("BrowserVer mismatch. Expected: %s, Got: %s", tc.expected.BrowserVer, result.BrowserVer)
  184. }
  185. if result.OS != tc.expected.OS {
  186. t.Errorf("OS mismatch. Expected: %s, Got: %s", tc.expected.OS, result.OS)
  187. }
  188. if result.OSVersion != tc.expected.OSVersion {
  189. t.Errorf("OSVersion mismatch. Expected: %s, Got: %s", tc.expected.OSVersion, result.OSVersion)
  190. }
  191. if result.DeviceType != tc.expected.DeviceType {
  192. t.Errorf("DeviceType mismatch. Expected: %s, Got: %s", tc.expected.DeviceType, result.DeviceType)
  193. }
  194. })
  195. }
  196. }
  197. // Removed - parseTimestamp is now an internal method of OptimizedLogParser
  198. /*
  199. func TestLogParser_ParseTimestamp(t *testing.T) {
  200. parser := NewOptimizedLogParser(NewMockUserAgentParser())
  201. testCases := []struct {
  202. name string
  203. timestamp string
  204. expected time.Time
  205. wantErr bool
  206. }{
  207. {
  208. name: "Valid timestamp with timezone",
  209. timestamp: "25/Dec/2023:10:00:00 +0000",
  210. expected: time.Date(2023, 12, 25, 10, 0, 0, 0, time.UTC),
  211. wantErr: false,
  212. },
  213. {
  214. name: "Valid timestamp with negative timezone",
  215. timestamp: "01/Jan/2023:12:00:00 -0500",
  216. expected: time.Date(2023, 1, 1, 17, 0, 0, 0, time.UTC), // UTC equivalent
  217. wantErr: false,
  218. },
  219. {
  220. name: "Valid timestamp with positive timezone",
  221. timestamp: "15/Jun/2023:14:30:45 +0200",
  222. expected: time.Date(2023, 6, 15, 12, 30, 45, 0, time.UTC), // UTC equivalent
  223. wantErr: false,
  224. },
  225. {
  226. name: "Invalid timestamp format",
  227. timestamp: "invalid timestamp",
  228. wantErr: true,
  229. },
  230. {
  231. name: "Empty timestamp",
  232. timestamp: "",
  233. wantErr: true,
  234. },
  235. {
  236. name: "Timestamp with invalid date",
  237. timestamp: "32/Dec/2023:10:00:00 +0000",
  238. wantErr: true,
  239. },
  240. {
  241. name: "Timestamp with invalid month",
  242. timestamp: "01/InvalidMonth/2023:10:00:00 +0000",
  243. wantErr: true,
  244. },
  245. }
  246. for _, tc := range testCases {
  247. t.Run(tc.name, func(t *testing.T) {
  248. result, err := parser.parseTimestamp(tc.timestamp)
  249. if tc.wantErr {
  250. if err == nil {
  251. t.Errorf("Expected error but got none")
  252. }
  253. return
  254. }
  255. if err != nil {
  256. t.Errorf("Unexpected error: %v", err)
  257. return
  258. }
  259. if !result.Equal(tc.expected) {
  260. t.Errorf("Timestamp mismatch. Expected: %v, Got: %v", tc.expected, result)
  261. }
  262. })
  263. }
  264. }
  265. */
  266. func TestDetectLogFormat(t *testing.T) {
  267. testCases := []struct {
  268. name string
  269. logLine string
  270. expected *LogFormat
  271. }{
  272. {
  273. name: "Combined format",
  274. logLine: `192.168.1.1 - - [25/Dec/2023:10:00:00 +0000] "GET /test HTTP/1.1" 200 1024 "https://example.com" "Mozilla/5.0" 0.123`,
  275. expected: CombinedFormat,
  276. },
  277. {
  278. name: "Common format",
  279. logLine: `192.168.1.1 - - [25/Dec/2023:10:00:00 +0000] "GET /test HTTP/1.1" 200 1024`,
  280. expected: MainFormat,
  281. },
  282. {
  283. name: "Unknown format",
  284. logLine: "invalid log line",
  285. expected: nil,
  286. },
  287. }
  288. for _, tc := range testCases {
  289. t.Run(tc.name, func(t *testing.T) {
  290. result := DetectLogFormat([]string{tc.logLine})
  291. if (result == nil && tc.expected != nil) || (result != nil && tc.expected == nil) || (result != nil && tc.expected != nil && result.Name != tc.expected.Name) {
  292. expectedName := "nil"
  293. if tc.expected != nil {
  294. expectedName = tc.expected.Name
  295. }
  296. resultName := "nil"
  297. if result != nil {
  298. resultName = result.Name
  299. }
  300. t.Errorf("Format mismatch. Expected: %s, Got: %s", expectedName, resultName)
  301. }
  302. })
  303. }
  304. }