- 只翻注释/文档字符串:剥掉注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 术语对齐既有译法(owner→主人、persona→角色、engage→参与度判定、quiet hours→安静时段、backoff→退避) - 复核:go build 绿 + channelbridge 包测试绿;gofmt 干净
153 lines
4.4 KiB
Go
153 lines
4.4 KiB
Go
package channelbridge
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/user"
|
|
)
|
|
|
|
var errReload = errors.New("build failed")
|
|
|
|
// stubFactory 返回某平台的固定适配器集合,使重载测试
|
|
// 无需真实网络 I/O 即可驱动热替换。
|
|
type stubFactory struct {
|
|
adpts []adapter.PlatformAdapter
|
|
err error
|
|
}
|
|
|
|
func (f *stubFactory) Build(_ string) ([]adapter.PlatformAdapter, error) { return f.adpts, f.err }
|
|
|
|
func newReloadBridge(t *testing.T, factory PlatformFactory) *Bridge {
|
|
t.Helper()
|
|
b, err := New(Options{
|
|
Loop: &stubChatter{res: &loop.Result{Text: "hi"}},
|
|
Sessions: &stubSessions{sess: session.Session{SessionID: "s", Owner: true, MemWritable: true}},
|
|
Owner: &user.OwnerProfile{},
|
|
Factory: factory,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
t.Cleanup(func() { b.Stop(context.Background()) })
|
|
return b
|
|
}
|
|
|
|
func adapterIn(b *Bridge, name string) bool {
|
|
for _, a := range b.snapshot() {
|
|
if a.Name() == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func TestReloadReplacesAdapterWithNewConfig(t *testing.T) {
|
|
t.Parallel()
|
|
old := &fakeAdapter{name: "kook:conf0", cfg: map[string]any{"bot_token": "old"}}
|
|
factory := &stubFactory{}
|
|
b := newReloadBridge(t, factory)
|
|
b.Add(old)
|
|
b.Start()
|
|
if !b.IsConnected("kook:conf0") {
|
|
t.Fatal("expected old adapter connected after Start")
|
|
}
|
|
|
|
newConf0 := &fakeAdapter{name: "kook:conf0", cfg: map[string]any{"bot_token": "new"}}
|
|
newConf1 := &fakeAdapter{name: "kook:conf1", cfg: map[string]any{"bot_token": "next"}}
|
|
factory.adpts = []adapter.PlatformAdapter{newConf0, newConf1}
|
|
|
|
if err := b.Reload("kook"); err != nil {
|
|
t.Fatalf("Reload: %v", err)
|
|
}
|
|
|
|
// 新适配器已连接并注册。
|
|
if !newConf0.IsConnected() || !newConf1.IsConnected() {
|
|
t.Fatalf("new adapters not connected: conf0=%v conf1=%v", newConf0.IsConnected(), newConf1.IsConnected())
|
|
}
|
|
if !b.IsConnected("kook:conf0") || !b.IsConnected("kook:conf1") {
|
|
t.Fatalf("bridge does not report new adapters connected")
|
|
}
|
|
// 旧适配器实例已断开(其读取循环已停止)。
|
|
if old.IsConnected() {
|
|
t.Fatal("old adapter should be disconnected after reload")
|
|
}
|
|
}
|
|
|
|
func TestReloadRemovesRemovedAccount(t *testing.T) {
|
|
t.Parallel()
|
|
conf0 := &fakeAdapter{name: "kook:conf0", cfg: map[string]any{"bot_token": "t0"}}
|
|
conf1 := &fakeAdapter{name: "kook:conf1", cfg: map[string]any{"bot_token": "t1"}}
|
|
factory := &stubFactory{}
|
|
b := newReloadBridge(t, factory)
|
|
b.Add(conf0)
|
|
b.Add(conf1)
|
|
b.Start()
|
|
|
|
// 新配置只保留 conf1(conf0 被删除)。
|
|
factory.adpts = []adapter.PlatformAdapter{
|
|
&fakeAdapter{name: "kook:conf1", cfg: map[string]any{"bot_token": "t1b"}},
|
|
}
|
|
if err := b.Reload("kook"); err != nil {
|
|
t.Fatalf("Reload: %v", err)
|
|
}
|
|
if adapterIn(b, "kook:conf0") {
|
|
t.Fatal("removed account's adapter should be dropped from the registry")
|
|
}
|
|
if !adapterIn(b, "kook:conf1") {
|
|
t.Fatal("kept account's adapter should still be registered")
|
|
}
|
|
if !b.IsConnected("kook:conf1") {
|
|
t.Fatal("kept account should be connected")
|
|
}
|
|
}
|
|
|
|
func TestReloadOtherPlatformUntouched(t *testing.T) {
|
|
t.Parallel()
|
|
tg := &fakeAdapter{name: "telegram", cfg: map[string]any{"bot_token": "tg"}}
|
|
kook := &fakeAdapter{name: "kook:conf0", cfg: map[string]any{"bot_token": "k"}}
|
|
factory := &stubFactory{}
|
|
b := newReloadBridge(t, factory)
|
|
b.Add(tg)
|
|
b.Add(kook)
|
|
b.Start()
|
|
|
|
factory.adpts = []adapter.PlatformAdapter{
|
|
&fakeAdapter{name: "kook:conf0", cfg: map[string]any{"bot_token": "k2"}},
|
|
}
|
|
if err := b.Reload("kook"); err != nil {
|
|
t.Fatalf("Reload: %v", err)
|
|
}
|
|
if !adapterIn(b, "telegram") || !tg.IsConnected() {
|
|
t.Fatal("reloading kook should leave the telegram adapter untouched")
|
|
}
|
|
}
|
|
|
|
func TestReloadNilFactoryErrors(t *testing.T) {
|
|
t.Parallel()
|
|
b := newReloadBridge(t, nil)
|
|
if err := b.Reload("kook"); err != ErrNilFactory {
|
|
t.Fatalf("err = %v, want ErrNilFactory", err)
|
|
}
|
|
}
|
|
|
|
func TestReloadBeforeStartErrors(t *testing.T) {
|
|
t.Parallel()
|
|
b := newReloadBridge(t, &stubFactory{})
|
|
if err := b.Reload("kook"); err != ErrBridgeClosed {
|
|
t.Fatalf("err = %v, want ErrBridgeClosed (not started)", err)
|
|
}
|
|
}
|
|
|
|
func TestReloadBuildError(t *testing.T) {
|
|
t.Parallel()
|
|
b := newReloadBridge(t, &stubFactory{err: errReload})
|
|
if err := b.Reload("kook"); err == nil {
|
|
t.Fatal("expected a build error to surface")
|
|
}
|
|
}
|