Skip to content

Commit 9ef9be4

Browse files
committed
chore: add more unittest for backend
Signed-off-by: Zzde <zhangxh1997@gmail.com>
1 parent 5639dba commit 9ef9be4

40 files changed

+4629
-46
lines changed

internal/load_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package internal
2+
3+
import (
4+
"errors"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/bytedance/mockey"
10+
"github.com/zxh326/kite/pkg/cluster"
11+
"github.com/zxh326/kite/pkg/model"
12+
"github.com/zxh326/kite/pkg/rbac"
13+
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
14+
)
15+
16+
func init() {
17+
_ = os.Setenv("MOCKEY_CHECK_GCFLAGS", "false")
18+
}
19+
20+
func TestLoadUserCreatesSuperUser(t *testing.T) {
21+
oldUsername := kiteUsername
22+
oldPassword := kitePassword
23+
oldSyncNow := rbac.SyncNow
24+
defer func() {
25+
kiteUsername = oldUsername
26+
kitePassword = oldPassword
27+
rbac.SyncNow = oldSyncNow
28+
}()
29+
30+
kiteUsername = "admin"
31+
kitePassword = "secret"
32+
rbac.SyncNow = make(chan struct{}, 1)
33+
34+
countMock := mockey.Mock(model.CountUsers).Return(int64(0), nil).Build()
35+
defer countMock.UnPatch()
36+
37+
addMock := mockey.Mock(model.AddSuperUser).To(func(user *model.User) error {
38+
if user.Username != "admin" || user.Password != "secret" {
39+
t.Fatalf("unexpected user: %#v", user)
40+
}
41+
return nil
42+
}).Build()
43+
defer addMock.UnPatch()
44+
45+
if err := loadUser(); err != nil {
46+
t.Fatalf("loadUser() error = %v", err)
47+
}
48+
49+
select {
50+
case <-rbac.SyncNow:
51+
default:
52+
t.Fatal("expected rbac sync signal")
53+
}
54+
}
55+
56+
func TestLoadUserReturnsAddSuperUserError(t *testing.T) {
57+
oldUsername := kiteUsername
58+
oldPassword := kitePassword
59+
oldSyncNow := rbac.SyncNow
60+
defer func() {
61+
kiteUsername = oldUsername
62+
kitePassword = oldPassword
63+
rbac.SyncNow = oldSyncNow
64+
}()
65+
66+
kiteUsername = "admin"
67+
kitePassword = "secret"
68+
rbac.SyncNow = make(chan struct{}, 1)
69+
70+
wantErr := errors.New("boom")
71+
countMock := mockey.Mock(model.CountUsers).Return(int64(0), nil).Build()
72+
defer countMock.UnPatch()
73+
74+
addMock := mockey.Mock(model.AddSuperUser).Return(wantErr).Build()
75+
defer addMock.UnPatch()
76+
77+
if err := loadUser(); !errors.Is(err, wantErr) {
78+
t.Fatalf("loadUser() error = %v, want %v", err, wantErr)
79+
}
80+
81+
select {
82+
case <-rbac.SyncNow:
83+
t.Fatal("unexpected rbac sync signal")
84+
default:
85+
}
86+
}
87+
88+
func TestLoadClustersSkipsWhenClustersExist(t *testing.T) {
89+
countMock := mockey.Mock(model.CountClusters).Return(int64(1), nil).Build()
90+
defer countMock.UnPatch()
91+
92+
importMock := mockey.Mock(cluster.ImportClustersFromKubeconfig).To(func(*clientcmdapi.Config) int64 {
93+
t.Fatal("ImportClustersFromKubeconfig() should not be called")
94+
return 0
95+
}).Build()
96+
defer importMock.UnPatch()
97+
98+
if err := loadClusters(); err != nil {
99+
t.Fatalf("loadClusters() error = %v", err)
100+
}
101+
}
102+
103+
func TestLoadClustersImportsFromKubeconfig(t *testing.T) {
104+
countMock := mockey.Mock(model.CountClusters).Return(int64(0), nil).Build()
105+
defer countMock.UnPatch()
106+
107+
imported := false
108+
importMock := mockey.Mock(cluster.ImportClustersFromKubeconfig).To(func(cfg *clientcmdapi.Config) int64 {
109+
imported = true
110+
if cfg.CurrentContext != "dev" {
111+
t.Fatalf("CurrentContext = %q, want %q", cfg.CurrentContext, "dev")
112+
}
113+
if len(cfg.Contexts) != 1 {
114+
t.Fatalf("len(Contexts) = %d, want 1", len(cfg.Contexts))
115+
}
116+
return 1
117+
}).Build()
118+
defer importMock.UnPatch()
119+
120+
dir := t.TempDir()
121+
kubeconfigPath := filepath.Join(dir, "config")
122+
if err := os.WriteFile(kubeconfigPath, []byte(validKubeconfig), 0o600); err != nil {
123+
t.Fatalf("os.WriteFile() error = %v", err)
124+
}
125+
t.Setenv("KUBECONFIG", kubeconfigPath)
126+
127+
if err := loadClusters(); err != nil {
128+
t.Fatalf("loadClusters() error = %v", err)
129+
}
130+
if !imported {
131+
t.Fatal("expected kubeconfig import")
132+
}
133+
}
134+
135+
func TestLoadClustersReturnsLoadError(t *testing.T) {
136+
countMock := mockey.Mock(model.CountClusters).Return(int64(0), nil).Build()
137+
defer countMock.UnPatch()
138+
139+
dir := t.TempDir()
140+
kubeconfigPath := filepath.Join(dir, "config")
141+
if err := os.WriteFile(kubeconfigPath, []byte("not: [valid"), 0o600); err != nil {
142+
t.Fatalf("os.WriteFile() error = %v", err)
143+
}
144+
t.Setenv("KUBECONFIG", kubeconfigPath)
145+
146+
if err := loadClusters(); err == nil {
147+
t.Fatal("expected loadClusters() to return error")
148+
}
149+
}
150+
151+
const validKubeconfig = `apiVersion: v1
152+
kind: Config
153+
current-context: dev
154+
clusters:
155+
- name: dev
156+
cluster:
157+
server: https://example.com
158+
contexts:
159+
- name: dev
160+
context:
161+
cluster: dev
162+
user: dev
163+
users:
164+
- name: dev
165+
user:
166+
token: test-token
167+
`

main_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"strings"
8+
"testing"
9+
10+
"github.com/gin-gonic/gin"
11+
"github.com/zxh326/kite/pkg/common"
12+
"github.com/zxh326/kite/pkg/version"
13+
)
14+
15+
func TestRegisterBaseRoutes(t *testing.T) {
16+
gin.SetMode(gin.TestMode)
17+
18+
oldVersion := version.Version
19+
oldBuildDate := version.BuildDate
20+
oldCommitID := version.CommitID
21+
oldEnableVersionCheck := common.EnableVersionCheck
22+
defer func() {
23+
version.Version = oldVersion
24+
version.BuildDate = oldBuildDate
25+
version.CommitID = oldCommitID
26+
common.EnableVersionCheck = oldEnableVersionCheck
27+
}()
28+
29+
version.Version = "v1.2.3"
30+
version.BuildDate = "2026-03-27"
31+
version.CommitID = "abc123"
32+
common.EnableVersionCheck = false
33+
34+
r := gin.New()
35+
registerBaseRoutes(&r.RouterGroup)
36+
37+
t.Run("healthz", func(t *testing.T) {
38+
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
39+
rec := httptest.NewRecorder()
40+
r.ServeHTTP(rec, req)
41+
42+
if rec.Code != http.StatusOK {
43+
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
44+
}
45+
if got := strings.TrimSpace(rec.Body.String()); got != `{"status":"ok"}` {
46+
t.Fatalf("body = %q, want %q", got, `{"status":"ok"}`)
47+
}
48+
})
49+
50+
t.Run("version", func(t *testing.T) {
51+
req := httptest.NewRequest(http.MethodGet, "/api/v1/version", nil)
52+
rec := httptest.NewRecorder()
53+
r.ServeHTTP(rec, req)
54+
55+
if rec.Code != http.StatusOK {
56+
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
57+
}
58+
59+
var got map[string]any
60+
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
61+
t.Fatalf("json.Unmarshal() error = %v", err)
62+
}
63+
if got["version"] != "v1.2.3" || got["buildDate"] != "2026-03-27" || got["commitId"] != "abc123" {
64+
t.Fatalf("unexpected version payload: %#v", got)
65+
}
66+
})
67+
}
68+
69+
func TestSetupStatic(t *testing.T) {
70+
gin.SetMode(gin.TestMode)
71+
72+
oldBase := common.Base
73+
oldEnableAnalytics := common.EnableAnalytics
74+
defer func() {
75+
common.Base = oldBase
76+
common.EnableAnalytics = oldEnableAnalytics
77+
}()
78+
79+
common.Base = "/kite"
80+
common.EnableAnalytics = true
81+
82+
r := gin.New()
83+
setupStatic(r)
84+
85+
t.Run("redirects root to base", func(t *testing.T) {
86+
req := httptest.NewRequest(http.MethodGet, "/", nil)
87+
rec := httptest.NewRecorder()
88+
r.ServeHTTP(rec, req)
89+
90+
if rec.Code != http.StatusFound {
91+
t.Fatalf("status = %d, want %d", rec.Code, http.StatusFound)
92+
}
93+
if location := rec.Header().Get("Location"); location != "/kite/" {
94+
t.Fatalf("Location = %q, want %q", location, "/kite/")
95+
}
96+
})
97+
98+
t.Run("serves index for ui routes with base and analytics injected", func(t *testing.T) {
99+
req := httptest.NewRequest(http.MethodGet, "/kite/overview", nil)
100+
rec := httptest.NewRecorder()
101+
r.ServeHTTP(rec, req)
102+
103+
if rec.Code != http.StatusOK {
104+
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
105+
}
106+
body := rec.Body.String()
107+
if !strings.Contains(body, `window.__dynamic_base__="/kite"`) {
108+
t.Fatalf("body missing dynamic base injection")
109+
}
110+
if !strings.Contains(body, "cloud.umami.is/script.js") {
111+
t.Fatalf("body missing analytics injection")
112+
}
113+
})
114+
115+
t.Run("returns api 404 for missing api route", func(t *testing.T) {
116+
req := httptest.NewRequest(http.MethodGet, "/kite/api/missing", nil)
117+
rec := httptest.NewRecorder()
118+
r.ServeHTTP(rec, req)
119+
120+
if rec.Code != http.StatusNotFound {
121+
t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound)
122+
}
123+
if got := strings.TrimSpace(rec.Body.String()); got != `{"error":"API endpoint not found"}` {
124+
t.Fatalf("body = %q, want %q", got, `{"error":"API endpoint not found"}`)
125+
}
126+
})
127+
}

0 commit comments

Comments
 (0)