Files
lislgosms/gateway/internal/control/server_test.go
T

223 lines
6.8 KiB
Go

package control
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"cmpp-platform/gateway/internal/inbound"
)
func TestConnectChannelCallbacksConnectedState(t *testing.T) {
var callback ConnectionStateCallback
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/admin/gateway/connections" {
t.Fatalf("unexpected callback path: %s", r.URL.Path)
}
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil {
t.Fatalf("decode callback: %v", err)
}
w.WriteHeader(http.StatusOK)
}))
defer api.Close()
handler := handlerWithDial(api.URL+"/api", func(context.Context, ConnectChannelCommand) error {
return nil
})
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/connections/connect", strings.NewReader(validConnectCommand()))
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusOK {
t.Fatalf("unexpected response status: %d body=%s", resp.Code, resp.Body.String())
}
if callback.Status != "connected" || callback.CurrentConnections != 2 || callback.DesiredConnections != 2 {
t.Fatalf("unexpected callback state: %+v", callback)
}
if callback.ChannelID != "channel-1" || callback.ConnectionID != "channel-1:primary" {
t.Fatalf("unexpected callback identity: %+v", callback)
}
if callback.LastConnectedAt == "" || callback.LastHeartbeatAt == "" {
t.Fatalf("expected connection timestamps: %+v", callback)
}
}
func TestConnectChannelCallbacksFailedState(t *testing.T) {
var callback ConnectionStateCallback
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil {
t.Fatalf("decode callback: %v", err)
}
w.WriteHeader(http.StatusOK)
}))
defer api.Close()
handler := handlerWithDial(api.URL+"/api", func(context.Context, ConnectChannelCommand) error {
return errTestDial
})
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/connections/connect", strings.NewReader(validConnectCommand()))
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusOK {
t.Fatalf("unexpected response status: %d body=%s", resp.Code, resp.Body.String())
}
if callback.Status != "failed" || callback.CurrentConnections != 0 || callback.LastError == "" {
t.Fatalf("unexpected callback state: %+v", callback)
}
}
func TestConnectChannelRejectsInvalidCommand(t *testing.T) {
handler := handlerWithDial("", func(context.Context, ConnectChannelCommand) error {
return nil
})
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/connections/connect", strings.NewReader(`{"messageType":"SubmitCommand"}`))
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusBadRequest {
t.Fatalf("unexpected response status: %d", resp.Code)
}
}
func TestRecoveryCandidatesEndpointReturnsView(t *testing.T) {
handler := handlerWithServer(Server{
RecoveryCandidates: func(context.Context) ([]inbound.DownstreamPresence, error) {
return []inbound.DownstreamPresence{{
Account: "100001",
GatewayInstanceID: "gateway-a",
State: "connected",
UpdatedAt: time.Now().UTC(),
}}, nil
},
})
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/downstream/recovery-candidates", nil)
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusOK {
t.Fatalf("unexpected response status: %d body=%s", resp.Code, resp.Body.String())
}
var payload []inbound.DownstreamPresence
if err := json.Unmarshal(resp.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if len(payload) != 1 || payload[0].Account != "100001" {
t.Fatalf("unexpected payload: %+v", payload)
}
}
func TestRecoveryStatusesEndpointReturnsView(t *testing.T) {
handler := handlerWithServer(Server{
RecoveryStatuses: func(context.Context) ([]inbound.DownstreamRecoveryStatus, error) {
return []inbound.DownstreamRecoveryStatus{{
Account: "100001",
GatewayInstanceID: "gateway-a",
State: "waiting_connection",
AttemptCount: 2,
}}, nil
},
})
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/downstream/recovery-statuses", nil)
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusOK {
t.Fatalf("unexpected response status: %d body=%s", resp.Code, resp.Body.String())
}
var payload []inbound.DownstreamRecoveryStatus
if err := json.Unmarshal(resp.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if len(payload) != 1 || payload[0].State != "waiting_connection" || payload[0].AttemptCount != 2 {
t.Fatalf("unexpected payload: %+v", payload)
}
}
func TestRecoveryOverviewEndpointReturnsCombinedView(t *testing.T) {
handler := handlerWithServer(Server{
RecoveryCandidates: func(context.Context) ([]inbound.DownstreamPresence, error) {
return []inbound.DownstreamPresence{{
Account: "100001",
GatewayInstanceID: "gateway-a",
State: "connected",
}}, nil
},
RecoveryStatuses: func(context.Context) ([]inbound.DownstreamRecoveryStatus, error) {
return []inbound.DownstreamRecoveryStatus{{
Account: "100001",
GatewayInstanceID: "gateway-a",
State: "success",
}}, nil
},
})
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/downstream/recovery-overview", nil)
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusOK {
t.Fatalf("unexpected response status: %d body=%s", resp.Code, resp.Body.String())
}
var payload DownstreamRecoveryOverview
if err := json.Unmarshal(resp.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if len(payload.Candidates) != 1 || payload.Candidates[0].Account != "100001" {
t.Fatalf("unexpected candidate payload: %+v", payload.Candidates)
}
if len(payload.Statuses) != 1 || payload.Statuses[0].State != "success" {
t.Fatalf("unexpected status payload: %+v", payload.Statuses)
}
}
type testDialError struct{}
func (testDialError) Error() string {
return "dial failed"
}
var errTestDial testDialError
func handlerWithDial(apiBaseURL string, dial DialFunc) http.Handler {
return handlerWithServer(Server{APIBaseURL: apiBaseURL, Dial: dial})
}
func handlerWithServer(server Server) http.Handler {
mux := http.NewServeMux()
Register(mux, server)
return mux
}
func validConnectCommand() string {
return `{
"schemaVersion": "v1",
"messageType": "ConnectChannel",
"traceId": "trace-1",
"channelId": "channel-1",
"connectionId": "channel-1:primary",
"reason": "channel_created",
"desiredConnections": 2,
"channel": {
"code": "CMPP-A",
"name": "主通道",
"gatewayHost": "127.0.0.1",
"gatewayPort": 17890,
"account": "sp",
"passwordCipher": "secret",
"srcId": "10690000",
"cmppVersion": "3.0",
"rateLimitPerSecond": 100
}
}`
}