* Copyright (c) 2025 Huawei Technologies Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package clustermanage
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"io"
"strings"
"github.com/emicklei/go-restful/v3"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.openfuyao.cn/cluster-api-provider-bke/api/bkecommon/v1beta1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"installer-service/pkg/installer"
"installer-service/pkg/utils/httputil"
)
type handlerMock struct {
createClusterErr error
defaultConfigResp installer.DefaultResp
defaultConfigErr error
judgeNodeFunc func(*installer.ClusterNodeInfo) error
allClustersResp installer.ClusterResponse
allClustersErr error
nodesByQueryResp installer.NodeResponse
nodesByQueryErr error
clusterFullResp installer.ClusterFullResponse
clusterFullErr error
deleteClusterResp *httputil.ResponseJson
deleteClusterStatus int
createNodesErr error
deleteNodesErr error
upgradeErr error
upgradeClusterVersionErr error
upgradeClusterName string
upgradeVersion string
upgradeClusterCalled bool
uploadPatchErr error
versionsResp []string
versionsErr error
installableVersionsResp []string
installableVersionsErr error
upgradeVersionsResp []string
upgradeVersionsErr error
clusterByNameResp *v1beta1.BKECluster
clusterByNameErr error
autoUpgradeErr error
clusterLogResp *httputil.ResponseJson
clusterLogStatus int
}
func (m *handlerMock) CreateCluster(object string) error {
return m.createClusterErr
}
func (m *handlerMock) GetDefaultConfig() (installer.DefaultResp, error) {
return m.defaultConfigResp, m.defaultConfigErr
}
func (m *handlerMock) JudgeClusterNode(info *installer.ClusterNodeInfo) error {
if m.judgeNodeFunc != nil {
return m.judgeNodeFunc(info)
}
return nil
}
func (m *handlerMock) GetClusterLog(_ string, _ *websocket.Conn) (*httputil.ResponseJson, int) {
return m.clusterLogResp, m.clusterLogStatus
}
func (m *handlerMock) GetClusters() ([]v1beta1.BKECluster, error) { return nil, nil }
func (m *handlerMock) GetClustersByName(_ string) (*v1beta1.BKECluster, error) {
return m.clusterByNameResp, m.clusterByNameErr
}
func (m *handlerMock) GetClustersByQuery(_ installer.ClusterRequest) (installer.ClusterResponse, error) {
return installer.ClusterResponse{}, nil
}
func (m *handlerMock) GetClusterFull(_ string) (installer.ClusterFullResponse, error) {
return m.clusterFullResp, m.clusterFullErr
}
func (m *handlerMock) GetAllClusters() (installer.ClusterResponse, error) {
return m.allClustersResp, m.allClustersErr
}
func (m *handlerMock) GetNodesByQuery(_ installer.NodeRequest) (installer.NodeResponse, error) {
return m.nodesByQueryResp, m.nodesByQueryErr
}
func (m *handlerMock) DeleteCluster(_ string) (*httputil.ResponseJson, int) {
return m.deleteClusterResp, m.deleteClusterStatus
}
func (m *handlerMock) GetClusterConfig(_ string) (installer.ClusterConfig, error) {
return installer.ClusterConfig{}, nil
}
func (m *handlerMock) PatchYaml(_ string, _ bool) error { return nil }
func (m *handlerMock) ScaleDownCluster(_ string, _ string) error { return nil }
func (m *handlerMock) CreateBKENodes(_ string, _ []installer.ClusterNode) error {
return m.createNodesErr
}
func (m *handlerMock) DeleteBKENodes(_ string, _ []string) error {
return m.deleteNodesErr
}
func (m *handlerMock) UploadPatchFile(_ string, _ string) error {
return m.uploadPatchErr
}
func (m *handlerMock) GetOpenFuyaoVersions() ([]string, error) {
return m.versionsResp, m.versionsErr
}
func (m *handlerMock) GetInstallableVersions() ([]string, error) {
return m.installableVersionsResp, m.installableVersionsErr
}
func (m *handlerMock) GetOpenFuyaoUpgradeVersions(_ string) ([]string, error) {
return m.upgradeVersionsResp, m.upgradeVersionsErr
}
func (m *handlerMock) GetUpgradeableVersions(_ string) ([]string, error) {
return m.upgradeVersionsResp, m.upgradeVersionsErr
}
func (m *handlerMock) AutoUpgradePatchPrepare(_ string, _ installer.AutoUpgradeRequest) error {
return m.autoUpgradeErr
}
func (m *handlerMock) UpgradeOpenFuyao(_ string, _ string) error {
return m.upgradeErr
}
func (m *handlerMock) UpgradeClusterVersion(clusterName string, version string) error {
m.upgradeClusterName = clusterName
m.upgradeVersion = version
m.upgradeClusterCalled = true
if m.upgradeClusterVersionErr != nil {
return m.upgradeClusterVersionErr
}
return m.upgradeErr
}
func (m *handlerMock) GetClientSet() kubernetes.Interface { return nil }
func (m *handlerMock) GetDynamicClient() dynamic.Interface { return nil }
func newRestfulPair(method, url, body string) (*restful.Request, *restful.Response, *httptest.ResponseRecorder) {
var httpReq *http.Request
if body != "" {
httpReq = httptest.NewRequest(method, url, bytes.NewBufferString(body))
} else {
httpReq = httptest.NewRequest(method, url, nil)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Accept", "application/json")
restReq := restful.NewRequest(httpReq)
recorder := httptest.NewRecorder()
restResp := restful.NewResponse(recorder)
restResp.SetRequestAccepts("application/json")
return restReq, restResp, recorder
}
func TestHandlerCreateCluster(t *testing.T) {
tests := []struct {
name string
body string
mock *handlerMock
expectStatus int
}{
{
name: "无效JSON请求",
body: "invalid",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "空请求体",
body: "",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "GetDefaultConfig失败返回500",
body: `{"cluster":{"name":"test-cluster","openFuyaoVersion":"v1.0.0"},"nodes":[]}`,
mock: &handlerMock{defaultConfigErr: errors.New("config error")},
expectStatus: http.StatusInternalServerError,
},
{
name: "BuildCreateClusterYaml失败_集群名为空返回400",
body: `{"cluster":{"name":"","openFuyaoVersion":"v1.0.0"},"nodes":[]}`,
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("POST", "/clusters", tt.body)
h.createCluster(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerJudgeClusterNode(t *testing.T) {
tests := []struct {
name string
body string
mock *handlerMock
expectStatus int
}{
{
name: "成功判断节点",
body: `{"nodeName":"node1","clusterName":"cluster1"}`,
mock: &handlerMock{judgeNodeFunc: func(_ *installer.ClusterNodeInfo) error { return nil }},
expectStatus: http.StatusOK,
},
{
name: "节点判断失败返回400",
body: `{"nodeName":"node1","clusterName":"cluster1"}`,
mock: &handlerMock{judgeNodeFunc: func(_ *installer.ClusterNodeInfo) error {
return errors.New("node validation failed")
}},
expectStatus: http.StatusBadRequest,
},
{
name: "空请求体返回500",
body: "",
mock: &handlerMock{},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("POST", "/judge-node", tt.body)
h.judgeClusterNode(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerListNode(t *testing.T) {
tests := []struct {
name string
clusterName string
mock *handlerMock
expectStatus int
}{
{
name: "集群名为空返回400",
clusterName: "",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "GetNodesByQuery成功返回200",
clusterName: "test-cluster",
mock: &handlerMock{nodesByQueryResp: installer.NodeResponse{}},
expectStatus: http.StatusOK,
},
{
name: "GetNodesByQuery失败返回500",
clusterName: "test-cluster",
mock: &handlerMock{nodesByQueryErr: errors.New("query error")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("GET", "/nodes", "")
if tt.clusterName != "" {
req.PathParameters()["cluster-name"] = tt.clusterName
}
h.listNode(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerDeleteCluster(t *testing.T) {
tests := []struct {
name string
clusterName string
mock *handlerMock
expectStatus int
}{
{
name: "成功删除集群",
clusterName: "del-cluster",
mock: &handlerMock{
deleteClusterResp: httputil.GetDefaultSuccessResponseJson(),
deleteClusterStatus: http.StatusOK,
},
expectStatus: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("DELETE", "/clusters/"+tt.clusterName, "")
req.PathParameters()["cluster-name"] = tt.clusterName
h.deleteCluster(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerGetDefaultConfig(t *testing.T) {
tests := []struct {
name string
mock *handlerMock
expectStatus int
}{
{
name: "成功获取默认配置",
mock: &handlerMock{},
expectStatus: http.StatusOK,
},
{
name: "获取默认配置失败返回500",
mock: &handlerMock{defaultConfigErr: errors.New("config error")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("GET", "/configs", "")
h.getDefaultConfig(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerScaleDownCluster(t *testing.T) {
tests := []struct {
name string
clusterName string
body string
mock *handlerMock
expectStatus int
}{
{
name: "空请求体返回400",
body: "",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "节点列表为空返回400",
body: `{"nodes":[]}`,
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "DeleteBKENodes成功",
clusterName: "down-cluster",
body: `{"nodes":["node1"]}`,
mock: &handlerMock{},
expectStatus: http.StatusOK,
},
{
name: "DeleteBKENodes失败返回500",
clusterName: "down-cluster",
body: `{"nodes":["node1"]}`,
mock: &handlerMock{deleteNodesErr: errors.New("delete nodes failed")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("POST", "/scale-down-cluster", tt.body)
if tt.clusterName != "" {
req.PathParameters()["cluster-name"] = tt.clusterName
}
h.scaleDownCluster(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerScaleUpCluster(t *testing.T) {
tests := []struct {
name string
clusterName string
body string
mock *handlerMock
expectStatus int
}{
{
name: "空请求体返回400",
body: "",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "节点列表为空返回400",
body: `{"nodes":[]}`,
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "CreateBKENodes成功",
clusterName: "up-cluster",
body: `{"nodes":[{"name":"node1","ip":"1.2.3.4"}]}`,
mock: &handlerMock{},
expectStatus: http.StatusOK,
},
{
name: "CreateBKENodes失败返回500",
clusterName: "up-cluster",
body: `{"nodes":[{"name":"node1","ip":"1.2.3.4"}]}`,
mock: &handlerMock{createNodesErr: errors.New("create nodes failed")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("POST", "/scale-up-cluster", tt.body)
if tt.clusterName != "" {
req.PathParameters()["cluster-name"] = tt.clusterName
}
h.scaleUpCluster(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerUpgradeCluster(t *testing.T) {
tests := []struct {
name string
clusterName string
body string
mock *handlerMock
expectStatus int
expectCall bool
}{
{
name: "无效请求体返回400",
body: "invalid-json",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "版本为空返回400",
body: `{"version":""}`,
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "UpgradeOpenFuyao成功",
clusterName: "upgrade-cluster",
body: `{"version":"v25.09.1"}`,
mock: &handlerMock{},
expectStatus: http.StatusOK,
expectCall: true,
},
{
name: "UpgradeOpenFuyao失败返回500",
clusterName: "upgrade-cluster",
body: `{"version":"v25.09.1"}`,
mock: &handlerMock{upgradeClusterVersionErr: errors.New("upgrade failed")},
expectStatus: http.StatusInternalServerError,
expectCall: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("POST", "/upgrade-cluster", tt.body)
if tt.clusterName != "" {
req.PathParameters()["cluster-name"] = tt.clusterName
}
h.upgradeCluster(req, resp)
assert.Equal(t, tt.expectCall, tt.mock.upgradeClusterCalled)
if tt.expectCall {
assert.Equal(t, tt.clusterName, tt.mock.upgradeClusterName)
assert.Equal(t, "v25.09.1", tt.mock.upgradeVersion)
}
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerGetOpenFuyaoVersions(t *testing.T) {
tests := []struct {
name string
mock *handlerMock
expectStatus int
}{
{
name: "成功获取版本列表",
mock: &handlerMock{versionsResp: []string{"v1.0", "v2.0"}},
expectStatus: http.StatusOK,
},
{
name: "获取版本失败返回500",
mock: &handlerMock{versionsErr: errors.New("versions error")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("GET", "/versions", "")
h.getOpenFuyaoVersions(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
if tt.expectStatus == http.StatusOK {
var body httputil.ResponseJson
err := json.Unmarshal(rec.Body.Bytes(), &body)
assert.NoError(t, err)
assert.NotNil(t, body.Data)
}
})
}
}
func TestHandlerGetInstallableVersions(t *testing.T) {
tests := []struct {
name string
mock *handlerMock
expectStatus int
}{
{
name: "成功获取可安装版本列表",
mock: &handlerMock{installableVersionsResp: []string{"v25.09", "v25.12"}},
expectStatus: http.StatusOK,
},
{
name: "获取可安装版本失败返回500",
mock: &handlerMock{installableVersionsErr: errors.New("versions error")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("GET", "/versions", "")
h.getInstallableVersions(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
if tt.expectStatus == http.StatusOK {
var body httputil.ResponseJson
err := json.Unmarshal(rec.Body.Bytes(), &body)
assert.NoError(t, err)
assert.NotNil(t, body.Data)
}
})
}
}
func TestHandlerGetUpgradeOpenFuyaoVersions(t *testing.T) {
tests := []struct {
name string
clusterName string
queryVersion string
mock *handlerMock
expectStatus int
}{
{
name: "通过集群资源获取当前版本_成功",
clusterName: "uv-cluster",
mock: &handlerMock{
clusterByNameResp: &v1beta1.BKECluster{
Spec: v1beta1.BKEClusterSpec{
ClusterConfig: &v1beta1.BKEConfig{
Cluster: v1beta1.Cluster{OpenFuyaoVersion: "v1.0"},
},
},
},
upgradeVersionsResp: []string{"v2.0"},
},
expectStatus: http.StatusOK,
},
{
name: "集群不存在且无currentVersion返回500",
clusterName: "missing-cluster",
mock: &handlerMock{clusterByNameErr: errors.New("not found")},
expectStatus: http.StatusInternalServerError,
},
{
name: "集群不存在但有currentVersion参数_成功",
clusterName: "missing-cluster",
queryVersion: "v1.0",
mock: &handlerMock{
clusterByNameErr: errors.New("not found"),
upgradeVersionsResp: []string{"v2.0"},
},
expectStatus: http.StatusOK,
},
{
name: "无集群名_从查询参数获取currentVersion_成功",
mock: &handlerMock{
upgradeVersionsResp: []string{"v2.0"},
},
queryVersion: "v1.0",
expectStatus: http.StatusOK,
},
{
name: "GetOpenFuyaoUpgradeVersions失败返回500",
clusterName: "uv-cluster",
mock: &handlerMock{
clusterByNameResp: &v1beta1.BKECluster{
Spec: v1beta1.BKEClusterSpec{
ClusterConfig: &v1beta1.BKEConfig{
Cluster: v1beta1.Cluster{OpenFuyaoVersion: "v1.0"},
},
},
},
upgradeVersionsErr: errors.New("upgrade versions error"),
},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
url := "/clusters/upgrade-versions"
if tt.queryVersion != "" {
url += "?currentVersion=" + tt.queryVersion
}
req, resp, rec := newRestfulPair("GET", url, "")
if tt.clusterName != "" {
req.PathParameters()["cluster-name"] = tt.clusterName
}
h.getUpgradeOpenFuyaoVersions(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
func TestHandlerAutoUpgrade(t *testing.T) {
tests := []struct {
name string
clusterName string
body string
mock *handlerMock
expectStatus int
}{
{
name: "空请求体返回400",
body: "",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "AutoUpgradePatchPrepare成功",
clusterName: "auto-cluster",
body: `{"patchDir":"/tmp/patch","patchName":"v1"}`,
mock: &handlerMock{},
expectStatus: http.StatusOK,
},
{
name: "AutoUpgradePatchPrepare失败返回500",
clusterName: "auto-cluster",
body: `{"patchDir":"/tmp/patch","patchName":"v1"}`,
mock: &handlerMock{autoUpgradeErr: errors.New("auto upgrade failed")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("POST", "/auto-upgrade", tt.body)
if tt.clusterName != "" {
req.PathParameters()["cluster-name"] = tt.clusterName
}
h.autoUpgrade(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code, "HTTP状态码不匹配")
})
}
}
type wsWriteMock struct{ handlerMock }
func (m *wsWriteMock) GetClusterLog(_ string, conn *websocket.Conn) (*httputil.ResponseJson, int) {
_ = conn.WriteMessage(websocket.TextMessage, []byte("hello"))
return httputil.GetDefaultSuccessResponseJson(), http.StatusOK
}
func TestHandlerGetClusterLog(t *testing.T) {
tests := []struct {
name string
useWs bool
}{
{name: "upgrade success", useWs: true},
{name: "upgrade fail", useWs: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var mock installer.Operation
if tt.useWs {
mock = &wsWriteMock{}
} else {
mock = &handlerMock{}
}
h := &Handler{installerHandler: mock}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
restReq := restful.NewRequest(r)
restResp := restful.NewResponse(w)
restResp.SetRequestAccepts("application/json")
parts := strings.Split(r.URL.Path, "/")
if len(parts) >= 3 {
restReq.PathParameters()["cluster-name"] = parts[2]
}
h.getClusterLog(restReq, restResp)
}))
defer srv.Close()
if tt.useWs {
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/clusters/test/logs"
header := http.Header{}
header.Set("Origin", srv.URL)
conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header)
require.NoError(t, err)
require.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode)
_, msg, err := conn.ReadMessage()
require.NoError(t, err)
assert.Equal(t, "hello", string(msg))
_ = conn.Close()
} else {
resp, err := http.Get(srv.URL + "/clusters/test/logs")
require.NoError(t, err)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
assert.True(t, resp.StatusCode == http.StatusInternalServerError || resp.StatusCode == http.StatusBadRequest)
assert.True(t, strings.Contains(string(body), "Internal Server Error") || strings.Contains(string(body), "Failed to upgrade"))
}
})
}
}
func TestHandlerListClusterGet(t *testing.T) {
tests := []struct {
name string
mock *handlerMock
expectStatus int
}{
{
name: "成功获取集群列表",
mock: &handlerMock{allClustersResp: installer.ClusterResponse{Items: []*installer.ClusterData{{Name: "c1"}}}},
expectStatus: http.StatusOK,
},
{
name: "GetAllClusters失败返回500",
mock: &handlerMock{allClustersErr: errors.New("list error")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("GET", "/clusters", "")
h.listClusterGet(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code)
})
}
}
func TestHandlerGetClusterFull(t *testing.T) {
tests := []struct {
name string
clusterName string
mock *handlerMock
expectStatus int
}{
{
name: "集群名为空返回400",
clusterName: "",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "成功获取集群详情",
clusterName: "detail-cluster",
mock: &handlerMock{clusterFullResp: installer.ClusterFullResponse{ClusterConfig: installer.ClusterConfig{ClusterName: "detail-cluster"}}},
expectStatus: http.StatusOK,
},
{
name: "GetClusterFull失败返回500",
clusterName: "detail-cluster",
mock: &handlerMock{clusterFullErr: errors.New("detail error")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("GET", "/clusters/"+tt.clusterName, "")
if tt.clusterName != "" {
req.PathParameters()["cluster-name"] = tt.clusterName
}
h.getClusterFull(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code)
})
}
}
func TestHandlerUploadPatchFile(t *testing.T) {
tests := []struct {
name string
body string
mock *handlerMock
expectStatus int
}{
{
name: "无效JSON返回400",
body: "invalid",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "空请求体返回400",
body: "",
mock: &handlerMock{},
expectStatus: http.StatusBadRequest,
},
{
name: "上传成功返回200",
body: `{"PatchFileName":"v1.yaml","PatchFileContent":"content"}`,
mock: &handlerMock{},
expectStatus: http.StatusOK,
},
{
name: "上传失败返回500",
body: `{"PatchFileName":"v1.yaml","PatchFileContent":"content"}`,
mock: &handlerMock{uploadPatchErr: errors.New("upload error")},
expectStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{installerHandler: tt.mock}
req, resp, rec := newRestfulPair("POST", "/patches", tt.body)
h.uploadPatchFile(req, resp)
assert.Equal(t, tt.expectStatus, rec.Code)
})
}
}