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 全绿, 已部署.
113 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|