feat(bridge): QQ adapter reconnect and send rate limiting
- supervisor loop keeps the OBv11 WebSocket alive, reconnecting with exponential backoff (1s→30s) when the connection drops; Start no longer fails permanently when NapCat is temporarily unreachable - Send enforces MinGap between messages (from SendOptions or the adapter's send_interval_ms setting, now wired from the admin panel) - parseMessage no longer panics on events missing sender/time fields - tests: reconnect after drop, rate limit enforcement, malformed events, send while disconnected
This commit is contained in:
1 parent
8603839711
commit
9f30cdd086
3 files changed
+237
-33
No files matched your search
@@ -0,0 +1,120 @@
|
||||
package qq
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/services/platform-bridge"
|
||||
)
|
||||
|
||||
func wsServer(t *testing.T, onConn func(*websocket.Conn, int)) *httptest.Server {
|
||||
t.Helper()
|
||||
var upgrader = websocket.Upgrader{}
|
||||
var count atomic.Int32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n := int(count.Add(1))
|
||||
onConn(conn, n)
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
||||
func wsURL(s *httptest.Server) string {
|
||||
return "ws" + strings.TrimPrefix(s.URL, "http")
|
||||
}
|
||||
|
||||
func TestParseMessageMissingSender(t *testing.T) {
|
||||
a := New(Config{Name: "test", BotQQ: "123"})
|
||||
// Should not panic on malformed events
|
||||
if msg := a.parseMessage(map[string]any{"post_type": "message", "message_type": "private"}); msg != nil {
|
||||
t.Error("expected nil for event without sender")
|
||||
}
|
||||
if msg := a.parseMessage(map[string]any{"post_type": "notice"}); msg != nil {
|
||||
t.Error("expected nil for non-message post_type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconnectAfterDrop(t *testing.T) {
|
||||
srv := wsServer(t, func(conn *websocket.Conn, n int) {
|
||||
if n == 1 {
|
||||
conn.Close() // drop immediately to trigger reconnect
|
||||
return
|
||||
}
|
||||
time.Sleep(2 * time.Second) // hold the second connection
|
||||
conn.Close()
|
||||
})
|
||||
|
||||
a := New(Config{Name: "test", WSURL: wsURL(srv)})
|
||||
if err := a.Start(); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer a.Stop()
|
||||
|
||||
// Wait for the second connection (first is dropped immediately)
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if a.IsConnected() {
|
||||
return // reconnected successfully
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
t.Fatal("adapter did not reconnect after connection drop")
|
||||
}
|
||||
|
||||
func TestSendRateLimit(t *testing.T) {
|
||||
received := make(chan struct{}, 4)
|
||||
srv := wsServer(t, func(conn *websocket.Conn, n int) {
|
||||
go func() {
|
||||
for {
|
||||
if _, _, err := conn.ReadMessage(); err != nil {
|
||||
return
|
||||
}
|
||||
received <- struct{}{}
|
||||
}
|
||||
}()
|
||||
time.Sleep(3 * time.Second)
|
||||
conn.Close()
|
||||
})
|
||||
|
||||
a := New(Config{Name: "test", WSURL: wsURL(srv)})
|
||||
a.Start()
|
||||
defer a.Stop()
|
||||
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for !a.IsConnected() && time.Now().Before(deadline) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
if !a.IsConnected() {
|
||||
t.Fatal("not connected")
|
||||
}
|
||||
|
||||
<-received // auth message
|
||||
|
||||
start := time.Now()
|
||||
if err := a.Send("10001", "第一条", bridge.SendOptions{MinGap: 300 * time.Millisecond}); err != nil {
|
||||
t.Fatalf("send 1: %v", err)
|
||||
}
|
||||
if err := a.Send("10001", "第二条", bridge.SendOptions{MinGap: 300 * time.Millisecond}); err != nil {
|
||||
t.Fatalf("send 2: %v", err)
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed < 300*time.Millisecond {
|
||||
t.Errorf("rate limit not enforced: two sends took %v", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendWhenDisconnected(t *testing.T) {
|
||||
a := New(Config{Name: "test", WSURL: "ws://127.0.0.1:1/unreachable"})
|
||||
if err := a.Send("10001", "hi", bridge.SendOptions{}); err == nil {
|
||||
t.Error("expected error when not connected")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user