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  }