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