master
go 479 lines 15.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package squidlog
4
5 import (
6 "errors"
7 "fmt"
8 "strconv"
9 "testing"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13 )
14
15 const emptyStr = ""
16
17 func TestLogLine_Assign(t *testing.T) {
18 type subTest struct {
19 input string
20 wantLine logLine
21 wantErr error
22 }
23 type test struct {
24 name string
25 field string
26 cases []subTest
27 }
28 tests := []test{
29 {
30 name: "Response Time",
31 field: fieldRespTime,
32 cases: []subTest{
33 {input: "0", wantLine: logLine{respTime: 0}},
34 {input: "1000", wantLine: logLine{respTime: 1000}},
35 {input: emptyStr, wantLine: emptyLogLine},
36 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadRespTime},
37 {input: "-1", wantLine: emptyLogLine, wantErr: errBadRespTime},
38 {input: "0.000", wantLine: emptyLogLine, wantErr: errBadRespTime},
39 },
40 },
41 {
42 name: "Client Address",
43 field: fieldClientAddr,
44 cases: []subTest{
45 {input: "127.0.0.1", wantLine: logLine{clientAddr: "127.0.0.1"}},
46 {input: "::1", wantLine: logLine{clientAddr: "::1"}},
47 {input: "kadr20.m1.netdata.lan", wantLine: logLine{clientAddr: "kadr20.m1.netdata.lan"}},
48 {input: "±!@#$%^&*()", wantLine: logLine{clientAddr: "±!@#$%^&*()"}},
49 {input: emptyStr, wantLine: emptyLogLine},
50 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadClientAddr},
51 },
52 },
53 {
54 name: "Cache Code",
55 field: fieldCacheCode,
56 cases: []subTest{
57 {input: "TCP_MISS", wantLine: logLine{cacheCode: "TCP_MISS"}},
58 {input: "TCP_DENIED", wantLine: logLine{cacheCode: "TCP_DENIED"}},
59 {input: "TCP_CLIENT_REFRESH_MISS", wantLine: logLine{cacheCode: "TCP_CLIENT_REFRESH_MISS"}},
60 {input: "UDP_MISS_NOFETCH", wantLine: logLine{cacheCode: "UDP_MISS_NOFETCH"}},
61 {input: "UDP_INVALID", wantLine: logLine{cacheCode: "UDP_INVALID"}},
62 {input: "NONE", wantLine: logLine{cacheCode: "NONE"}},
63 {input: "NONE_NONE", wantLine: logLine{cacheCode: "NONE_NONE"}},
64 {input: emptyStr, wantLine: emptyLogLine},
65 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadCacheCode},
66 {input: "TCP", wantLine: emptyLogLine, wantErr: errBadCacheCode},
67 {input: "UDP_", wantLine: emptyLogLine, wantErr: errBadCacheCode},
68 {input: "NONE_MISS", wantLine: emptyLogLine, wantErr: errBadCacheCode},
69 },
70 },
71 {
72 name: "HTTP Code",
73 field: fieldHTTPCode,
74 cases: []subTest{
75 {input: "000", wantLine: logLine{httpCode: 0}},
76 {input: "100", wantLine: logLine{httpCode: 100}},
77 {input: "200", wantLine: logLine{httpCode: 200}},
78 {input: "300", wantLine: logLine{httpCode: 300}},
79 {input: "400", wantLine: logLine{httpCode: 400}},
80 {input: "500", wantLine: logLine{httpCode: 500}},
81 {input: "603", wantLine: logLine{httpCode: 603}},
82 {input: emptyStr, wantLine: emptyLogLine},
83 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadHTTPCode},
84 {input: "1", wantLine: emptyLogLine, wantErr: errBadHTTPCode},
85 {input: "604", wantLine: emptyLogLine, wantErr: errBadHTTPCode},
86 {input: "1000", wantLine: emptyLogLine, wantErr: errBadHTTPCode},
87 {input: "TCP_MISS", wantLine: emptyLogLine, wantErr: errBadHTTPCode},
88 },
89 },
90 {
91 name: "Response Size",
92 field: fieldRespSize,
93 cases: []subTest{
94 {input: "0", wantLine: logLine{respSize: 0}},
95 {input: "1000", wantLine: logLine{respSize: 1000}},
96 {input: emptyStr, wantLine: emptyLogLine},
97 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadRespSize},
98 {input: "-1", wantLine: emptyLogLine, wantErr: errBadRespSize},
99 {input: "0.000", wantLine: emptyLogLine, wantErr: errBadRespSize},
100 },
101 },
102 {
103 name: "Request Method",
104 field: fieldReqMethod,
105 cases: []subTest{
106 {input: "GET", wantLine: logLine{reqMethod: "GET"}},
107 {input: "HEAD", wantLine: logLine{reqMethod: "HEAD"}},
108 {input: "POST", wantLine: logLine{reqMethod: "POST"}},
109 {input: "PUT", wantLine: logLine{reqMethod: "PUT"}},
110 {input: "PATCH", wantLine: logLine{reqMethod: "PATCH"}},
111 {input: "DELETE", wantLine: logLine{reqMethod: "DELETE"}},
112 {input: "CONNECT", wantLine: logLine{reqMethod: "CONNECT"}},
113 {input: "OPTIONS", wantLine: logLine{reqMethod: "OPTIONS"}},
114 {input: "TRACE", wantLine: logLine{reqMethod: "TRACE"}},
115 {input: "ICP_QUERY", wantLine: logLine{reqMethod: "ICP_QUERY"}},
116 {input: "PURGE", wantLine: logLine{reqMethod: "PURGE"}},
117 {input: "PROPFIND", wantLine: logLine{reqMethod: "PROPFIND"}},
118 {input: "PROPATCH", wantLine: logLine{reqMethod: "PROPATCH"}},
119 {input: "MKCOL", wantLine: logLine{reqMethod: "MKCOL"}},
120 {input: "COPY", wantLine: logLine{reqMethod: "COPY"}},
121 {input: "MOVE", wantLine: logLine{reqMethod: "MOVE"}},
122 {input: "LOCK", wantLine: logLine{reqMethod: "LOCK"}},
123 {input: "UNLOCK", wantLine: logLine{reqMethod: "UNLOCK"}},
124 {input: "NONE", wantLine: logLine{reqMethod: "NONE"}},
125 {input: emptyStr, wantLine: emptyLogLine},
126 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadReqMethod},
127 {input: "get", wantLine: emptyLogLine, wantErr: errBadReqMethod},
128 {input: "0.000", wantLine: emptyLogLine, wantErr: errBadReqMethod},
129 {input: "TCP_MISS", wantLine: emptyLogLine, wantErr: errBadReqMethod},
130 },
131 },
132 {
133 name: "Hier Code",
134 field: fieldHierCode,
135 cases: []subTest{
136 {input: "HIER_NONE", wantLine: logLine{hierCode: "HIER_NONE"}},
137 {input: "HIER_SIBLING_HIT", wantLine: logLine{hierCode: "HIER_SIBLING_HIT"}},
138 {input: "HIER_NO_CACHE_DIGEST_DIRECT", wantLine: logLine{hierCode: "HIER_NO_CACHE_DIGEST_DIRECT"}},
139 {input: emptyStr, wantLine: emptyLogLine},
140 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadHierCode},
141 {input: "0.000", wantLine: emptyLogLine, wantErr: errBadHierCode},
142 {input: "TCP_MISS", wantLine: emptyLogLine, wantErr: errBadHierCode},
143 {input: "HIER", wantLine: emptyLogLine, wantErr: errBadHierCode},
144 {input: "HIER_", wantLine: emptyLogLine, wantErr: errBadHierCode},
145 {input: "NONE", wantLine: emptyLogLine, wantErr: errBadHierCode},
146 {input: "SIBLING_HIT", wantLine: emptyLogLine, wantErr: errBadHierCode},
147 {input: "NO_CACHE_DIGEST_DIRECT", wantLine: emptyLogLine, wantErr: errBadHierCode},
148 },
149 },
150 {
151 name: "Server Address",
152 field: fieldServerAddr,
153 cases: []subTest{
154 {input: "127.0.0.1", wantLine: logLine{serverAddr: "127.0.0.1"}},
155 {input: "::1", wantLine: logLine{serverAddr: "::1"}},
156 {input: "kadr20.m1.netdata.lan", wantLine: logLine{serverAddr: "kadr20.m1.netdata.lan"}},
157 {input: "±!@#$%^&*()", wantLine: logLine{serverAddr: "±!@#$%^&*()"}},
158 {input: emptyStr, wantLine: emptyLogLine},
159 {input: hyphen, wantLine: emptyLogLine},
160 },
161 },
162 {
163 name: "Mime Type",
164 field: fieldMimeType,
165 cases: []subTest{
166 {input: "application/zstd", wantLine: logLine{mimeType: "application"}},
167 {input: "audio/3gpp2", wantLine: logLine{mimeType: "audio"}},
168 {input: "font/otf", wantLine: logLine{mimeType: "font"}},
169 {input: "image/tiff", wantLine: logLine{mimeType: "image"}},
170 {input: "message/global", wantLine: logLine{mimeType: "message"}},
171 {input: "model/example", wantLine: logLine{mimeType: "model"}},
172 {input: "multipart/encrypted", wantLine: logLine{mimeType: "multipart"}},
173 {input: "text/html", wantLine: logLine{mimeType: "text"}},
174 {input: "video/3gpp", wantLine: logLine{mimeType: "video"}},
175 {input: emptyStr, wantLine: emptyLogLine},
176 {input: hyphen, wantLine: emptyLogLine},
177 {input: "example/example", wantLine: emptyLogLine},
178 {input: "unknown/example", wantLine: emptyLogLine},
179 {input: "audio", wantLine: emptyLogLine, wantErr: errBadMimeType},
180 {input: "/", wantLine: emptyLogLine, wantErr: errBadMimeType},
181 },
182 },
183 {
184 name: "Result Code",
185 field: fieldResultCode,
186 cases: []subTest{
187 {input: "TCP_MISS/000", wantLine: logLine{cacheCode: "TCP_MISS", httpCode: 0}},
188 {input: "TCP_DENIED/603", wantLine: logLine{cacheCode: "TCP_DENIED", httpCode: 603}},
189 {input: emptyStr, wantLine: emptyLogLine},
190 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadResultCode},
191 {input: "TCP_MISS:000", wantLine: emptyLogLine, wantErr: errBadResultCode},
192 {input: "TCP_MISS 000", wantLine: emptyLogLine, wantErr: errBadResultCode},
193 {input: "/", wantLine: emptyLogLine, wantErr: errBadResultCode},
194 {input: "tcp/000", wantLine: emptyLogLine, wantErr: errBadCacheCode},
195 {input: "TCP_MISS/", wantLine: logLine{cacheCode: "TCP_MISS", httpCode: emptyNumber}, wantErr: errBadHTTPCode},
196 },
197 },
198 {
199 name: "Hierarchy",
200 field: fieldHierarchy,
201 cases: []subTest{
202 {input: "HIER_NONE/-", wantLine: logLine{hierCode: "HIER_NONE", serverAddr: emptyString}},
203 {input: "HIER_SIBLING_HIT/127.0.0.1", wantLine: logLine{hierCode: "HIER_SIBLING_HIT", serverAddr: "127.0.0.1"}},
204 {input: emptyStr, wantLine: emptyLogLine},
205 {input: hyphen, wantLine: emptyLogLine, wantErr: errBadHierarchy},
206 {input: "HIER_NONE:-", wantLine: emptyLogLine, wantErr: errBadHierarchy},
207 {input: "HIER_SIBLING_HIT 127.0.0.1", wantLine: emptyLogLine, wantErr: errBadHierarchy},
208 {input: "/", wantLine: emptyLogLine, wantErr: errBadHierarchy},
209 {input: "HIER/-", wantLine: emptyLogLine, wantErr: errBadHierCode},
210 {input: "HIER_NONE/", wantLine: logLine{hierCode: "HIER_NONE", serverAddr: emptyStr}},
211 },
212 },
213 }
214
215 for _, tt := range tests {
216 for i, tc := range tt.cases {
217 name := fmt.Sprintf("[%s:%d]field='%s'|input='%s'", tt.name, i+1, tt.field, tc.input)
218 t.Run(name, func(t *testing.T) {
219
220 line := newEmptyLogLine()
221 err := line.Assign(tt.field, tc.input)
222
223 if tc.wantErr != nil {
224 require.Error(t, err)
225 assert.Truef(t, errors.Is(err, tc.wantErr), "expected '%v' error, got '%v'", tc.wantErr, err)
226 } else {
227 require.NoError(t, err)
228 }
229
230 expected := prepareAssignLogLine(t, tt.field, tc.wantLine)
231 assert.Equal(t, expected, *line)
232 })
233 }
234 }
235 }
236
237 func TestLogLine_verify(t *testing.T) {
238 type subTest struct {
239 input string
240 wantErr error
241 }
242 type test = struct {
243 name string
244 field string
245 cases []subTest
246 }
247 tests := []test{
248 {
249 name: "Response Time",
250 field: fieldRespTime,
251 cases: []subTest{
252 {input: "0"},
253 {input: "1000"},
254 {input: "-1", wantErr: errBadRespTime},
255 },
256 },
257 {
258 name: "Client Address",
259 field: fieldClientAddr,
260 cases: []subTest{
261 {input: "127.0.0.1"},
262 {input: "::1"},
263 {input: "kadr20.m1.netdata.lan"},
264 {input: emptyStr},
265 {input: "±!@#$%^&*()", wantErr: errBadClientAddr},
266 },
267 },
268 {
269 name: "Cache Code",
270 field: fieldCacheCode,
271 cases: []subTest{
272 {input: "TCP_MISS"},
273 {input: "TCP_DENIED"},
274 {input: "TCP_CLIENT_REFRESH_MISS"},
275 {input: "UDP_MISS_NOFETCH"},
276 {input: "UDP_INVALID"},
277 {input: "NONE"},
278 {input: "NONE_NONE"},
279 {input: emptyStr},
280 {input: "TCP", wantErr: errBadCacheCode},
281 {input: "UDP", wantErr: errBadCacheCode},
282 {input: "NONE_MISS", wantErr: errBadCacheCode},
283 },
284 },
285 {
286 name: "HTTP Code",
287 field: fieldHTTPCode,
288 cases: []subTest{
289 {input: "000"},
290 {input: "100"},
291 {input: "200"},
292 {input: "300"},
293 {input: "400"},
294 {input: "500"},
295 {input: "603"},
296 {input: "1", wantErr: errBadHTTPCode},
297 {input: "604", wantErr: errBadHTTPCode},
298 },
299 },
300 {
301 name: "Response Size",
302 field: fieldRespSize,
303 cases: []subTest{
304 {input: "0"},
305 {input: "1000"},
306 {input: "-1", wantErr: errBadRespSize},
307 },
308 },
309 {
310 name: "Request Method",
311 field: fieldReqMethod,
312 cases: []subTest{
313 {input: "GET"},
314 {input: "HEAD"},
315 {input: "POST"},
316 {input: "PUT"},
317 {input: "PATCH"},
318 {input: "DELETE"},
319 {input: "CONNECT"},
320 {input: "OPTIONS"},
321 {input: "TRACE"},
322 {input: "ICP_QUERY"},
323 {input: "PURGE"},
324 {input: "PROPFIND"},
325 {input: "PROPATCH"},
326 {input: "MKCOL"},
327 {input: "COPY"},
328 {input: "MOVE"},
329 {input: "LOCK"},
330 {input: "UNLOCK"},
331 {input: "NONE"},
332 {input: emptyStr},
333 {input: "get", wantErr: errBadReqMethod},
334 {input: "TCP_MISS", wantErr: errBadReqMethod},
335 },
336 },
337 {
338 name: "Hier Code",
339 field: fieldHierCode,
340 cases: []subTest{
341 {input: "HIER_NONE"},
342 {input: "HIER_SIBLING_HIT"},
343 {input: "HIER_NO_CACHE_DIGEST_DIRECT"},
344 {input: emptyStr},
345 {input: "0.000", wantErr: errBadHierCode},
346 {input: "TCP_MISS", wantErr: errBadHierCode},
347 {input: "HIER", wantErr: errBadHierCode},
348 {input: "HIER_", wantErr: errBadHierCode},
349 {input: "NONE", wantErr: errBadHierCode},
350 {input: "SIBLING_HIT", wantErr: errBadHierCode},
351 {input: "NO_CACHE_DIGEST_DIRECT", wantErr: errBadHierCode},
352 },
353 },
354 {
355 name: "Server Address",
356 field: fieldServerAddr,
357 cases: []subTest{
358 {input: "127.0.0.1"},
359 {input: "::1"},
360 {input: "kadr20.m1.netdata.lan"},
361 {input: emptyStr},
362 {input: "±!@#$%^&*()", wantErr: errBadServerAddr},
363 },
364 },
365 {
366 name: "Mime Type",
367 field: fieldMimeType,
368 cases: []subTest{
369 {input: "application"},
370 {input: "audio"},
371 {input: "font"},
372 {input: "image"},
373 {input: "message"},
374 {input: "model"},
375 {input: "multipart"},
376 {input: "text"},
377 {input: "video"},
378 {input: emptyStr},
379 {input: "example/example", wantErr: errBadMimeType},
380 {input: "unknown", wantErr: errBadMimeType},
381 {input: "/", wantErr: errBadMimeType},
382 },
383 },
384 }
385
386 for _, tt := range tests {
387 for i, tc := range tt.cases {
388 name := fmt.Sprintf("[%s:%d]field='%s'|input='%s'", tt.name, i+1, tt.field, tc.input)
389 t.Run(name, func(t *testing.T) {
390 line := prepareVerifyLogLine(t, tt.field, tc.input)
391
392 err := line.verify()
393
394 if tc.wantErr != nil {
395 require.Error(t, err)
396 assert.Truef(t, errors.Is(err, tc.wantErr), "expected '%v' error, got '%v'", tc.wantErr, err)
397 } else {
398 require.NoError(t, err)
399 }
400 })
401 }
402 }
403 }
404
405 func prepareAssignLogLine(t *testing.T, field string, template logLine) logLine {
406 t.Helper()
407 if template.empty() {
408 return template
409 }
410
411 var line logLine
412 line.reset()
413
414 switch field {
415 default:
416 t.Errorf("prepareAssignLogLine unknown field: '%s'", field)
417 case fieldRespTime:
418 line.respTime = template.respTime
419 case fieldClientAddr:
420 line.clientAddr = template.clientAddr
421 case fieldCacheCode:
422 line.cacheCode = template.cacheCode
423 case fieldHTTPCode:
424 line.httpCode = template.httpCode
425 case fieldRespSize:
426 line.respSize = template.respSize
427 case fieldReqMethod:
428 line.reqMethod = template.reqMethod
429 case fieldHierCode:
430 line.hierCode = template.hierCode
431 case fieldMimeType:
432 line.mimeType = template.mimeType
433 case fieldServerAddr:
434 line.serverAddr = template.serverAddr
435 case fieldResultCode:
436 line.cacheCode = template.cacheCode
437 line.httpCode = template.httpCode
438 case fieldHierarchy:
439 line.hierCode = template.hierCode
440 line.serverAddr = template.serverAddr
441 }
442 return line
443 }
444
445 func prepareVerifyLogLine(t *testing.T, field string, value string) logLine {
446 t.Helper()
447 var line logLine
448 line.reset()
449
450 switch field {
451 default:
452 t.Errorf("prepareVerifyLogLine unknown field: '%s'", field)
453 case fieldRespTime:
454 v, err := strconv.Atoi(value)
455 require.NoError(t, err)
456 line.respTime = v
457 case fieldClientAddr:
458 line.clientAddr = value
459 case fieldCacheCode:
460 line.cacheCode = value
461 case fieldHTTPCode:
462 v, err := strconv.Atoi(value)
463 require.NoError(t, err)
464 line.httpCode = v
465 case fieldRespSize:
466 v, err := strconv.Atoi(value)
467 require.NoError(t, err)
468 line.respSize = v
469 case fieldReqMethod:
470 line.reqMethod = value
471 case fieldHierCode:
472 line.hierCode = value
473 case fieldMimeType:
474 line.mimeType = value
475 case fieldServerAddr:
476 line.serverAddr = value
477 }
478 return line
479 }