Skip to content

Commit 74cb806

Browse files
committed
tests: add util tests
1 parent ba46493 commit 74cb806

8 files changed

Lines changed: 690 additions & 6 deletions

File tree

‎internal/utils/app_utils.go‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,21 @@ import (
1313
)
1414

1515
// Get root domain parses a hostname and returns the upper domain (e.g. sub1.sub2.domain.com -> sub2.domain.com)
16-
func GetRootDomain(appUrl string) (string, error) {
17-
appUrlParsed, err := url.Parse(appUrl)
16+
func GetRootDomain(u string) (string, error) {
17+
appUrl, err := url.Parse(u)
1818
if err != nil {
1919
return "", err
2020
}
2121

22-
host := appUrlParsed.Hostname()
22+
host := appUrl.Hostname()
2323

2424
if netIP := net.ParseIP(host); netIP != nil {
2525
return "", errors.New("IP addresses are not allowed")
2626
}
2727

2828
urlParts := strings.Split(host, ".")
2929

30-
if len(urlParts) < 2 {
30+
if len(urlParts) < 3 {
3131
return "", errors.New("invalid domain, must be at least second level domain")
3232
}
3333

@@ -49,6 +49,7 @@ func ParseFileToLine(content string) string {
4949
}
5050

5151
func Filter[T any](slice []T, test func(T) bool) (res []T) {
52+
res = make([]T, 0)
5253
for _, value := range slice {
5354
if test(value) {
5455
res = append(res, value)

‎internal/utils/app_utils_test.go‎

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package utils_test
2+
3+
import (
4+
"testing"
5+
"tinyauth/internal/config"
6+
"tinyauth/internal/utils"
7+
8+
"github.com/gin-gonic/gin"
9+
"gotest.tools/v3/assert"
10+
)
11+
12+
func TestGetRootDomain(t *testing.T) {
13+
// Normal case
14+
domain := "http://sub.example.com"
15+
expected := "example.com"
16+
result, err := utils.GetRootDomain(domain)
17+
assert.NilError(t, err)
18+
assert.Equal(t, expected, result)
19+
20+
// Domain with multiple subdomains
21+
domain = "http://b.c.example.com"
22+
expected = "c.example.com"
23+
result, err = utils.GetRootDomain(domain)
24+
assert.NilError(t, err)
25+
assert.Equal(t, expected, result)
26+
27+
// Domain with no subdomain
28+
domain = "http://example.com"
29+
expected = "example.com"
30+
_, err = utils.GetRootDomain(domain)
31+
assert.Error(t, err, "invalid domain, must be at least second level domain")
32+
33+
// Invalid domain (only TLD)
34+
domain = "com"
35+
_, err = utils.GetRootDomain(domain)
36+
assert.ErrorContains(t, err, "invalid domain")
37+
38+
// IP address
39+
domain = "http://10.10.10.10"
40+
_, err = utils.GetRootDomain(domain)
41+
assert.ErrorContains(t, err, "IP addresses are not allowed")
42+
43+
// Invalid URL
44+
domain = "http://[::1]:namedport"
45+
_, err = utils.GetRootDomain(domain)
46+
assert.ErrorContains(t, err, "parse \"http://[::1]:namedport\": invalid port \":namedport\" after host")
47+
48+
// URL with scheme and path
49+
domain = "https://sub.example.com/path"
50+
expected = "example.com"
51+
result, err = utils.GetRootDomain(domain)
52+
assert.NilError(t, err)
53+
assert.Equal(t, expected, result)
54+
55+
// URL with port
56+
domain = "http://sub.example.com:8080"
57+
expected = "example.com"
58+
result, err = utils.GetRootDomain(domain)
59+
assert.NilError(t, err)
60+
assert.Equal(t, expected, result)
61+
}
62+
63+
func TestParseFileToLine(t *testing.T) {
64+
// Normal case
65+
content := "user1\nuser2\nuser3"
66+
expected := "user1,user2,user3"
67+
result := utils.ParseFileToLine(content)
68+
assert.Equal(t, expected, result)
69+
70+
// Case with empty lines and spaces
71+
content = " user1 \n\n user2 \n user3 \n"
72+
expected = "user1,user2,user3"
73+
result = utils.ParseFileToLine(content)
74+
assert.Equal(t, expected, result)
75+
76+
// Case with only empty lines
77+
content = "\n\n\n"
78+
expected = ""
79+
result = utils.ParseFileToLine(content)
80+
assert.Equal(t, expected, result)
81+
82+
// Case with single user
83+
content = "singleuser"
84+
expected = "singleuser"
85+
result = utils.ParseFileToLine(content)
86+
assert.Equal(t, expected, result)
87+
88+
// Case with trailing newline
89+
content = "user1\nuser2\n"
90+
expected = "user1,user2"
91+
result = utils.ParseFileToLine(content)
92+
assert.Equal(t, expected, result)
93+
}
94+
95+
func TestFilter(t *testing.T) {
96+
// Normal case
97+
slice := []int{1, 2, 3, 4, 5}
98+
testFunc := func(n int) bool { return n%2 == 0 }
99+
expected := []int{2, 4}
100+
result := utils.Filter(slice, testFunc)
101+
assert.DeepEqual(t, expected, result)
102+
103+
// Case with no matches
104+
slice = []int{1, 3, 5}
105+
testFunc = func(n int) bool { return n%2 == 0 }
106+
expected = []int{}
107+
result = utils.Filter(slice, testFunc)
108+
assert.DeepEqual(t, expected, result)
109+
110+
// Case with all matches
111+
slice = []int{2, 4, 6}
112+
testFunc = func(n int) bool { return n%2 == 0 }
113+
expected = []int{2, 4, 6}
114+
result = utils.Filter(slice, testFunc)
115+
assert.DeepEqual(t, expected, result)
116+
117+
// Case with empty slice
118+
slice = []int{}
119+
testFunc = func(n int) bool { return n%2 == 0 }
120+
expected = []int{}
121+
result = utils.Filter(slice, testFunc)
122+
assert.DeepEqual(t, expected, result)
123+
124+
// Case with different type (string)
125+
sliceStr := []string{"apple", "banana", "cherry"}
126+
testFuncStr := func(s string) bool { return len(s) > 5 }
127+
expectedStr := []string{"banana", "cherry"}
128+
resultStr := utils.Filter(sliceStr, testFuncStr)
129+
assert.DeepEqual(t, expectedStr, resultStr)
130+
}
131+
132+
func TestGetContext(t *testing.T) {
133+
// Setup
134+
gin.SetMode(gin.TestMode)
135+
c, _ := gin.CreateTestContext(nil)
136+
137+
// Normal case
138+
c.Set("context", &config.UserContext{Username: "testuser"})
139+
result, err := utils.GetContext(c)
140+
assert.NilError(t, err)
141+
assert.Equal(t, "testuser", result.Username)
142+
143+
// Case with no context
144+
c.Set("context", nil)
145+
_, err = utils.GetContext(c)
146+
assert.Error(t, err, "invalid user context in request")
147+
148+
// Case with invalid context type
149+
c.Set("context", "invalid type")
150+
_, err = utils.GetContext(c)
151+
assert.Error(t, err, "invalid user context in request")
152+
}
153+
154+
func TestIsRedirectSafe(t *testing.T) {
155+
// Setup
156+
domain := "example.com"
157+
158+
// Case with no subdomain
159+
redirectURL := "http://example.com/welcome"
160+
result := utils.IsRedirectSafe(redirectURL, domain)
161+
assert.Equal(t, false, result)
162+
163+
// Case with different domain
164+
redirectURL = "http://malicious.com/phishing"
165+
result = utils.IsRedirectSafe(redirectURL, domain)
166+
assert.Equal(t, false, result)
167+
168+
// Case with subdomain
169+
redirectURL = "http://sub.example.com/page"
170+
result = utils.IsRedirectSafe(redirectURL, domain)
171+
assert.Equal(t, true, result)
172+
173+
// Case with empty redirect URL
174+
redirectURL = ""
175+
result = utils.IsRedirectSafe(redirectURL, domain)
176+
assert.Equal(t, false, result)
177+
178+
// Case with invalid URL
179+
redirectURL = "http://[::1]:namedport"
180+
result = utils.IsRedirectSafe(redirectURL, domain)
181+
assert.Equal(t, false, result)
182+
183+
// Case with URL having port
184+
redirectURL = "http://sub.example.com:8080/page"
185+
result = utils.IsRedirectSafe(redirectURL, domain)
186+
assert.Equal(t, true, result)
187+
188+
// Case with URL having different subdomain
189+
redirectURL = "http://another.example.com/page"
190+
result = utils.IsRedirectSafe(redirectURL, domain)
191+
assert.Equal(t, true, result)
192+
193+
// Case with URL having different TLD
194+
redirectURL = "http://example.org/page"
195+
result = utils.IsRedirectSafe(redirectURL, domain)
196+
assert.Equal(t, false, result)
197+
}

‎internal/utils/fs_utils_test.go‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package utils
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"gotest.tools/v3/assert"
8+
)
9+
10+
func TestReadFile(t *testing.T) {
11+
// Setup
12+
file, err := os.Create("/tmp/tinyauth_test_file")
13+
assert.NilError(t, err)
14+
15+
_, err = file.WriteString("file content\n")
16+
assert.NilError(t, err)
17+
18+
err = file.Close()
19+
assert.NilError(t, err)
20+
defer os.Remove("/tmp/tinyauth_test_file")
21+
22+
// Normal case
23+
content, err := ReadFile("/tmp/tinyauth_test_file")
24+
assert.NilError(t, err)
25+
assert.Equal(t, "file content\n", content)
26+
27+
// Non-existing file
28+
content, err = ReadFile("/tmp/non_existing_file")
29+
assert.ErrorContains(t, err, "no such file or directory")
30+
assert.Equal(t, "", content)
31+
}

‎internal/utils/label_utils_test.go‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package utils_test
2+
3+
import (
4+
"testing"
5+
"tinyauth/internal/utils"
6+
7+
"gotest.tools/v3/assert"
8+
)
9+
10+
func TestParseHeaders(t *testing.T) {
11+
// Normal case
12+
headers := []string{
13+
"X-Custom-Header=Value",
14+
"Another-Header=AnotherValue",
15+
}
16+
expected := map[string]string{
17+
"X-Custom-Header": "Value",
18+
"Another-Header": "AnotherValue",
19+
}
20+
assert.DeepEqual(t, expected, utils.ParseHeaders(headers))
21+
22+
// Case insensitivity and trimming
23+
headers = []string{
24+
" x-custom-header = Value ",
25+
"ANOTHER-HEADER=AnotherValue",
26+
}
27+
expected = map[string]string{
28+
"X-Custom-Header": "Value",
29+
"Another-Header": "AnotherValue",
30+
}
31+
assert.DeepEqual(t, expected, utils.ParseHeaders(headers))
32+
33+
// Invalid headers (missing '=', empty key/value)
34+
headers = []string{
35+
"InvalidHeader",
36+
"=NoKey",
37+
"NoValue=",
38+
" = ",
39+
}
40+
expected = map[string]string{}
41+
assert.DeepEqual(t, expected, utils.ParseHeaders(headers))
42+
43+
// Headers with unsafe characters
44+
headers = []string{
45+
"X-Custom-Header=Val\x00ue", // Null byte
46+
"Another-Header=Anoth\x7FerValue", // DEL character
47+
"Good-Header=GoodValue",
48+
}
49+
expected = map[string]string{
50+
"X-Custom-Header": "Value",
51+
"Another-Header": "AnotherValue",
52+
"Good-Header": "GoodValue",
53+
}
54+
assert.DeepEqual(t, expected, utils.ParseHeaders(headers))
55+
56+
// Header with spaces in key (should be ignored)
57+
headers = []string{
58+
"X Custom Header=Value",
59+
"Valid-Header=ValidValue",
60+
}
61+
expected = map[string]string{
62+
"Valid-Header": "ValidValue",
63+
}
64+
assert.DeepEqual(t, expected, utils.ParseHeaders(headers))
65+
}
66+
67+
func TestSanitizeHeader(t *testing.T) {
68+
// Normal case
69+
header := "X-Custom-Header"
70+
expected := "X-Custom-Header"
71+
assert.Equal(t, expected, utils.SanitizeHeader(header))
72+
73+
// Header with unsafe characters
74+
header = "X-Cust\x00om-Hea\x7Fder" // Null byte and DEL character
75+
expected = "X-Custom-Header"
76+
assert.Equal(t, expected, utils.SanitizeHeader(header))
77+
78+
// Header with only unsafe characters
79+
header = "\x00\x01\x02\x7F"
80+
expected = ""
81+
assert.Equal(t, expected, utils.SanitizeHeader(header))
82+
83+
// Header with spaces and tabs (should be preserved)
84+
header = "X Custom\tHeader"
85+
expected = "X Custom\tHeader"
86+
assert.Equal(t, expected, utils.SanitizeHeader(header))
87+
}

‎internal/utils/security_utils.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ func GetBasicAuth(username string, password string) string {
4848
func FilterIP(filter string, ip string) (bool, error) {
4949
ipAddr := net.ParseIP(ip)
5050

51+
if ipAddr == nil {
52+
return false, errors.New("invalid IP address")
53+
}
54+
55+
filter = strings.Replace(filter, "-", "/", -1)
56+
5157
if strings.Contains(filter, "/") {
5258
_, cidr, err := net.ParseCIDR(filter)
5359
if err != nil {
@@ -73,8 +79,6 @@ func CheckFilter(filter string, str string) bool {
7379
return true
7480
}
7581

76-
filter = strings.Replace(filter, "-", "/", -1)
77-
7882
if strings.HasPrefix(filter, "/") && strings.HasSuffix(filter, "/") {
7983
re, err := regexp.Compile(filter[1 : len(filter)-1])
8084
if err != nil {

0 commit comments

Comments
 (0)