Files
huya-go/internal/huya/wss_client_test.go
T
yml2213 36aadae90a 按 10.13 抓包对齐 WSS:host 自动解析、exp 回传、pingSignal 与兑换记录字段
- 新增 launch.queryHttpDns:类型定义 + HTTPClient.QueryHttpDns + WSSClient.resolveHost(TTL 缓存、多 host 轮询、失败回退默认 host);Host 留空或等于历史默认值即视为自动解析
- WSS 握手 baseinfo 补 tag5=exp 与 tag10 空 customHeaders;UpdateUserExpsReq(cmd23) 改为回传 getConfig 的版本 map(缓存供重连复用)
- 新增 launch.pingSignal:HTTP 预热改走 pingSignal,WSS 预热在建连后探测业务通道(失败仅记日志)
- UserPrizeInfo 补 tag12 orderId/tag14 图标/tag16 prizeId/tag17 shopItemId/tag19 itemId/tag20 ext/tag21 exchangeDate,前端复盘表加订单号列
- taf 补 WriteListString/ReadListString/ReadMapStringInt64/ReadMapStringStruct
- 新增 internal/huya/launch_test.go:用抓包真实报文做字节级夹具(baseinfo 122B、queryHttpDns 请求 93B、记录 308B 等)
- CLI -wss-host 默认改为留空;GUI 设置支持留空并给出占位提示;README/WSS_NOTES 同步协议细节
2026-10-13 20:19:14 +08:00

170 lines
3.8 KiB
Go

package huya
import (
"context"
"encoding/base64"
"encoding/hex"
"net/url"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"huya-go/internal/taf"
)
func TestWSRegisterGroupReqEncoding(t *testing.T) {
w := taf.NewWriter()
req := &wsRegisterGroupReq{GroupIDs: []string{WSSGroupID}}
if err := req.WriteTo(w); err != nil {
t.Fatal(err)
}
got := hex.EncodeToString(w.Bytes())
want := "09000106066c6976653a301600"
if got != want {
t.Fatalf("register group bytes = %s, want %s", got, want)
}
}
func TestWSUpdateUserExpsReqEncoding(t *testing.T) {
w := taf.NewWriter()
if err := (&wsUpdateUserExpsReq{}).WriteTo(w); err != nil {
t.Fatal(err)
}
got := hex.EncodeToString(w.Bytes())
want := "080c"
if got != want {
t.Fatalf("update user exps bytes = %s, want %s", got, want)
}
}
func TestGenerateWSSBaseinfoMatchesBrowserShape(t *testing.T) {
encoded, err := GenerateWSSBaseinfo(123, "guid-value", "58664.107263")
if err != nil {
t.Fatal(err)
}
unescaped, err := url.QueryUnescape(encoded)
if err != nil {
t.Fatal(err)
}
raw, err := base64.StdEncoding.DecodeString(unescaped)
if err != nil {
t.Fatal(err)
}
r := taf.NewReader(raw)
uid, err := r.ReadInt64(0, true, 0)
if err != nil {
t.Fatal(err)
}
guid, err := r.ReadString(1, true, "")
if err != nil {
t.Fatal(err)
}
ua, err := r.ReadString(2, true, "")
if err != nil {
t.Fatal(err)
}
appSrc, err := r.ReadString(3, true, "")
if err != nil {
t.Fatal(err)
}
exp, err := r.ReadString(5, true, "")
if err != nil {
t.Fatal(err)
}
cookie, err := r.ReadString(8, false, "not-empty")
if err != nil {
t.Fatal(err)
}
traceID, err := r.ReadString(9, false, "not-empty")
if err != nil {
t.Fatal(err)
}
if uid != 123 || guid != "guid-value" || ua != WSSCookieUA || appSrc != "HUYA&ZH&2052" {
t.Fatalf("unexpected baseinfo: uid=%d guid=%q ua=%q appSrc=%q", uid, guid, ua, appSrc)
}
if exp != "58664.107263" {
t.Fatalf("baseinfo exp=%q, want 58664.107263", exp)
}
if cookie != "" || traceID != "" {
t.Fatalf("wss baseinfo should not carry cookie/trace: cookie=%q trace=%q", cookie, traceID)
}
}
func TestNewWSSCommandTraceMatchesBrowserShape(t *testing.T) {
trace := NewWSSCommandTrace()
parts := strings.Split(trace, ":")
if len(parts) != 4 {
t.Fatalf("trace parts = %v", parts)
}
if parts[0] == "" || parts[1] == "" || parts[0] != parts[1] {
t.Fatalf("unexpected trace prefix: %q", trace)
}
if parts[2] != "0" || parts[3] != "0" {
t.Fatalf("trace suffix = %q, want :0:0", trace)
}
}
func TestSessionSharesSingleDialPerUID(t *testing.T) {
client := NewWSSClient(nil)
dialStarted := make(chan struct{}, 1)
release := make(chan struct{})
var dials atomic.Int32
client.dialFunc = func(ctx context.Context, uid int64, cookie string) (*WSSSession, error) {
dials.Add(1)
select {
case dialStarted <- struct{}{}:
default:
}
<-release
return &WSSSession{
client: client,
uid: uid,
cookie: cookie,
closed: make(chan struct{}),
pending: map[int32]chan rpcResult{},
}, nil
}
const callers = 4
sessions := make([]*WSSSession, callers)
var wg sync.WaitGroup
for i := 0; i < callers; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
session, err := client.session(context.Background(), 42, "yyuid=42")
if err != nil {
t.Errorf("session: %v", err)
return
}
sessions[i] = session
}(i)
}
select {
case <-dialStarted:
case <-time.After(time.Second):
t.Fatal("dial was not started")
}
// 留出时间让其余调用进入等待分支。
time.Sleep(50 * time.Millisecond)
close(release)
wg.Wait()
if got := dials.Load(); got != 1 {
t.Fatalf("dials=%d, want 1", got)
}
for i, session := range sessions {
if session == nil {
t.Fatalf("caller %d got nil session", i)
}
if session != sessions[0] {
t.Fatalf("caller %d got a different session", i)
}
}
}