Files
OpenFlare/atsf_server/router/api_phase1_test.go
T

195 lines
6.1 KiB
Go

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)
}