Copyright (c) 2026 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 remote
import (
"context"
"crypto/tls"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testData struct {
Name string `json:"name"`
Value int `json:"value"`
}
const (
testExpectedValue = 100
testCustomTimeoutSec = 10
testHTTPClientTimeout = 5
testRetryCount = 3
testFluentTimeoutSec = 15
testFluentRetryCount = 5
)
func setupTestHTTPServer(handler http.HandlerFunc) (*httptest.Server, *httpClient) {
ts := httptest.NewTLSServer(handler)
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
client := NewHTTPClientBuilder(ts.URL).WithHTTPClient(httpClient).Build()
return ts, client
}
func mockPostSuccessHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
resp := ubseResponse[testData]{
Code: 0,
Msg: "success",
Data: testData{Name: "test", Value: testExpectedValue},
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
panic(err)
}
}
func mockPostHTTPErrorHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
if _, err := w.Write([]byte("internal server error")); err != nil {
panic(err)
}
}
func mockPostBusinessErrorHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
resp := ubseResponse[testData]{
Code: 1,
Msg: "volume already exists",
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
panic(err)
}
}
func mockPostInvalidJSONHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
if _, err := w.Write([]byte("invalid json")); err != nil {
panic(err)
}
}
func mockHTTPDeleteSuccessHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
resp := ubseResponse[testData]{
Code: 0,
Msg: "success",
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
panic(err)
}
}
func mockHTTPDeleteErrorHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
if _, err := w.Write([]byte("internal server error")); err != nil {
panic(err)
}
}
func TestPost(t *testing.T) {
t.Run("成功场景", func(t *testing.T) {
t.Run("返回正确解析的数据", func(t *testing.T) {
ts, client := setupTestHTTPServer(mockPostSuccessHandler)
defer ts.Close()
body := testData{Name: "request", Value: 50}
result, err := Post[testData](client, context.Background(), "/test", body)
require.NoError(t, err)
assert.Equal(t, "test", result.Name)
assert.Equal(t, testExpectedValue, result.Value)
})
})
t.Run("失败场景", func(t *testing.T) {
t.Run("HTTP状态码错误", func(t *testing.T) {
ts, client := setupTestHTTPServer(mockPostHTTPErrorHandler)
defer ts.Close()
body := testData{Name: "request", Value: 50}
_, err := Post[testData](client, context.Background(), "/test", body)
require.Error(t, err)
ubseErr, ok := err.(*UBSEResp)
require.True(t, ok)
assert.Equal(t, http.StatusInternalServerError, ubseErr.HTTPStatus)
})
t.Run("UBSE业务错误码", func(t *testing.T) {
ts, client := setupTestHTTPServer(mockPostBusinessErrorHandler)
defer ts.Close()
body := testData{Name: "request", Value: 50}
_, err := Post[testData](client, context.Background(), "/test", body)
require.Error(t, err)
ubseErr, ok := err.(*UBSEResp)
require.True(t, ok)
assert.Equal(t, 1, ubseErr.Code)
assert.Contains(t, ubseErr.Msg, "already exists")
})
t.Run("JSON解析错误", func(t *testing.T) {
ts, client := setupTestHTTPServer(mockPostInvalidJSONHandler)
defer ts.Close()
body := testData{Name: "request", Value: 50}
_, err := Post[testData](client, context.Background(), "/test", body)
require.Error(t, err)
assert.Contains(t, err.Error(), "unmarshal")
})
})
}
func TestDelete(t *testing.T) {
t.Run("成功场景", func(t *testing.T) {
t.Run("返回成功响应", func(t *testing.T) {
ts, client := setupTestHTTPServer(mockHTTPDeleteSuccessHandler)
defer ts.Close()
result, err := Delete[testData](client, context.Background(), "/test/vol-1")
require.NoError(t, err)
assert.NotNil(t, result)
})
})
t.Run("失败场景", func(t *testing.T) {
t.Run("HTTP状态码错误", func(t *testing.T) {
ts, client := setupTestHTTPServer(mockHTTPDeleteErrorHandler)
defer ts.Close()
_, err := Delete[testData](client, context.Background(), "/test/vol-1")
require.Error(t, err)
ubseErr, ok := err.(*UBSEResp)
require.True(t, ok)
assert.Equal(t, http.StatusInternalServerError, ubseErr.HTTPStatus)
})
})
}
func TestHTTPClientBuilder(t *testing.T) {
t.Run("默认配置", func(t *testing.T) {
client := NewHTTPClientBuilder("https://test.com").Build()
assert.NotNil(t, client)
})
t.Run("自定义超时", func(t *testing.T) {
client := NewHTTPClientBuilder("https://test.com").
WithTimeout(testCustomTimeoutSec * time.Second).
Build()
assert.NotNil(t, client)
})
t.Run("自定义HTTPClient", func(t *testing.T) {
customClient := &http.Client{Timeout: testHTTPClientTimeout * time.Second}
client := NewHTTPClientBuilder("https://test.com").
WithHTTPClient(customClient).
Build()
assert.NotNil(t, client)
})
t.Run("自定义重试次数", func(t *testing.T) {
client := NewHTTPClientBuilder("https://test.com").
WithRetry(testRetryCount).
Build()
assert.NotNil(t, client)
})
t.Run("Fluent链式调用", func(t *testing.T) {
customClient := &http.Client{Timeout: testFluentTimeoutSec * time.Second}
client := NewHTTPClientBuilder("https://test.com").
WithTimeout(testFluentTimeoutSec * time.Second).
WithRetry(testFluentRetryCount).
WithHTTPClient(customClient).
Build()
assert.NotNil(t, client)
})
}
func TestUBSEError(t *testing.T) {
t.Run("带HTTP状态的错误信息", func(t *testing.T) {
err := &UBSEResp{HTTPStatus: 500, Code: 1, Msg: "test error"}
assert.Contains(t, err.Error(), "http_status=500")
assert.Contains(t, err.Error(), "code=1")
})
t.Run("不带HTTP状态的错误信息", func(t *testing.T) {
err := &UBSEResp{Code: 1, Msg: "test error"}
assert.Contains(t, err.Error(), "code=1")
assert.NotContains(t, err.Error(), "http_status")
})
}