4
0
Эх сурвалжийг харах

chore(tests): test cases for generate.go (#1623)

GenerateSemiGenericRegex,
GenerateUniqueTokenRegex

to clarify and document current behavior
Ben-grmbl 1 жил өмнө
parent
commit
7edfc6b5d5

+ 235 - 0
cmd/generate/config/utils/generate_test.go

@@ -0,0 +1,235 @@
+package utils
+
+import (
+	"testing"
+)
+
+func TestGenerateSemiGenericRegex(t *testing.T) {
+	tests := []struct {
+		name              string
+		identifiers       []string
+		secretRegex       string
+		isCaseInsensitive []bool
+		validStrings      []string
+		invalidStrings    []string
+	}{
+		{
+			name:              "secret is case sensitive, if isCaseInsensitive is false",
+			identifiers:       []string{"api_key"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{false},
+			validStrings:      []string{"api_key=xxx"},
+			invalidStrings:    []string{"api_key=XXX", "api_key=xXx"},
+		},
+		{
+			name:              "secret is case insensitive, if isCaseInsensitive is true",
+			identifiers:       []string{"api_key"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{true},
+			validStrings:      []string{"api_key=xxx", "api_key=XXX", "api_key=xXx"},
+			invalidStrings:    []string{"api_key=x!x"},
+		},
+		{
+			name:              "identifier is case insensitive, regardless of isCaseInsensitive",
+			identifiers:       []string{"api_key"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{true, false},
+			validStrings:      []string{"api_key=xxx", "ApI_KeY=xxx", "aPi_kEy=xxx", "API_KEY=xxx"},
+			invalidStrings:    []string{"api!key=xxx"},
+		},
+		{
+			name:              "identifier can be case sensitive",
+			identifiers:       []string{"(?-i:[Aa]pi_?[Kk]ey|API_?KEY)"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{true, false},
+			validStrings:      []string{"apikey=xxx", "ApiKey=xxx", "Apikey=xxx", "APIKEY=xxx", "api_key=xxx"},
+			invalidStrings:    []string{"ApIKeY=xxx", "aPikEy=xxx"},
+		},
+		{
+			name:              "identifier can be part of a longer word",
+			identifiers:       []string{"key"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{true, false},
+			validStrings:      []string{"mykey=xxx", "keys=xxx", "key1=xxx", "keystore=xxx", "monkey=xxx"},
+			invalidStrings:    []string{},
+		},
+		{
+			name:              "identifier may be followed by specific characters",
+			identifiers:       []string{"api_key"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{true, false},
+			validStrings: []string{
+				"api_key-----=xxx",
+				"api_key.....=xxx",
+				"api_key_____=xxx",
+				"'''api_key'''=xxx",
+				`"""api_key"""=xxx`,
+				"api_key          =xxx",
+				"api_key\t\t\t\t\t=xxx",
+				"api_key\n\n\n=xxx", // potentially invalid?,
+				"api_key\r\n=xxx",
+				"api_key|||=xxx",
+			},
+			invalidStrings: []string{
+				"api_key&=xxx",
+				"$api_key$=xxx",
+				"%api_key%=xxx",
+				"api_key[0]=xxx",
+				"api_key/*REMOVE*/=xxx",
+			},
+		},
+		{
+			name:              "identifier and secret must be separated by specific operators",
+			identifiers:       []string{"api_key"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{true, false},
+			validStrings: []string{
+				"api_key=xxx",
+				"api_key: xxx",
+				"<api_key>xxx",
+				"api_key:=xxx",
+				"api_key:::=xxx",
+				"api_key||:=xxx",
+				"api_key <= xxx",
+				"api_key => xxx",
+				"api_key ?= xxx",
+			},
+			invalidStrings: []string{
+				"api_keyxxx",
+				"api_key\txxx", // potentially valid in a tab-separated file
+				"api_key, xxx",
+				"api_key; xxx",
+				"api_key<xxx>",
+				"api_key&xxx",
+				"api_key = true ? 'xxx' : 'yyy'",
+			},
+		},
+		{
+			name:              "secret is limited by specific boundaries",
+			identifiers:       []string{"api_key"},
+			secretRegex:       `[a-z]{3}`,
+			isCaseInsensitive: []bool{true, false},
+			validStrings: []string{
+				"api_key=    xxx ",
+				"api_key=xxx\n",
+				"api_key=xxx\r\n",
+				"api_key=\n\n\n\n\nxxx", // potentially invalid (e.g. .env.example)
+				"api_key=\r\n\r\nxxx",
+				"api_key=\t\t\t\txxx\t",
+				"api_key======xxx;",
+				"api_key='''xxx'''",
+				`api_key="""xxx"""`,
+				"api_key=```xxx```",
+				`api_key="xxx'`, // could try to match only same opening and closing quotes, might not be worth the complexity
+				`api_key="don't do it!"`,
+				`api_key="xxx;notpartofthematch"`,
+			},
+			invalidStrings: []string{
+				"api_key=_xxx",
+				"api_key=xxx_",
+				"api_key=$xxx",
+				"api_key=%xxx%",
+				"api_key=[xxx]",
+				"api_key=(xxx)",
+				"api_key=<xxx>",
+				"api_key={xxx}",
+				"<api_key>xxx</api_key>",            // potentially valid
+				"example.com?api_key=xxx&other=yyy", // potentially valid
+			},
+		},
+		// Note: these test cases do not necessarily prescribe the expected behavior of the function,
+		// but rather document the current behavior, to ensure that future changes are intentional.
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			for _, isCaseInsensitive := range tt.isCaseInsensitive {
+				regex := GenerateSemiGenericRegex(tt.identifiers, tt.secretRegex, isCaseInsensitive)
+				for _, validString := range tt.validStrings {
+					if !regex.MatchString(validString) {
+						t.Errorf("Expected match, but got none, \nfor GenerateSemiGenericRegex(%v, /%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
+							tt.identifiers, tt.secretRegex, isCaseInsensitive, validString, regex)
+					}
+				}
+				for _, invalidString := range tt.invalidStrings {
+					if regex.MatchString(invalidString) {
+						t.Errorf("Expected no match, but got one, \nfor GenerateSemiGenericRegex(%v, /%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
+							tt.identifiers, tt.secretRegex, isCaseInsensitive, invalidString, regex)
+					}
+				}
+			}
+		})
+	}
+}
+
+func TestGenerateUniqueTokenRegex(t *testing.T) {
+	tests := []struct {
+		name              string
+		secretRegex       string
+		isCaseInsensitive bool
+		validStrings      []string
+		invalidStrings    []string
+	}{
+		{
+			name:              "case sensitive secret",
+			secretRegex:       `[a-c]{3}`,
+			isCaseInsensitive: false,
+			validStrings:      []string{"abc"},
+			invalidStrings:    []string{"ABC", "Abc"},
+		},
+		{
+			name:              "case insensitive secret",
+			secretRegex:       `[a-c]{3}`,
+			isCaseInsensitive: true,
+			validStrings:      []string{"abc", "ABC", "Abc"},
+			invalidStrings:    []string{"123"},
+		},
+		{
+			name:              "allowed boundaries",
+			secretRegex:       `[a-c]{3}`,
+			isCaseInsensitive: false,
+			validStrings: []string{
+				"abc",
+				" abc ",
+				"\nabc\n",
+				"\r\nabc\r\n",
+				"\tabc\t",
+				"'abc'",
+				`"abc"`,
+				"```abc```",
+				"my abc's",
+				".com?abc",
+			},
+			invalidStrings: []string{
+				"abcabc",
+				"_abc_",
+				".com?abc&def", // potentially valid
+				"/*abc*/",
+				"<abc>",
+				"<str>abc</str>", // potentially valid
+				"{{{abc}}}",
+				"abc, d",
+			},
+		},
+		// Note: these test cases do not necessarily prescribe the expected behavior of the function,
+		// but rather document the current behavior, to ensure that future changes are intentional.
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			regex := GenerateUniqueTokenRegex(tt.secretRegex, tt.isCaseInsensitive)
+			for _, validString := range tt.validStrings {
+				if !regex.MatchString(validString) {
+					t.Errorf("Expected match, but got none, \nfor GenerateUniqueTokenRegex(/%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
+						tt.secretRegex, tt.isCaseInsensitive, validString, regex)
+				}
+			}
+			for _, invalidString := range tt.invalidStrings {
+				if regex.MatchString(invalidString) {
+					t.Errorf("Expected no match, but got one, \nfor GenerateUniqueTokenRegex(/%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
+						tt.secretRegex, tt.isCaseInsensitive, invalidString, regex)
+				}
+			}
+		})
+	}
+}