mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
feat: 添加更新路由,支持获取最新版本信息,并实现相关测试用例
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const latestReleaseURL = "https://api.github.com/repos/Rain-kl/ATSFlare/releases/latest"
|
||||
|
||||
var updateHTTPClient = &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
type latestReleaseResponse struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Body string `json:"body"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
PublishedAt string `json:"published_at"`
|
||||
}
|
||||
|
||||
func GetLatestRelease(c *gin.Context) {
|
||||
req, err := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, latestReleaseURL, nil)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "创建更新请求失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
req.Header.Set("User-Agent", "ATSFlare-Server")
|
||||
|
||||
resp, err := updateHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("获取最新版本失败: %v", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("GitHub 返回异常状态: %s", resp.Status),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var release latestReleaseResponse
|
||||
if err = json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "解析最新版本信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": release,
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateHTTPClientForTest() *http.Client {
|
||||
return updateHTTPClient
|
||||
}
|
||||
|
||||
func SetUpdateHTTPClientForTest(client *http.Client) {
|
||||
updateHTTPClient = client
|
||||
}
|
||||
@@ -55,6 +55,11 @@ func SetApiRouter(router *gin.Engine) {
|
||||
optionRoute.GET("/", controller.GetOptions)
|
||||
optionRoute.PUT("/", controller.UpdateOption)
|
||||
}
|
||||
updateRoute := apiRouter.Group("/update")
|
||||
updateRoute.Use(middleware.RootAuth(), middleware.NoTokenAuth())
|
||||
{
|
||||
updateRoute.GET("/latest-release", controller.GetLatestRelease)
|
||||
}
|
||||
fileRoute := apiRouter.Group("/file")
|
||||
fileRoute.Use(middleware.AdminAuth())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"atsflare/common"
|
||||
"atsflare/controller"
|
||||
"atsflare/router"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type roundTripFunc func(req *http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req)
|
||||
}
|
||||
|
||||
func TestLatestReleaseProxy(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
common.RedisEnabled = false
|
||||
setupTestDB(t)
|
||||
|
||||
originalClient := controller.UpdateHTTPClientForTest()
|
||||
controller.SetUpdateHTTPClientForTest(&http.Client{
|
||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.String() != "https://api.github.com/repos/Rain-kl/ATSFlare/releases/latest" {
|
||||
t.Fatalf("unexpected request url: %s", req.URL.String())
|
||||
}
|
||||
if req.Header.Get("Accept") != "application/vnd.github+json" {
|
||||
t.Fatalf("unexpected accept header: %s", req.Header.Get("Accept"))
|
||||
}
|
||||
if req.Header.Get("User-Agent") != "ATSFlare-Server" {
|
||||
t.Fatalf("unexpected user-agent header: %s", req.Header.Get("User-Agent"))
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(strings.NewReader(`{
|
||||
"tag_name":"v1.2.3",
|
||||
"body":"release notes",
|
||||
"html_url":"https://github.com/Rain-kl/ATSFlare/releases/tag/v1.2.3",
|
||||
"published_at":"2026-03-11T00:00:00Z"
|
||||
}`)),
|
||||
}, nil
|
||||
}),
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
controller.SetUpdateHTTPClientForTest(originalClient)
|
||||
})
|
||||
|
||||
engine := gin.New()
|
||||
engine.Use(sessions.Sessions("session", cookie.NewStore([]byte("test-secret"))))
|
||||
router.SetApiRouter(engine)
|
||||
|
||||
loginBody, err := json.Marshal(map[string]string{
|
||||
"username": "root",
|
||||
"password": "123456",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal login body: %v", err)
|
||||
}
|
||||
loginReq := httptest.NewRequest(http.MethodPost, "/api/user/login", bytes.NewReader(loginBody))
|
||||
loginReq.Header.Set("Content-Type", "application/json")
|
||||
loginRecorder := httptest.NewRecorder()
|
||||
engine.ServeHTTP(loginRecorder, loginReq)
|
||||
if loginRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected login status code: %d", loginRecorder.Code)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/update/latest-release", nil)
|
||||
for _, cookieValue := range loginRecorder.Result().Cookies() {
|
||||
req.AddCookie(cookieValue)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
engine.ServeHTTP(recorder, req)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected status code: %d", recorder.Code)
|
||||
}
|
||||
|
||||
var resp apiResponse
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Fatalf("expected success response, got message: %s", resp.Message)
|
||||
}
|
||||
|
||||
var data map[string]any
|
||||
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
||||
t.Fatalf("failed to decode response data: %v", err)
|
||||
}
|
||||
if data["tag_name"] != "v1.2.3" {
|
||||
t.Fatalf("unexpected tag_name: %#v", data["tag_name"])
|
||||
}
|
||||
}
|
||||
@@ -89,10 +89,13 @@ const OtherSetting = () => {
|
||||
};
|
||||
|
||||
const checkUpdate = async () => {
|
||||
const res = await API.get(
|
||||
'https://api.github.com/repos/Rain-kl/ATSFlare/releases/latest'
|
||||
);
|
||||
const { tag_name, body } = res.data;
|
||||
const res = await API.get('/api/update/latest-release');
|
||||
const { success, message, data } = res.data;
|
||||
if (!success) {
|
||||
showError(message);
|
||||
return;
|
||||
}
|
||||
const { tag_name, body } = data;
|
||||
if (
|
||||
hasBuildVersion() &&
|
||||
normalizeVersion(tag_name) === getBuildVersion()
|
||||
|
||||
Reference in New Issue
Block a user