mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-07 10:47:15 +00:00
1aa81428b8
The Speed column showed "--" for AmneziaWG (and MTProto, which has the identical gap) even while cumulative traffic totals were correct. XrayTrafficJob drives live speed by querying xray-core's own stats API and broadcasting the delta over websocket -- but AmneziaWG/MTProto never run inside xray-core's own runtime inbounds, so they're invisible to that API. Their own jobs already compute the same per-poll delta shape (that's what keeps cumulative totals correct) but never broadcast it. Reusing the existing "traffics"/"clientTraffics" broadcast would have two real bugs: the frontend's existing scope/replace logic would let each side clobber the other's speed on its next unrelated tick, and the websocket hub's per-message-type throttle is keyed only by message type, not caller -- since both sidecar jobs run on identical "@every 10s" grids registered milliseconds apart, one would silently lose almost every broadcast if both protocols were ever configured together. Fixed with a small unthrottled broadcast path (both sidecar jobs are already self-rate-limited by their own cron cadence) and protocol- namespaced wire keys, tracked in their own frontend state and merged into the existing inboundSpeed/clientSpeed only at read time -- so every existing consumer needs zero changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
60 lines
2.2 KiB
Go
60 lines
2.2 KiB
Go
package job
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
)
|
|
|
|
func TestSidecarTrafficPayload_UsesProtocolNamespacedKeys(t *testing.T) {
|
|
traffics := []*xray.Traffic{{IsInbound: true, Tag: "amneziawg-1", Up: 10, Down: 20}}
|
|
clientTraffics := []*xray.ClientTraffic{{Email: "a@b.c", Up: 10, Down: 20}}
|
|
|
|
payload := sidecarTrafficPayload("amneziawg", traffics, clientTraffics)
|
|
|
|
if _, ok := payload["amneziawgTraffics"]; !ok {
|
|
t.Fatal("expected amneziawgTraffics key")
|
|
}
|
|
if _, ok := payload["amneziawgClientTraffics"]; !ok {
|
|
t.Fatal("expected amneziawgClientTraffics key")
|
|
}
|
|
for _, wrongKey := range []string{"traffics", "clientTraffics", "mtprotoTraffics", "mtprotoClientTraffics"} {
|
|
if _, ok := payload[wrongKey]; ok {
|
|
t.Fatalf("payload must not contain %q -- it would collide with a different broadcast source", wrongKey)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSidecarTrafficPayload_DistinctProtocolsNamespacedIndependently(t *testing.T) {
|
|
amneziawgPayload := sidecarTrafficPayload("amneziawg", nil, nil)
|
|
mtprotoPayload := sidecarTrafficPayload("mtproto", nil, nil)
|
|
|
|
if _, ok := amneziawgPayload["mtprotoTraffics"]; ok {
|
|
t.Fatal("amneziawg payload must not contain mtproto keys")
|
|
}
|
|
if _, ok := mtprotoPayload["amneziawgTraffics"]; ok {
|
|
t.Fatal("mtproto payload must not contain amneziawg keys")
|
|
}
|
|
}
|
|
|
|
func TestSidecarTrafficPayload_EmptyInputsStillProduceBothKeys(t *testing.T) {
|
|
// The frontend clears a peer's speed when its tag/email is absent from
|
|
// the payload's arrays -- but only if the key itself is present. If the
|
|
// key vanished entirely for an idle poll, idle-clearing would never
|
|
// trigger and the last nonzero speed would stick forever.
|
|
payload := sidecarTrafficPayload("amneziawg", nil, nil)
|
|
|
|
if _, ok := payload["amneziawgTraffics"]; !ok {
|
|
t.Fatal("expected amneziawgTraffics key present even for nil input")
|
|
}
|
|
if _, ok := payload["amneziawgClientTraffics"]; !ok {
|
|
t.Fatal("expected amneziawgClientTraffics key present even for nil input")
|
|
}
|
|
}
|
|
|
|
func TestBroadcastSidecarTraffic_NoOpWithoutHub(t *testing.T) {
|
|
// No global web server/hub is configured in this test binary, so
|
|
// websocket.HasClients() is false -- this must return without panicking.
|
|
broadcastSidecarTraffic("amneziawg", nil, nil)
|
|
}
|