owpengram-server/internal/store/memory/botapi_update_test.go
A bf965f610c feat: sync bot keyboards and callbacks
Sync telesrv b96f2dd (feat(bot): complete keyboards callbacks and durable delivery).

Skipped private docs and preserved public README files per sync rules; normalized the appearance seed log label for public naming.
2026-07-19 20:38:48 +08:00

191 lines
8.5 KiB
Go

package memory
import (
"context"
"testing"
"time"
"telesrv/internal/domain"
)
func botAPIMessageRequest(botID int64, kind domain.BotAPIUpdateKind, messageID int) domain.EnqueueBotAPIUpdateRequest {
return domain.EnqueueBotAPIUpdateRequest{
BotUserID: botID,
Kind: kind,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 2001},
MessageID: messageID,
SourcePts: messageID,
Date: 1700000000 + messageID,
}
}
func TestBotAPIPollLeaseCompareOwnerAndExpiry(t *testing.T) {
ctx := context.Background()
store := NewBotAPIUpdateStore()
if acquired, err := store.AcquireBotAPIPollLease(ctx, 1001, "one", 20*time.Millisecond); err != nil || !acquired {
t.Fatalf("first acquire=%v err=%v", acquired, err)
}
if acquired, err := store.AcquireBotAPIPollLease(ctx, 1001, "two", time.Second); err != nil || acquired {
t.Fatalf("competing acquire=%v err=%v", acquired, err)
}
if err := store.ReleaseBotAPIPollLease(ctx, 1001, "stale"); err != nil {
t.Fatal(err)
}
if acquired, _ := store.AcquireBotAPIPollLease(ctx, 1001, "two", time.Second); acquired {
t.Fatal("stale release removed active owner")
}
time.Sleep(25 * time.Millisecond)
if acquired, err := store.AcquireBotAPIPollLease(ctx, 1001, "two", time.Second); err != nil || !acquired {
t.Fatalf("expired acquire=%v err=%v", acquired, err)
}
}
func TestBotAPIWebhookLeaseWakeAndAtomicDrop(t *testing.T) {
ctx := context.Background()
store := NewBotAPIUpdateStore()
if _, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, 1)); err != nil || !created {
t.Fatalf("enqueue initial created=%v err=%v", created, err)
}
config := domain.BotAPIWebhook{BotUserID: 1001, URL: "https://example.test/hook", MaxConnections: 8}
if err := store.SetBotAPIWebhook(ctx, config, true); err != nil {
t.Fatal(err)
}
if count, _ := store.PendingBotAPIUpdateCount(ctx, 1001); count != 0 {
t.Fatalf("pending after atomic drop=%d", count)
}
if acquired, err := store.AcquireBotAPIWebhookLease(ctx, 1001, "worker-1", time.Second); err != nil || !acquired {
t.Fatalf("lease acquire=%v err=%v", acquired, err)
}
if acquired, _ := store.AcquireBotAPIWebhookLease(ctx, 1001, "worker-2", time.Second); acquired {
t.Fatal("second webhook worker acquired active lease")
}
if err := store.RecordBotAPIWebhookSuccess(ctx, 1001, "worker-1", time.Now().Add(time.Hour)); err != nil {
t.Fatal(err)
}
if due, err := store.ListDueBotAPIWebhooks(ctx, 10); err != nil || len(due) != 0 {
t.Fatalf("idle due=%#v err=%v", due, err)
}
if _, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, 2)); err != nil || !created {
t.Fatalf("enqueue wake created=%v err=%v", created, err)
}
if due, err := store.ListDueBotAPIWebhooks(ctx, 10); err != nil || len(due) != 1 || due[0].BotUserID != 1001 {
t.Fatalf("woken due=%#v err=%v", due, err)
}
}
func TestBotAPIWebhookAllowedUpdatesOmissionPreservesPolicy(t *testing.T) {
ctx := context.Background()
store := NewBotAPIUpdateStore()
if err := store.SetBotAPIAllowedUpdates(ctx, 1001, []domain.BotAPIUpdateKind{domain.BotAPIUpdateCallbackQuery}); err != nil {
t.Fatal(err)
}
config := domain.BotAPIWebhook{BotUserID: 1001, URL: "https://example.test/one", MaxConnections: 8}
if err := store.SetBotAPIWebhook(ctx, config, false); err != nil {
t.Fatal(err)
}
stored, found, err := store.BotAPIWebhook(ctx, 1001)
if err != nil || !found || len(stored.AllowedUpdates) != 1 || stored.AllowedUpdates[0] != domain.BotAPIUpdateCallbackQuery {
t.Fatalf("preserved webhook=%#v found=%v err=%v", stored, found, err)
}
if row, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, 1)); err != nil || created || row.ID != 0 {
t.Fatalf("message bypassed preserved policy: row=%#v created=%v err=%v", row, created, err)
}
config.URL = "https://example.test/two"
config.AllowedUpdatesSet = true // Explicit empty resets to the default/all policy.
if err := store.SetBotAPIWebhook(ctx, config, false); err != nil {
t.Fatal(err)
}
stored, found, err = store.BotAPIWebhook(ctx, 1001)
if err != nil || !found || stored.AllowedUpdates != nil {
t.Fatalf("explicit empty webhook=%#v found=%v err=%v", stored, found, err)
}
if _, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, 2)); err != nil || !created {
t.Fatalf("message after explicit reset created=%v err=%v", created, err)
}
}
func TestBotAPIUpdateCursorClampDropAndTail(t *testing.T) {
ctx := context.Background()
store := NewBotAPIUpdateStore()
for id := 1; id <= 5; id++ {
if _, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, id)); err != nil || !created {
t.Fatalf("enqueue %d: created=%v err=%v", id, created, err)
}
}
tail, err := store.ListTailBotAPIUpdates(ctx, 1001, 2, 100)
if err != nil || len(tail) != 2 || tail[0].MessageID != 4 || tail[1].MessageID != 5 {
t.Fatalf("tail = %#v err=%v", tail, err)
}
if err := store.ConfirmBotAPIUpdates(ctx, 1001, 1<<60); err != nil {
t.Fatalf("confirm huge offset: %v", err)
}
confirmed, found, err := store.ConfirmedBotAPIUpdateID(ctx, 1001)
if err != nil || !found || confirmed != 5 {
t.Fatalf("confirmed = %d found=%v err=%v, want 5", confirmed, found, err)
}
row, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, 6))
if err != nil || !created {
t.Fatalf("enqueue after huge offset: row=%#v created=%v err=%v", row, created, err)
}
if err := store.ConfirmBotAPIUpdates(ctx, 1001, 1<<60); err != nil {
t.Fatalf("repeat foreign offset: %v", err)
}
if confirmed, _, _ := store.ConfirmedBotAPIUpdateID(ctx, 1001); confirmed != 5 {
t.Fatalf("repeat foreign offset advanced cursor to %d, want 5", confirmed)
}
pending, err := store.ListBotAPIUpdates(ctx, 1001, confirmed+1, 100)
if err != nil || len(pending) != 1 || pending[0].MessageID != 6 {
t.Fatalf("pending after huge offset = %#v err=%v", pending, err)
}
if err := store.DropPendingBotAPIUpdates(ctx, 1001); err != nil {
t.Fatalf("drop pending: %v", err)
}
count, err := store.PendingBotAPIUpdateCount(ctx, 1001)
if err != nil || count != 0 {
t.Fatalf("pending count = %d err=%v", count, err)
}
}
func TestBotAPIAllowedUpdatesOnlyAffectsFutureEnqueue(t *testing.T) {
ctx := context.Background()
store := NewBotAPIUpdateStore()
first, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, 1))
if err != nil || !created {
t.Fatalf("enqueue pre-policy: %#v created=%v err=%v", first, created, err)
}
if err := store.SetBotAPIAllowedUpdates(ctx, 1001, []domain.BotAPIUpdateKind{domain.BotAPIUpdateEditedMessage}); err != nil {
t.Fatalf("set policy: %v", err)
}
if row, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateMessage, 2)); err != nil || created || row.ID != 0 {
t.Fatalf("filtered message = %#v created=%v err=%v", row, created, err)
}
if _, created, err := store.EnqueueBotAPIUpdate(ctx, botAPIMessageRequest(1001, domain.BotAPIUpdateEditedMessage, 3)); err != nil || !created {
t.Fatalf("allowed edit created=%v err=%v", created, err)
}
rows, err := store.ListBotAPIUpdates(ctx, 1001, 1, 100)
if err != nil || len(rows) != 2 || rows[0].ID != first.ID || rows[1].Kind != domain.BotAPIUpdateEditedMessage {
t.Fatalf("rows = %#v err=%v", rows, err)
}
}
func TestBotAPIInlineCallbackRoundTrip(t *testing.T) {
ctx := context.Background()
store := NewBotAPIUpdateStore()
callback := &domain.BotCallbackQuery{
ID: 77, BotUserID: 1001, UserID: 2001, ChatInstance: 99, Data: []byte("tap"),
InlineMessage: &domain.BotInlineMessageID{DCID: 2, OwnerID: 2001, ID: 15, AccessHash: 1234},
}
row, created, err := store.EnqueueBotAPIUpdate(ctx, domain.EnqueueBotAPIUpdateRequest{
BotUserID: 1001, Kind: domain.BotAPIUpdateCallbackQuery, Date: int(time.Now().Unix()), Callback: callback,
})
if err != nil || !created || row.MessageID != 0 || row.Peer != (domain.Peer{}) || row.Callback == nil ||
row.Callback.InlineMessage == nil || *row.Callback.InlineMessage != *callback.InlineMessage {
t.Fatalf("inline callback row=%#v created=%v err=%v", row, created, err)
}
callback.Data[0] = 'X'
callback.InlineMessage.ID = 99
rows, err := store.ListBotAPIUpdates(ctx, 1001, 1, 100)
if err != nil || len(rows) != 1 || string(rows[0].Callback.Data) != "tap" || rows[0].Callback.InlineMessage.ID != 15 {
t.Fatalf("inline callback rows=%#v err=%v", rows, err)
}
}