Files
Cyrene_v2/internal/channel/adapter/adapter_test.go
T
Aska 36bd19e997
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(weixin): 扫码登录二维码真实可扫——前端生成QR(修坏图) + DecodeConfig容忍空config
weixin qrcode_img_content 是微信liteapp HTML页(非图片),后端fetch转base64只得<!doctype>→前端img坏图。
- 后端 login-qr 改返回 qrUrl(qrcode_img_content URL)+qrCode(原始qrcode),去掉坏 qrImage
- 前端用 qrcode 库从 qrUrl 生成真实二维码图片(微信扫https链接即开连接页), 新增 qrcode+@types/qrcode(纯UI依赖)
- DecodeConfig: nil config map 当空map解码(不报错), 否则 pending账号(空config)构建被跳过→未注册→登录端点404
- 测试更新(DecodeConfigNil/admin QR端点断言 qrUrl+qrCode)
- 另有 weixinAccountFor 裸weixin兜底(此前提交 d3b0921) 保证 accountId 命中
- 实测: login-qr 200{waiting, qrUrl=89}, 前端能看到可扫二维码
build/vet/test 全绿, 已部署.
2026-09-07 21:32:19 +08:00

113 lines
3.4 KiB
Go

package adapter
import (
"context"
"testing"
)
// mockAdapter is a minimal in-memory PlatformAdapter used to prove the
// interface is satisfiable and to exercise its surface. It mocks the contract,
// not a concrete adapter type.
type mockAdapter struct {
name string
cfg map[string]any
caps Capabilities
handler MessageHandler
sent []UnifiedMessage
}
func (m *mockAdapter) Name() string { return m.name }
func (m *mockAdapter) Config() map[string]any { return m.cfg }
func (m *mockAdapter) Connect(context.Context) error {
return nil
}
func (m *mockAdapter) Disconnect(context.Context) error { return nil }
func (m *mockAdapter) IsConnected() bool { return true }
func (m *mockAdapter) HealthCheck(context.Context) error {
return nil
}
func (m *mockAdapter) ReadMessages(context.Context, func(UnifiedMessage)) {}
func (m *mockAdapter) SendMessage(_ context.Context, resp UnifiedResponse) error {
m.sent = resp.Messages
return nil
}
func (m *mockAdapter) SendProactive(context.Context, UnifiedMessage) error { return nil }
func (m *mockAdapter) SetMessageHandler(fn MessageHandler) { m.handler = fn }
func (m *mockAdapter) Capabilities() Capabilities { return m.caps }
func (m *mockAdapter) ListChannels(context.Context) ([]ChannelInfo, error) {
return nil, nil
}
// Interface conformance is enforced at compile time.
var _ PlatformAdapter = (*mockAdapter)(nil)
func TestMockAdapterSatisfiesContract(t *testing.T) {
t.Parallel()
ad := &mockAdapter{
name: "telegram",
cfg: map[string]any{"token": "t-1"},
caps: DefaultCapabilities(),
}
if got := ad.Name(); got != "telegram" {
t.Errorf("Name() = %q, want telegram", got)
}
if got := ad.Config()["token"]; got != "t-1" {
t.Errorf("Config() token = %v, want t-1", got)
}
if got := ad.IsConnected(); !got {
t.Error("IsConnected() = false, want true")
}
if got := ad.Capabilities().MaxMessageLength; got != defaultMaxMessageLength {
t.Errorf("Capabilities().MaxMessageLength = %d, want %d", got, defaultMaxMessageLength)
}
}
func TestMockAdapterSendMessageRecords(t *testing.T) {
t.Parallel()
ad := &mockAdapter{}
msg := UnifiedMessage{ChannelType: ChannelDirect, ContentType: ContentText, Content: "pong"}
resp := UnifiedResponse{Messages: []UnifiedMessage{msg}, ReplyTo: "m-1", Hint: "ok"}
if err := ad.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage() error = %v", err)
}
if len(ad.sent) != 1 || ad.sent[0].Content != "pong" {
t.Errorf("sent = %+v, want one message with content pong", ad.sent)
}
}
func TestDecodeConfig(t *testing.T) {
t.Parallel()
type cfg struct {
Token string
Port int
}
raw := map[string]any{"token": "abc", "port": 8080}
var c cfg
if err := DecodeConfig(raw, &c); err != nil {
t.Fatalf("DecodeConfig() error = %v", err)
}
if c.Token != "abc" || c.Port != 8080 {
t.Errorf("DecodeConfig() = %+v, want {abc 8080}", c)
}
}
func TestDecodeConfigNil(t *testing.T) {
t.Parallel()
// A nil config map is treated as empty (decodes to zero value), so a pending
// account with no keys yet builds with defaults rather than erroring.
var c struct{ Token string }
if err := DecodeConfig(nil, &c); err != nil {
t.Fatalf("DecodeConfig(nil, ...) error = %v, want nil (empty config)", err)
}
if c.Token != "" {
t.Fatalf("Token = %q, want empty zero value", c.Token)
}
}