mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-02 00:46:38 +08:00
216 lines
6.6 KiB
Go
216 lines
6.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"go-backend/internal/store/repo"
|
|
)
|
|
|
|
func TestTunnelCreatePersistsProbeTargetAndListReturnsConfiguredValue(t *testing.T) {
|
|
h := setupProbeTargetTunnelHandler(t)
|
|
body := bytes.NewReader([]byte(`{
|
|
"name":"custom-target",
|
|
"type":1,
|
|
"flow":1,
|
|
"trafficRatio":1,
|
|
"status":1,
|
|
"inNodeId":[{"nodeId":10,"protocol":"tls"}],
|
|
"probeTargetHost":"speed.example.com",
|
|
"probeTargetPort":8443
|
|
}`))
|
|
|
|
res := httptest.NewRecorder()
|
|
h.tunnelCreate(res, httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/create", body))
|
|
assertProbeTargetSuccess(t, res)
|
|
|
|
listRes := httptest.NewRecorder()
|
|
h.tunnelList(listRes, httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/list", nil))
|
|
var payload struct {
|
|
Code int `json:"code"`
|
|
Data []map[string]any `json:"data"`
|
|
}
|
|
decodeProbeTargetResponse(t, listRes, &payload)
|
|
if payload.Code != 0 {
|
|
t.Fatalf("expected success, got code %d", payload.Code)
|
|
}
|
|
item := payload.Data[0]
|
|
if item["probeTargetHost"] != "speed.example.com" || item["probeTargetPort"] != float64(8443) {
|
|
t.Fatalf("unexpected probe target in list response: %+v", item)
|
|
}
|
|
}
|
|
|
|
func TestTunnelUpdatePersistsDefaultProbeTargetAsEmpty(t *testing.T) {
|
|
h := setupProbeTargetTunnelHandler(t)
|
|
seedProbeTargetTunnel(t, h, 77, "existing", "old.example.com", 9443)
|
|
body := bytes.NewReader([]byte(`{
|
|
"id":77,
|
|
"name":"existing",
|
|
"type":1,
|
|
"flow":1,
|
|
"trafficRatio":1,
|
|
"status":1,
|
|
"inNodeId":[{"nodeId":10,"protocol":"tls"}],
|
|
"probeTargetHost":"",
|
|
"probeTargetPort":0
|
|
}`))
|
|
|
|
res := httptest.NewRecorder()
|
|
h.tunnelUpdate(res, httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/update", body))
|
|
assertProbeTargetSuccess(t, res)
|
|
|
|
items, err := h.repo.ListTunnels()
|
|
if err != nil {
|
|
t.Fatalf("list tunnels: %v", err)
|
|
}
|
|
item := findProbeTargetTunnelItem(t, items, 77)
|
|
if item["probeTargetHost"] != "" || item["probeTargetPort"] != 0 {
|
|
t.Fatalf("expected default target to round-trip as empty/0, got %+v", item)
|
|
}
|
|
}
|
|
|
|
func TestTunnelCreateRejectsInvalidProbeTarget(t *testing.T) {
|
|
h := setupProbeTargetTunnelHandler(t)
|
|
body := bytes.NewReader([]byte(`{
|
|
"name":"bad-target",
|
|
"type":1,
|
|
"flow":1,
|
|
"trafficRatio":1,
|
|
"status":1,
|
|
"inNodeId":[{"nodeId":10,"protocol":"tls"}],
|
|
"probeTargetHost":"https://example.com",
|
|
"probeTargetPort":443
|
|
}`))
|
|
|
|
res := httptest.NewRecorder()
|
|
h.tunnelCreate(res, httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/create", body))
|
|
var payload struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
}
|
|
decodeProbeTargetResponse(t, res, &payload)
|
|
if payload.Code == 0 || payload.Msg == "" {
|
|
t.Fatalf("expected validation failure, got %+v", payload)
|
|
}
|
|
}
|
|
|
|
func TestTunnelUpdateInvalidProbeTargetDoesNotCleanFederationBindings(t *testing.T) {
|
|
h := setupProbeTargetTunnelHandler(t)
|
|
seedProbeTargetTunnel(t, h, 88, "existing", "old.example.com", 9443)
|
|
seedProbeTargetFederationBinding(t, h, 88)
|
|
body := bytes.NewReader([]byte(`{
|
|
"id":88,
|
|
"name":"existing",
|
|
"type":1,
|
|
"flow":1,
|
|
"trafficRatio":1,
|
|
"status":1,
|
|
"inNodeId":[{"nodeId":10,"protocol":"tls"}],
|
|
"probeTargetHost":"https://example.com",
|
|
"probeTargetPort":443
|
|
}`))
|
|
|
|
res := httptest.NewRecorder()
|
|
h.tunnelUpdate(res, httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/update", body))
|
|
var payload struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
}
|
|
decodeProbeTargetResponse(t, res, &payload)
|
|
if payload.Code == 0 || payload.Msg == "" {
|
|
t.Fatalf("expected validation failure, got %+v", payload)
|
|
}
|
|
|
|
bindings, err := h.repo.ListActiveFederationTunnelBindingsByTunnel(88)
|
|
if err != nil {
|
|
t.Fatalf("list federation bindings: %v", err)
|
|
}
|
|
if len(bindings) != 1 {
|
|
t.Fatalf("expected federation binding to remain after invalid update, got %d", len(bindings))
|
|
}
|
|
}
|
|
|
|
func setupProbeTargetTunnelHandler(t *testing.T) *Handler {
|
|
t.Helper()
|
|
r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db"))
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = r.Close() })
|
|
h := New(r, "secret")
|
|
now := time.Now().UnixMilli()
|
|
if err := r.DB().Exec(`
|
|
INSERT INTO node(id, name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx)
|
|
VALUES(10, 'entry-a', 'entry-secret', '10.0.0.1', '10.0.0.1', '', '30000-30010', '', 'v1', 1, 1, 1, ?, ?, 1, '[::]', '[::]', 0)
|
|
`, now, now).Error; err != nil {
|
|
t.Fatalf("insert node: %v", err)
|
|
}
|
|
return h
|
|
}
|
|
|
|
func seedProbeTargetTunnel(t *testing.T, h *Handler, id int64, name string, host string, port int) {
|
|
t.Helper()
|
|
now := time.Now().UnixMilli()
|
|
if err := h.repo.DB().Exec(`
|
|
INSERT INTO tunnel(id, name, traffic_ratio, type, protocol, flow, created_time, updated_time, status, inx, ip_preference, probe_target_host, probe_target_port)
|
|
VALUES(?, ?, 1, 1, 'tls', 1, ?, ?, 1, ?, '', ?, ?)
|
|
`, id, name, now, now, id, host, port).Error; err != nil {
|
|
t.Fatalf("insert tunnel: %v", err)
|
|
}
|
|
if err := h.repo.DB().Exec(`
|
|
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
|
|
VALUES(?, '1', 10, 30001, 'round', 1, 'tls')
|
|
`, id).Error; err != nil {
|
|
t.Fatalf("insert chain: %v", err)
|
|
}
|
|
}
|
|
|
|
func seedProbeTargetFederationBinding(t *testing.T, h *Handler, tunnelID int64) {
|
|
t.Helper()
|
|
now := time.Now().UnixMilli()
|
|
if err := h.repo.DB().Exec(`
|
|
INSERT INTO federation_tunnel_binding(tunnel_id, node_id, chain_type, hop_inx, remote_url, resource_key, remote_binding_id, allocated_port, status, created_time, updated_time)
|
|
VALUES(?, 10, 1, 0, 'http://peer.example', ?, 'remote-binding', 30001, 1, ?, ?)
|
|
`, tunnelID, "probe-target-test-binding", now, now).Error; err != nil {
|
|
t.Fatalf("insert federation binding: %v", err)
|
|
}
|
|
}
|
|
|
|
func assertProbeTargetSuccess(t *testing.T, res *httptest.ResponseRecorder) {
|
|
t.Helper()
|
|
var payload struct {
|
|
Code int `json:"code"`
|
|
Msg string `json:"msg"`
|
|
}
|
|
decodeProbeTargetResponse(t, res, &payload)
|
|
if payload.Code != 0 {
|
|
t.Fatalf("expected success, got %+v", payload)
|
|
}
|
|
}
|
|
|
|
func decodeProbeTargetResponse(t *testing.T, res *httptest.ResponseRecorder, v any) {
|
|
t.Helper()
|
|
if res.Code != http.StatusOK {
|
|
t.Fatalf("expected HTTP %d, got %d", http.StatusOK, res.Code)
|
|
}
|
|
if err := json.NewDecoder(res.Body).Decode(v); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
}
|
|
|
|
func findProbeTargetTunnelItem(t *testing.T, items []map[string]interface{}, id int64) map[string]interface{} {
|
|
t.Helper()
|
|
for _, item := range items {
|
|
if asInt64(item["id"], 0) == id {
|
|
return item
|
|
}
|
|
}
|
|
t.Fatalf("tunnel %d not found: %+v", id, items)
|
|
return nil
|
|
}
|