mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
177 lines
6.3 KiB
Go
177 lines
6.3 KiB
Go
package router_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"gin-template/common"
|
|
"gin-template/model"
|
|
"gin-template/router"
|
|
"gin-template/service"
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-contrib/sessions/cookie"
|
|
"github.com/gin-gonic/gin"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestPhase2AgentLifecycle(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
common.RedisEnabled = false
|
|
common.AgentToken = "phase2-agent-token"
|
|
setupTestDB(t)
|
|
|
|
engine := gin.New()
|
|
engine.Use(sessions.Sessions("session", cookie.NewStore([]byte("test-secret"))))
|
|
router.SetApiRouter(engine)
|
|
|
|
adminToken := prepareRootToken(t)
|
|
|
|
createRouteAndPublishVersion(t, engine, adminToken)
|
|
|
|
unauthorizedRequest := httptest.NewRequest(http.MethodPost, "/api/agent/nodes/register", bytes.NewReader([]byte(`{}`)))
|
|
unauthorizedRecorder := httptest.NewRecorder()
|
|
engine.ServeHTTP(unauthorizedRecorder, unauthorizedRequest)
|
|
if unauthorizedRecorder.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected unauthorized status for missing agent token, got %d", unauthorizedRecorder.Code)
|
|
}
|
|
|
|
nodePayload := map[string]any{
|
|
"node_id": "node-001",
|
|
"name": "shanghai-edge-1",
|
|
"ip": "10.0.0.8",
|
|
"agent_version": "0.1.0",
|
|
"nginx_version": "1.25.5",
|
|
"current_version": "",
|
|
"last_error": "",
|
|
}
|
|
resp := performAgentJSONRequest(t, engine, http.MethodPost, "/api/agent/nodes/register", nodePayload)
|
|
var registeredNode model.Node
|
|
decodeResponseData(t, resp, ®isteredNode)
|
|
if registeredNode.NodeID != "node-001" || registeredNode.Status != service.NodeStatusOnline {
|
|
t.Fatal("expected node registration to persist online node state")
|
|
}
|
|
|
|
heartbeatPayload := map[string]any{
|
|
"node_id": "node-001",
|
|
"name": "shanghai-edge-1",
|
|
"ip": "10.0.0.9",
|
|
"agent_version": "0.1.1",
|
|
"nginx_version": "1.25.5",
|
|
"current_version": "",
|
|
"last_error": "",
|
|
}
|
|
resp = performAgentJSONRequest(t, engine, http.MethodPost, "/api/agent/nodes/heartbeat", heartbeatPayload)
|
|
decodeResponseData(t, resp, ®isteredNode)
|
|
if registeredNode.IP != "10.0.0.9" || registeredNode.AgentVersion != "0.1.1" {
|
|
t.Fatal("expected heartbeat to update node metadata")
|
|
}
|
|
|
|
activeConfigResp := performAgentJSONRequest(t, engine, http.MethodGet, "/api/agent/config-versions/active", nil)
|
|
var activeConfig service.AgentConfigResponse
|
|
decodeResponseData(t, activeConfigResp, &activeConfig)
|
|
if activeConfig.Version == "" || activeConfig.RenderedConfig == "" || activeConfig.Checksum == "" {
|
|
t.Fatal("expected active config response to contain version payload")
|
|
}
|
|
|
|
successApplyResp := performAgentJSONRequest(t, engine, http.MethodPost, "/api/agent/apply-logs", map[string]any{
|
|
"node_id": "node-001",
|
|
"version": activeConfig.Version,
|
|
"result": service.ApplyResultOK,
|
|
"message": "apply ok",
|
|
})
|
|
var successApplyLog model.ApplyLog
|
|
decodeResponseData(t, successApplyResp, &successApplyLog)
|
|
if successApplyLog.Result != service.ApplyResultOK {
|
|
t.Fatal("expected apply log success to be recorded")
|
|
}
|
|
|
|
failedApplyResp := performAgentJSONRequest(t, engine, http.MethodPost, "/api/agent/apply-logs", map[string]any{
|
|
"node_id": "node-001",
|
|
"version": activeConfig.Version,
|
|
"result": service.ApplyResultFailed,
|
|
"message": "nginx reload failed",
|
|
})
|
|
var failedApplyLog model.ApplyLog
|
|
decodeResponseData(t, failedApplyResp, &failedApplyLog)
|
|
if failedApplyLog.Result != service.ApplyResultFailed {
|
|
t.Fatal("expected failed apply log to be recorded")
|
|
}
|
|
|
|
nodesResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/nodes/", nil)
|
|
var nodes []service.NodeView
|
|
decodeResponseData(t, nodesResp, &nodes)
|
|
if len(nodes) != 1 {
|
|
t.Fatalf("expected 1 node, got %d", len(nodes))
|
|
}
|
|
if nodes[0].LatestApplyResult != service.ApplyResultFailed || nodes[0].LatestApplyMessage != "nginx reload failed" {
|
|
t.Fatal("expected node list to expose latest apply status")
|
|
}
|
|
if nodes[0].CurrentVersion != activeConfig.Version {
|
|
t.Fatal("expected node current_version to remain at last successful version")
|
|
}
|
|
if nodes[0].LastError != "nginx reload failed" {
|
|
t.Fatal("expected node last_error to reflect failed apply")
|
|
}
|
|
|
|
logsResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/apply-logs/?node_id=node-001", nil)
|
|
var logs []model.ApplyLog
|
|
decodeResponseData(t, logsResp, &logs)
|
|
if len(logs) != 2 {
|
|
t.Fatalf("expected 2 apply logs, got %d", len(logs))
|
|
}
|
|
|
|
oldTime := time.Now().Add(-common.NodeOfflineThreshold - time.Minute)
|
|
if err := model.DB.Model(&model.Node{}).Where("node_id = ?", "node-001").Update("last_seen_at", oldTime).Error; err != nil {
|
|
t.Fatalf("failed to update node last_seen_at: %v", err)
|
|
}
|
|
nodesResp = performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/nodes/", nil)
|
|
decodeResponseData(t, nodesResp, &nodes)
|
|
if nodes[0].Status != service.NodeStatusOffline {
|
|
t.Fatal("expected node to be shown as offline after timeout")
|
|
}
|
|
}
|
|
|
|
func performAgentJSONRequest(t *testing.T, engine http.Handler, 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("X-Agent-Token", common.AgentToken)
|
|
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 createRouteAndPublishVersion(t *testing.T, engine http.Handler, adminToken string) {
|
|
t.Helper()
|
|
createBody := map[string]any{
|
|
"domain": "agent.example.com",
|
|
"origin_url": "https://agent-origin.internal",
|
|
"enabled": true,
|
|
"remark": "agent route",
|
|
}
|
|
performJSONRequest(t, engine, adminToken, http.MethodPost, "/api/proxy-routes/", createBody)
|
|
performJSONRequest(t, engine, adminToken, http.MethodPost, "/api/config-versions/publish", nil)
|
|
}
|