Skip to content

Commit 1f78b3d

Browse files
committed
test(missions): add table-driven unit tests for pure functions
1 parent 0050574 commit 1f78b3d

3 files changed

Lines changed: 246 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package missions
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestEmbeddedHiddenMissionEntry_HiddenFiles(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
entry string
13+
hidden bool
14+
}{
15+
{name: "dotfile", entry: ".hidden", hidden: true},
16+
{name: "double dot", entry: "..something", hidden: true},
17+
{name: "hidden dir entry", entry: ".config", hidden: true},
18+
{name: "index json", entry: "index.json", hidden: true},
19+
{name: "search state", entry: "search-state.json", hidden: true},
20+
{name: "regular file", entry: "mission.yaml", hidden: false},
21+
{name: "regular dir", entry: "fixes", hidden: false},
22+
{name: "with extension", entry: "README.md", hidden: false},
23+
{name: "empty string", entry: "", hidden: false},
24+
{name: "index as suffix", entry: "not-index.json", hidden: false},
25+
}
26+
for _, tt := range tests {
27+
t.Run(tt.name, func(t *testing.T) {
28+
result := embeddedHiddenMissionEntry(tt.entry)
29+
assert.Equal(t, tt.hidden, result)
30+
})
31+
}
32+
}
33+
34+
func TestEmbeddedHiddenMissionEntry_Deterministic(t *testing.T) {
35+
v1 := embeddedHiddenMissionEntry(".secret")
36+
v2 := embeddedHiddenMissionEntry(".secret")
37+
assert.Equal(t, v1, v2, "pure function must be deterministic")
38+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package missions
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/gofiber/fiber/v2"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestIsDemoMode(t *testing.T) {
16+
app := fiber.New()
17+
app.Get("/test-demo", func(c *fiber.Ctx) error {
18+
if isDemoMode(c) {
19+
return c.SendString("true")
20+
}
21+
return c.SendString("false")
22+
})
23+
24+
tests := []struct {
25+
name string
26+
header string
27+
want bool
28+
}{
29+
{name: "exact true", header: "true", want: true},
30+
{name: "uppercase TRUE", header: "TRUE", want: false},
31+
{name: "numeric 1", header: "1", want: false},
32+
{name: "literal false", header: "false", want: false},
33+
{name: "no header", header: "", want: false},
34+
{name: "yes value", header: "yes", want: false},
35+
}
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
req := httptest.NewRequest(http.MethodGet, "/test-demo", nil)
39+
if tt.header != "" {
40+
req.Header.Set("X-Demo-Mode", tt.header)
41+
}
42+
resp, err := app.Test(req, 5000)
43+
require.NoError(t, err)
44+
body, _ := io.ReadAll(resp.Body)
45+
assert.Equal(t, fmt.Sprintf("%t", tt.want), string(body))
46+
})
47+
}
48+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package missions
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestValidateSlackWebhookURL_Valid(t *testing.T) {
11+
validURL := "https://hooks.slack.com/services/T00/B00/XXX"
12+
err := validateSlackWebhookURL(validURL)
13+
assert.NoError(t, err)
14+
}
15+
16+
func TestValidateSlackWebhookURL_Invalid(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
url string
20+
msg string
21+
}{
22+
{name: "http instead of https", url: "http://hooks.slack.com/services/T00/B00/XXX", msg: "must use https"},
23+
{name: "javascript protocol", url: "javascript:alert(1)", msg: "must use https"},
24+
{name: "file protocol", url: "file:///etc/passwd", msg: "must use https"},
25+
{name: "SSRF internal IP", url: "https://192.168.1.1/services/T", msg: "host must be hooks.slack.com"},
26+
{name: "SSRF localhost", url: "https://localhost/services/T", msg: "host must be hooks.slack.com"},
27+
{name: "wrong domain", url: "https://evil.com/services/T00/B00/XXX", msg: "host must be hooks.slack.com"},
28+
{name: "subdomain confusion", url: "https://hooks.slack.com.evil.com/services/T", msg: "host must be hooks.slack.com"},
29+
{name: "empty string", url: "", msg: "webhook URL is required"},
30+
}
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
err := validateSlackWebhookURL(tt.url)
34+
assert.Error(t, err)
35+
assert.Contains(t, err.Error(), tt.msg)
36+
})
37+
}
38+
}
39+
40+
func TestResolveAllowedShareRepos(t *testing.T) {
41+
const envVar = "KC_ALLOWED_SHARE_REPOS"
42+
43+
t.Run("env unset includes default repos", func(t *testing.T) {
44+
t.Cleanup(func() {
45+
os.Unsetenv(envVar)
46+
})
47+
os.Unsetenv(envVar)
48+
49+
repos := resolveAllowedShareRepos()
50+
assert.NotEmpty(t, repos, "allowlist must not be empty")
51+
assert.Contains(t, repos, "kubestellar/console-kb")
52+
})
53+
54+
t.Run("env with additional repos", func(t *testing.T) {
55+
t.Cleanup(func() {
56+
os.Unsetenv(envVar)
57+
})
58+
os.Setenv(envVar, "org1/repo1,org2/repo2")
59+
60+
repos := resolveAllowedShareRepos()
61+
assert.Len(t, repos, 3)
62+
assert.Contains(t, repos, "org1/repo1")
63+
assert.Contains(t, repos, "org2/repo2")
64+
})
65+
66+
t.Run("env values are trimmed", func(t *testing.T) {
67+
t.Cleanup(func() {
68+
os.Unsetenv(envVar)
69+
})
70+
os.Setenv(envVar, " org1/repo1 , org2/repo2 ")
71+
72+
repos := resolveAllowedShareRepos()
73+
assert.Len(t, repos, 3)
74+
assert.Contains(t, repos, "org1/repo1")
75+
assert.Contains(t, repos, "org2/repo2")
76+
})
77+
78+
t.Run("env with empty entries are ignored", func(t *testing.T) {
79+
t.Cleanup(func() {
80+
os.Unsetenv(envVar)
81+
})
82+
os.Setenv(envVar, "org1/repo1,,org2/repo2")
83+
84+
repos := resolveAllowedShareRepos()
85+
assert.Len(t, repos, 3)
86+
})
87+
}
88+
89+
func TestIsRepoAllowedForShareWithList(t *testing.T) {
90+
allowlist := []string{"kubestellar/console-kb", "org1/repo1"}
91+
92+
t.Run("repo in list", func(t *testing.T) {
93+
assert.True(t, isRepoAllowedForShareWithList("kubestellar/console-kb", allowlist))
94+
})
95+
96+
t.Run("case insensitive match", func(t *testing.T) {
97+
assert.True(t, isRepoAllowedForShareWithList("KubeStellar/Console-KB", allowlist))
98+
})
99+
100+
t.Run("repo not in list", func(t *testing.T) {
101+
assert.False(t, isRepoAllowedForShareWithList("evil/repo", allowlist))
102+
})
103+
104+
t.Run("empty list", func(t *testing.T) {
105+
assert.False(t, isRepoAllowedForShareWithList("kubestellar/console-kb", []string{}))
106+
})
107+
108+
t.Run("nil list", func(t *testing.T) {
109+
assert.False(t, isRepoAllowedForShareWithList("kubestellar/console-kb", nil))
110+
})
111+
}
112+
113+
func TestIsRepoAllowedForShare(t *testing.T) {
114+
const envVar = "KC_ALLOWED_SHARE_REPOS"
115+
116+
t.Run("exact match", func(t *testing.T) {
117+
t.Cleanup(func() {
118+
os.Unsetenv(envVar)
119+
})
120+
os.Unsetenv(envVar)
121+
122+
assert.True(t, isRepoAllowedForShare("kubestellar/console-kb"))
123+
})
124+
125+
t.Run("uppercase variant", func(t *testing.T) {
126+
t.Cleanup(func() {
127+
os.Unsetenv(envVar)
128+
})
129+
os.Unsetenv(envVar)
130+
131+
assert.True(t, isRepoAllowedForShare("KUBESTELLAR/CONSOLE-KB"))
132+
})
133+
134+
t.Run("not in list", func(t *testing.T) {
135+
t.Cleanup(func() {
136+
os.Unsetenv(envVar)
137+
})
138+
os.Unsetenv(envVar)
139+
140+
assert.False(t, isRepoAllowedForShare("evil/repo"))
141+
})
142+
143+
t.Run("empty string", func(t *testing.T) {
144+
t.Cleanup(func() {
145+
os.Unsetenv(envVar)
146+
})
147+
os.Unsetenv(envVar)
148+
149+
assert.False(t, isRepoAllowedForShare(""))
150+
})
151+
152+
t.Run("path traversal attempt", func(t *testing.T) {
153+
t.Cleanup(func() {
154+
os.Unsetenv(envVar)
155+
})
156+
os.Unsetenv(envVar)
157+
158+
assert.False(t, isRepoAllowedForShare("layer5io/../admin/repo"))
159+
})
160+
}

0 commit comments

Comments
 (0)