- 只翻注释/文档字符串:剥掉行尾注释后,每条「-」都有逐字节相同的「+」对应行(8 组,代码零改动) - 保留协议/线上标识符原文:errcode、bot_token、context_token、getupdates、binded_redirect、PKCS#7、ECB 等 - 术语对齐既有译法:owner→主人、peer→对端、long poll→长轮询、backoff→退避、sentinel error→哨兵错误 - 已知既有欠债(未顺手修):login_reuse_test.go 在 HEAD 上就未 gofmt 干净;login_test.go 的 TestStartLogin_ScanStatusTransition 91 行 >80(HEAD 同为 91 行)
201 lines
6.0 KiB
Go
201 lines
6.0 KiB
Go
package weixin_ilink
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
)
|
|
|
|
func TestAdapter_Name_And_DefaultCapabilities(t *testing.T) {
|
|
t.Parallel()
|
|
ad, err := New(Config{BaseURL: "https://ilinkai.weixin.qq.com"}, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
if got := ad.Name(); got != "weixin-ilink" {
|
|
t.Fatalf("Name() = %q, want weixin-ilink", got)
|
|
}
|
|
caps := ad.Capabilities()
|
|
if !caps.Proactive {
|
|
t.Error("Capabilities().Proactive = false, want true (iLink has a 24h window)")
|
|
}
|
|
if !caps.Image {
|
|
t.Error("Capabilities().Image = false, want true")
|
|
}
|
|
if got := caps.Normalize().MaxMessageLength; got <= 0 {
|
|
t.Fatalf("MaxMessageLength = %d, want > 0", got)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_New_RejectsBadBaseURL(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := New(Config{BaseURL: "not-a-url"}, nil, nil)
|
|
if !errors.Is(err, ErrInvalidConfig) {
|
|
t.Fatalf("err = %v, want ErrInvalidConfig", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Config_ReturnsSnapshot(t *testing.T) {
|
|
t.Parallel()
|
|
ad, err := New(Config{BaseURL: "https://ilinkai.weixin.qq.com", BotID: "bot-1"}, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
cfg := ad.Config()
|
|
if cfg["bot_id"] != "bot-1" {
|
|
t.Fatalf("Config()[bot_id] = %v, want bot-1", cfg["bot_id"])
|
|
}
|
|
if cfg["base_url"] != "https://ilinkai.weixin.qq.com" {
|
|
t.Fatalf("Config()[base_url] = %v", cfg["base_url"])
|
|
}
|
|
// 经 DecodeConfig 往返必须保留类型化配置。
|
|
var back Config
|
|
if err := adapter.DecodeConfig(cfg, &back); err != nil {
|
|
t.Fatalf("DecodeConfig: %v", err)
|
|
}
|
|
if back.BaseURL != "https://ilinkai.weixin.qq.com" || back.BotID != "bot-1" {
|
|
t.Fatalf("decoded config = %+v, want base_url/bot_id preserved", back)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Connect_WithToken_IsIdempotent(t *testing.T) {
|
|
t.Parallel()
|
|
ad, err := New(Config{BaseURL: "https://ilinkai.weixin.qq.com", BotToken: "tok-1"}, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
if ad.IsConnected() {
|
|
t.Fatal("IsConnected() = true before Connect")
|
|
}
|
|
if err := ad.Connect(context.Background()); err != nil {
|
|
t.Fatalf("Connect: %v", err)
|
|
}
|
|
if !ad.IsConnected() {
|
|
t.Fatal("IsConnected() = false after Connect")
|
|
}
|
|
// 第二次调用是无副作用的成功。
|
|
if err := ad.Connect(context.Background()); err != nil {
|
|
t.Fatalf("second Connect: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_HealthCheck(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("disconnected", func(t *testing.T) {
|
|
t.Parallel()
|
|
ad, _ := New(Config{BaseURL: "https://ilinkai.weixin.qq.com"}, nil, nil)
|
|
if err := ad.HealthCheck(context.Background()); !errors.Is(err, adapter.ErrNotConnected) {
|
|
t.Fatalf("HealthCheck = %v, want ErrNotConnected", err)
|
|
}
|
|
})
|
|
t.Run("connected_with_token", func(t *testing.T) {
|
|
t.Parallel()
|
|
ad, _ := New(Config{BaseURL: "https://ilinkai.weixin.qq.com", BotToken: "tok-1"}, nil, nil)
|
|
if err := ad.Connect(context.Background()); err != nil {
|
|
t.Fatalf("Connect: %v", err)
|
|
}
|
|
if err := ad.HealthCheck(context.Background()); err != nil {
|
|
t.Fatalf("HealthCheck = %v, want nil", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAdapter_Disconnect_IsIdempotent(t *testing.T) {
|
|
t.Parallel()
|
|
ad, _ := New(Config{BaseURL: "https://ilinkai.weixin.qq.com", BotToken: "tok-1"}, nil, nil)
|
|
_ = ad.Connect(context.Background())
|
|
if err := ad.Disconnect(context.Background()); err != nil {
|
|
t.Fatalf("Disconnect: %v", err)
|
|
}
|
|
if ad.IsConnected() {
|
|
t.Fatal("IsConnected() = true after Disconnect")
|
|
}
|
|
if err := ad.Disconnect(context.Background()); err != nil {
|
|
t.Fatalf("second Disconnect: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_ListChannels(t *testing.T) {
|
|
t.Parallel()
|
|
ad, _ := New(Config{BaseURL: "https://ilinkai.weixin.qq.com", BotID: "bot-9"}, nil, nil)
|
|
chans, err := ad.ListChannels(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("ListChannels: %v", err)
|
|
}
|
|
if len(chans) != 1 {
|
|
t.Fatalf("ListChannels() = %d channels, want 1", len(chans))
|
|
}
|
|
if chans[0].ChannelID != "bot-9" || chans[0].ChannelType != adapter.ChannelDirect {
|
|
t.Fatalf("channel = %+v, want {bot-9 direct}", chans[0])
|
|
}
|
|
}
|
|
|
|
func TestAdapter_SetMessageHandler_And_RecordInbound(t *testing.T) {
|
|
t.Parallel()
|
|
ad, _ := New(Config{BaseURL: "https://ilinkai.weixin.qq.com"}, nil, nil)
|
|
|
|
called := false
|
|
ad.SetMessageHandler(func(ctx context.Context, m adapter.UnifiedMessage) { called = true })
|
|
if ad.handlerLocked() == nil {
|
|
t.Fatal("handlerLocked() = nil after SetMessageHandler")
|
|
}
|
|
ad.SetMessageHandler(nil)
|
|
if ad.handlerLocked() != nil {
|
|
t.Fatal("handlerLocked() != nil after clearing")
|
|
}
|
|
if called {
|
|
t.Fatal("handler called before any message")
|
|
}
|
|
|
|
// recordInbound 必须刷新按对端的 token 与最后消息时间。
|
|
ad.recordInbound("tok-abc", adapter.UnifiedMessage{
|
|
SenderID: "user-1",
|
|
Timestamp: ad.clock(),
|
|
})
|
|
if got := ad.contextFor("user-1"); got != "tok-abc" {
|
|
t.Fatalf("contextFor(user-1) = %q, want tok-abc", got)
|
|
}
|
|
}
|
|
|
|
func TestBuildClientVersion(t *testing.T) {
|
|
t.Parallel()
|
|
cases := map[string]string{
|
|
"2.4.8": "132104", // 官方 @tencent-weixin/openclaw-weixin 2.4.8
|
|
"1.0.11": "65547", // 官方注释示例 0x0001000B
|
|
"1.2.3": "66051",
|
|
"0.0.0": "0",
|
|
"bad": "0",
|
|
}
|
|
for in, want := range cases {
|
|
if got := buildClientVersion(in); got != want {
|
|
t.Fatalf("buildClientVersion(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Recipient_ResolvesGroupAndSender(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
msg adapter.UnifiedMessage
|
|
wantPeer string
|
|
wantGroup bool
|
|
}{
|
|
{name: "direct sender", msg: adapter.UnifiedMessage{SenderID: "u1"}, wantPeer: "u1"},
|
|
{name: "group id", msg: adapter.UnifiedMessage{ChannelType: adapter.ChannelGroup, GroupID: "g1"}, wantPeer: "g1", wantGroup: true},
|
|
{name: "default channel id", msg: adapter.UnifiedMessage{ChannelID: "c1"}, wantPeer: "c1"},
|
|
}
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
peer, group := recipient(tc.msg)
|
|
if peer != tc.wantPeer || group != tc.wantGroup {
|
|
t.Fatalf("recipient() = (%q,%v), want (%q,%v)", peer, group, tc.wantPeer, tc.wantGroup)
|
|
}
|
|
})
|
|
}
|
|
}
|