package backthink import ( "context" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter" ) // TestBackthink_SetActiveSink_RebuildsPusher 验证槽位设置器会重建 // 推送器,使之后的推送路由到注入的活跃客户端,而不是 // New 时构建的默认 nil 槽 RouteNone 推送器。 func TestBackthink_SetActiveSink_RebuildsPusher(t *testing.T) { db := newTestDB(t) bt, err := New(Config{}, Deps{ Thinker: &mockThinker{text: "[我决定:push]\n[渠道:web]\nhi"}, State: &mockState{}, Gate: &mockGate{allow: true}, DB: db, }) if err != nil { t.Fatalf("new: %v", err) } active := &mockActive{n: 2} im := &mockIMAdapter{} bt.SetActiveSink(active) bt.SetIMSink(im) res := bt.pusher.Push(context.Background(), PushRequest{ Message: adapter.UnifiedMessage{Content: "hi"}, ChannelHint: "web", }) if !res.Pushed || res.Route != RouteActiveClient { t.Fatalf("push = %+v, want active_client pushed", res) } if active.calls != 1 { t.Fatalf("active calls = %d, want 1", active.calls) } if im.count() != 0 { t.Fatalf("im calls = %d, want 0 (active preferred)", im.count()) } } // TestBackthink_SetIMSink_Fallback 验证没有活跃客户端在线时, // 已接线的 IM 槽会收到推送(主人因此仍被触达)。 func TestBackthink_SetIMSink_Fallback(t *testing.T) { db := newTestDB(t) bt, err := New(Config{}, Deps{ Thinker: &mockThinker{text: "[我决定:push]\nhi"}, State: &mockState{}, Gate: &mockGate{allow: true}, DB: db, }) if err != nil { t.Fatalf("new: %v", err) } active := &mockActive{n: 0} // 没有活跃客户端在线 im := &mockIMAdapter{} bt.SetActiveSink(active) bt.SetIMSink(im) res := bt.pusher.Push(context.Background(), PushRequest{ Message: adapter.UnifiedMessage{Content: "hi"}, }) if !res.Pushed || res.Route != RouteIMBridge { t.Fatalf("push = %+v, want im_bridge pushed", res) } if im.count() != 1 { t.Fatalf("im calls = %d, want 1", im.count()) } } // TestBackthink_SinkSetters_ConcurrentSafe 验证设置器与对 // 当前推送器的读取在生命周期锁下不产生竞态,因此进行中的轮次绝不会 // 碰到半重建的推送器。 func TestBackthink_SinkSetters_ConcurrentSafe(t *testing.T) { db := newTestDB(t) bt, err := New(Config{}, Deps{ Thinker: &mockThinker{}, State: &mockState{}, Gate: &mockGate{allow: true}, DB: db, }) if err != nil { t.Fatalf("new: %v", err) } done := make(chan struct{}) go func() { defer close(done) for i := 0; i < 200; i++ { bt.SetActiveSink(&mockActive{n: 1}) bt.SetIMSink(&mockIMAdapter{}) } }() for i := 0; i < 200; i++ { _ = bt.currentPusher().Push(context.Background(), PushRequest{Message: adapter.UnifiedMessage{Content: "x"}}) } <-done }