feat(backthink): 事件驱动主动消息绕过PushGate——IoT等事件该发就发(heartbeat仍受60min节流)
- trigger.go: 新增 TriggerKind(TriggerHeartbeat/TriggerEvent) + Trigger.Kind 标记触发来源 - push.go: PushRequest.TriggerKind 分流——event 跳过 gate.Allow/RecordPush(该发就发),heartbeat 仍受 60min≤1/冷却/分类限频;PushResult.Bypassed 标记 + detail=event_bypass 区分 - backthink.go: think() 传 TriggerKind + 过同事件去重门 - event_dedup.go(新): 同事件(Source+Summary)去重防刷,无 60min 节流;不同事件/心跳后重置链 - 测试: event 连续 3 次全推 bypass / heartbeat 第2次 rate_limited / event 不消耗心跳限 / 同事件去重 - build/vet/test 全绿 + windows/darwin 交叉编译通过
This commit is contained in:
1 parent
3e4efa0e41
commit
d09b25071d
6 files changed
+266
-22
No files matched your search
@@ -3,6 +3,7 @@ package backthink
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -308,3 +309,66 @@ func TestBackthink_Stop_UnsubscribesBus(t *testing.T) {
|
||||
t.Fatalf("thinker calls grew after stop (bus not unsubscribed): %d -> %d", before, got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackthink_EventPush_DedupSameEvent verifies the same-event dedup guard: a
|
||||
// repeating identical event (same source + summary) is suppressed after the first
|
||||
// push (防刷), while a genuinely different event still goes out. The duplicate is
|
||||
// recorded as suppressed (route none), not delivered.
|
||||
func TestBackthink_EventPush_DedupSameEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
thinker := &mockThinker{text: "washing finished, clothes ready"}
|
||||
pusher := &mockPusher{result: PushResult{Pushed: true, Route: RouteActiveClient}}
|
||||
b, _ := newBackthink(t, Config{}, Deps{
|
||||
Thinker: thinker,
|
||||
State: &mockState{state: UserState{}},
|
||||
Pusher: pusher,
|
||||
Now: func() time.Time { return dayAt() },
|
||||
})
|
||||
|
||||
ev := triggerForEvent(bus.Event{Subject: iot.DeviceUpdateSubject, Payload: iot.DeviceUpdate{
|
||||
Device: iot.Device{ID: "washer", Type: "sensor"}, Action: "finished",
|
||||
}})
|
||||
|
||||
b.think(context.Background(), ev) // first: pushes
|
||||
b.think(context.Background(), ev) // second: same event -> suppressed
|
||||
b.think(context.Background(), ev) // third: same event -> suppressed
|
||||
|
||||
if got := pusher.count(); got != 1 {
|
||||
t.Fatalf("pusher calls = %d, want 1 (repeating same event deduped)", got)
|
||||
}
|
||||
recs, err := b.List(context.Background(), 10)
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(recs) < 3 {
|
||||
t.Fatalf("len(recs) = %d, want at least 3 (one per think)", len(recs))
|
||||
}
|
||||
// Newest first: the two most recent records are the suppressed duplicates.
|
||||
for i := 0; i < 2; i++ {
|
||||
if recs[i].PushResult.Pushed || recs[i].PushResult.Route != RouteNone {
|
||||
t.Fatalf("recs[%d] = %+v, want suppressed duplicate", i, recs[i].PushResult)
|
||||
}
|
||||
if !strings.Contains(recs[i].PushResult.Detail, "duplicate") {
|
||||
t.Fatalf("recs[%d].Detail = %q, want it to mention duplicate", i, recs[i].PushResult.Detail)
|
||||
}
|
||||
}
|
||||
// The third-most-recent record is the first (delivered) push.
|
||||
if !recs[2].PushResult.Pushed || recs[2].PushResult.Route != RouteActiveClient {
|
||||
t.Fatalf("recs[2] = %+v, want the first delivered push", recs[2].PushResult)
|
||||
}
|
||||
|
||||
// A genuinely different event resets the chain and goes out.
|
||||
ev2 := triggerForEvent(bus.Event{Subject: iot.DeviceUpdateSubject, Payload: iot.DeviceUpdate{
|
||||
Device: iot.Device{ID: "dryer", Type: "sensor"}, Action: "finished",
|
||||
}})
|
||||
b.think(context.Background(), ev2)
|
||||
if got := pusher.count(); got != 2 {
|
||||
t.Fatalf("pusher calls after a different event = %d, want 2", got)
|
||||
}
|
||||
// After the different event, the original identical event is no longer a
|
||||
// consecutive duplicate, so it pushes again — the guard only dedups consecutively.
|
||||
b.think(context.Background(), ev)
|
||||
if got := pusher.count(); got != 3 {
|
||||
t.Fatalf("pusher calls after an intervening different event = %d, want 3", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user