feat: sync Bot API gateway support
This commit is contained in:
parent
9a501f900a
commit
4c0cc2b7a7
44 changed files with 4609 additions and 49 deletions
122
internal/store/postgres/botapi_update_integration_test.go
Normal file
122
internal/store/postgres/botapi_update_integration_test.go
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// TestBotAPIUpdateRetention 锁定 H1 场景矩阵:
|
||||
// - 已确认 + 超宽限 → 删;已确认 + 宽限内 → 留;
|
||||
// - 未确认 + date 超保留期 → 删(含无 state 行的 MTProto-only bot);
|
||||
// - 未确认 + date 在保留期内 → 留;
|
||||
// - 删除后 getUpdates 读路径(fromID > confirmed)不受影响。
|
||||
func TestBotAPIUpdateRetention(t *testing.T) {
|
||||
pool := testPool(t)
|
||||
ctx := context.Background()
|
||||
|
||||
suffix := randomSuffix(t)
|
||||
users := NewUserStore(pool)
|
||||
newBot := func(phoneTail, name string) int64 {
|
||||
t.Helper()
|
||||
u, err := users.Create(ctx, domain.User{
|
||||
AccessHash: 920,
|
||||
Phone: "+1920" + suffix + phoneTail,
|
||||
FirstName: name,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create bot user %s: %v", name, err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO bots (bot_user_id, owner_user_id, token_secret)
|
||||
VALUES ($1, $1, 'retention-test-secret')
|
||||
ON CONFLICT (bot_user_id) DO NOTHING`, u.ID); err != nil {
|
||||
t.Fatalf("seed bot %s: %v", name, err)
|
||||
}
|
||||
return u.ID
|
||||
}
|
||||
confirmedBot := newBot("01", "RetentionConfirmedBot")
|
||||
mtprotoOnlyBot := newBot("02", "RetentionMTOnlyBot")
|
||||
t.Cleanup(func() {
|
||||
_, _ = pool.Exec(ctx, "DELETE FROM bot_api_updates WHERE bot_user_id IN ($1, $2)", confirmedBot, mtprotoOnlyBot)
|
||||
_, _ = pool.Exec(ctx, "DELETE FROM bot_api_update_states WHERE bot_user_id IN ($1, $2)", confirmedBot, mtprotoOnlyBot)
|
||||
_, _ = pool.Exec(ctx, "DELETE FROM bots WHERE bot_user_id IN ($1, $2)", confirmedBot, mtprotoOnlyBot)
|
||||
})
|
||||
|
||||
s := NewBotAPIUpdateStore(pool)
|
||||
now := time.Now().Unix()
|
||||
stale := now - int64((48 * time.Hour).Seconds())
|
||||
enqueue := func(botID int64, messageID int, date int64) domain.BotAPIUpdate {
|
||||
t.Helper()
|
||||
row, created, err := s.EnqueueBotAPIUpdate(ctx, domain.EnqueueBotAPIUpdateRequest{
|
||||
BotUserID: botID,
|
||||
Kind: domain.BotAPIUpdateMessage,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 1},
|
||||
MessageID: messageID,
|
||||
SourcePts: messageID,
|
||||
Date: int(date),
|
||||
})
|
||||
if err != nil || !created {
|
||||
t.Fatalf("enqueue bot=%d msg=%d: created=%v err=%v", botID, messageID, created, err)
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
confirmedOld := enqueue(confirmedBot, 1, now) // 已确认 + created_at 回拨超宽限 → 删
|
||||
confirmedFresh := enqueue(confirmedBot, 2, now) // 已确认 + 宽限内 → 留
|
||||
unconfirmedFresh := enqueue(confirmedBot, 3, now)
|
||||
expiredNoState := enqueue(mtprotoOnlyBot, 4, stale) // 无 state 行 + date 超保留期 → 删
|
||||
freshNoState := enqueue(mtprotoOnlyBot, 5, now)
|
||||
|
||||
if err := s.ConfirmBotAPIUpdates(ctx, confirmedBot, confirmedFresh.ID); err != nil {
|
||||
t.Fatalf("confirm: %v", err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx,
|
||||
"UPDATE bot_api_updates SET created_at = now() - interval '1 hour' WHERE id = $1", confirmedOld.ID); err != nil {
|
||||
t.Fatalf("backdate confirmed row: %v", err)
|
||||
}
|
||||
|
||||
deleted, err := s.DeleteDeliveredOrExpired(ctx, 15*time.Minute, 24*time.Hour, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteDeliveredOrExpired: %v", err)
|
||||
}
|
||||
// 共享测试库可能有其它历史行同被回收,只要求至少删掉本测试的 2 行;
|
||||
// 精确归属由下方 remaining 断言保证。
|
||||
if deleted < 2 {
|
||||
t.Fatalf("deleted = %d, want >= 2 (confirmed+grace expired, date expired)", deleted)
|
||||
}
|
||||
|
||||
remaining := map[int64]bool{}
|
||||
rows, err := pool.Query(ctx, "SELECT id FROM bot_api_updates WHERE bot_user_id IN ($1, $2)", confirmedBot, mtprotoOnlyBot)
|
||||
if err != nil {
|
||||
t.Fatalf("list remaining: %v", err)
|
||||
}
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
t.Fatalf("scan remaining: %v", err)
|
||||
}
|
||||
remaining[id] = true
|
||||
}
|
||||
rows.Close()
|
||||
if remaining[confirmedOld.ID] {
|
||||
t.Fatal("confirmed row past grace was not deleted")
|
||||
}
|
||||
if remaining[expiredNoState.ID] {
|
||||
t.Fatal("expired row of state-less bot was not deleted")
|
||||
}
|
||||
if !remaining[confirmedFresh.ID] || !remaining[unconfirmedFresh.ID] || !remaining[freshNoState.ID] {
|
||||
t.Fatalf("fresh rows were deleted, remaining=%v", remaining)
|
||||
}
|
||||
|
||||
// 读路径回归:确认水位之后的未确认行仍可被 getUpdates 读到。
|
||||
items, err := s.ListBotAPIUpdates(ctx, confirmedBot, confirmedFresh.ID+1, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("list after retention: %v", err)
|
||||
}
|
||||
if len(items) != 1 || items[0].ID != unconfirmedFresh.ID {
|
||||
t.Fatalf("post-retention list = %+v, want only unconfirmed fresh row %d", items, unconfirmedFresh.ID)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue