mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 07:36:37 +08:00
添加代理路由和配置版本的控制器与服务,更新数据库模型,完善API路由
This commit is contained in:
@@ -62,5 +62,21 @@ func SetApiRouter(router *gin.Engine) {
|
||||
fileRoute.POST("/", middleware.UploadRateLimit(), controller.UploadFile)
|
||||
fileRoute.DELETE("/:id", controller.DeleteFile)
|
||||
}
|
||||
proxyRoute := apiRouter.Group("/proxy-routes")
|
||||
proxyRoute.Use(middleware.AdminAuth())
|
||||
{
|
||||
proxyRoute.GET("/", controller.GetProxyRoutes)
|
||||
proxyRoute.POST("/", controller.CreateProxyRoute)
|
||||
proxyRoute.PUT("/:id", controller.UpdateProxyRoute)
|
||||
proxyRoute.DELETE("/:id", controller.DeleteProxyRoute)
|
||||
}
|
||||
configVersionRoute := apiRouter.Group("/config-versions")
|
||||
configVersionRoute.Use(middleware.AdminAuth())
|
||||
{
|
||||
configVersionRoute.GET("/", controller.GetConfigVersions)
|
||||
configVersionRoute.GET("/active", controller.GetActiveConfigVersion)
|
||||
configVersionRoute.POST("/publish", controller.PublishConfigVersion)
|
||||
configVersionRoute.PUT("/:id/activate", controller.ActivateConfigVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"gin-template/common"
|
||||
"gin-template/model"
|
||||
"gin-template/router"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type apiResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
func TestPhase1PublishLifecycle(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
common.RedisEnabled = false
|
||||
setupTestDB(t)
|
||||
|
||||
engine := gin.New()
|
||||
engine.Use(sessions.Sessions("session", cookie.NewStore([]byte("test-secret"))))
|
||||
router.SetApiRouter(engine)
|
||||
|
||||
token := prepareRootToken(t)
|
||||
|
||||
createBody := map[string]any{
|
||||
"domain": "app.example.com",
|
||||
"origin_url": "https://origin-a.internal",
|
||||
"enabled": true,
|
||||
"remark": "primary route",
|
||||
}
|
||||
resp := performJSONRequest(t, engine, token, http.MethodPost, "/api/proxy-routes/", createBody)
|
||||
var createdRoute model.ProxyRoute
|
||||
decodeResponseData(t, resp, &createdRoute)
|
||||
if createdRoute.Domain != "app.example.com" {
|
||||
t.Fatalf("unexpected created route domain: %s", createdRoute.Domain)
|
||||
}
|
||||
|
||||
resp = performJSONRequest(t, engine, token, http.MethodGet, "/api/proxy-routes/", nil)
|
||||
var routes []model.ProxyRoute
|
||||
decodeResponseData(t, resp, &routes)
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("expected 1 route, got %d", len(routes))
|
||||
}
|
||||
|
||||
resp = performJSONRequest(t, engine, token, http.MethodPost, "/api/config-versions/publish", nil)
|
||||
var version1 model.ConfigVersion
|
||||
decodeResponseData(t, resp, &version1)
|
||||
if !version1.IsActive {
|
||||
t.Fatal("expected published version to be active")
|
||||
}
|
||||
if version1.SnapshotJSON == "" || version1.RenderedConfig == "" || version1.Checksum == "" {
|
||||
t.Fatal("expected published version to contain snapshot, rendered config and checksum")
|
||||
}
|
||||
|
||||
initialSnapshot := version1.SnapshotJSON
|
||||
initialRendered := version1.RenderedConfig
|
||||
|
||||
updateBody := map[string]any{
|
||||
"domain": "app.example.com",
|
||||
"origin_url": "https://origin-b.internal",
|
||||
"enabled": true,
|
||||
"remark": "updated route",
|
||||
}
|
||||
routePath := "/api/proxy-routes/" + toString(createdRoute.ID)
|
||||
resp = performJSONRequest(t, engine, token, http.MethodPut, routePath, updateBody)
|
||||
decodeResponseData(t, resp, &createdRoute)
|
||||
if createdRoute.OriginURL != "https://origin-b.internal" {
|
||||
t.Fatalf("unexpected updated route origin: %s", createdRoute.OriginURL)
|
||||
}
|
||||
|
||||
resp = performJSONRequest(t, engine, token, http.MethodPost, "/api/config-versions/publish", nil)
|
||||
var version2 model.ConfigVersion
|
||||
decodeResponseData(t, resp, &version2)
|
||||
if version2.ID == version1.ID {
|
||||
t.Fatal("expected a new version record")
|
||||
}
|
||||
|
||||
resp = performJSONRequest(t, engine, token, http.MethodGet, "/api/config-versions/", nil)
|
||||
var versions []model.ConfigVersion
|
||||
decodeResponseData(t, resp, &versions)
|
||||
if len(versions) != 2 {
|
||||
t.Fatalf("expected 2 versions, got %d", len(versions))
|
||||
}
|
||||
|
||||
activeResp := performJSONRequest(t, engine, token, http.MethodGet, "/api/config-versions/active", nil)
|
||||
var activeVersion model.ConfigVersion
|
||||
decodeResponseData(t, activeResp, &activeVersion)
|
||||
if activeVersion.ID != version2.ID {
|
||||
t.Fatalf("expected version %d active, got %d", version2.ID, activeVersion.ID)
|
||||
}
|
||||
|
||||
activatePath := "/api/config-versions/" + toString(version1.ID) + "/activate"
|
||||
resp = performJSONRequest(t, engine, token, http.MethodPut, activatePath, nil)
|
||||
decodeResponseData(t, resp, &activeVersion)
|
||||
if activeVersion.ID != version1.ID || !activeVersion.IsActive {
|
||||
t.Fatal("expected version1 to become active after rollback activation")
|
||||
}
|
||||
|
||||
var storedVersion1 model.ConfigVersion
|
||||
if err := model.DB.First(&storedVersion1, version1.ID).Error; err != nil {
|
||||
t.Fatalf("failed to query version1: %v", err)
|
||||
}
|
||||
if storedVersion1.SnapshotJSON != initialSnapshot {
|
||||
t.Fatal("expected version1 snapshot to remain immutable")
|
||||
}
|
||||
if storedVersion1.RenderedConfig != initialRendered {
|
||||
t.Fatal("expected version1 rendered config to remain immutable")
|
||||
}
|
||||
|
||||
deletePath := "/api/proxy-routes/" + toString(createdRoute.ID)
|
||||
resp = performJSONRequest(t, engine, token, http.MethodDelete, deletePath, nil)
|
||||
if !resp.Success {
|
||||
t.Fatalf("expected delete route success, got: %s", resp.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestDB(t *testing.T) {
|
||||
t.Helper()
|
||||
dbPath := filepath.Join(t.TempDir(), "phase1.db")
|
||||
common.SQLitePath = dbPath
|
||||
if err := model.InitDB(); err != nil {
|
||||
t.Fatalf("failed to init db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := model.CloseDB(); err != nil {
|
||||
t.Fatalf("failed to close db: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func prepareRootToken(t *testing.T) string {
|
||||
t.Helper()
|
||||
user := &model.User{Username: "root"}
|
||||
if err := user.FillUserByUsername(); err != nil {
|
||||
t.Fatalf("failed to load root user: %v", err)
|
||||
}
|
||||
user.Token = "phase1-test-token"
|
||||
if err := model.DB.Model(user).Update("token", user.Token).Error; err != nil {
|
||||
t.Fatalf("failed to set root token: %v", err)
|
||||
}
|
||||
return user.Token
|
||||
}
|
||||
|
||||
func performJSONRequest(t *testing.T, engine http.Handler, token string, method string, path string, body any) apiResponse {
|
||||
t.Helper()
|
||||
var payload []byte
|
||||
var err error
|
||||
if body != nil {
|
||||
payload, err = json.Marshal(body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal request body: %v", err)
|
||||
}
|
||||
}
|
||||
req := httptest.NewRequest(method, path, bytes.NewReader(payload))
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
recorder := httptest.NewRecorder()
|
||||
engine.ServeHTTP(recorder, req)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected status %d for %s %s: %s", recorder.Code, method, path, recorder.Body.String())
|
||||
}
|
||||
var resp apiResponse
|
||||
if err = json.Unmarshal(recorder.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Fatalf("request %s %s failed: %s", method, path, resp.Message)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func decodeResponseData(t *testing.T, resp apiResponse, target any) {
|
||||
t.Helper()
|
||||
if err := json.Unmarshal(resp.Data, target); err != nil {
|
||||
t.Fatalf("failed to decode response data: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func toString(id uint) string {
|
||||
return strconv.FormatUint(uint64(id), 10)
|
||||
}
|
||||
Reference in New Issue
Block a user