owpengram-server/internal/store/redisstore/ephemeral_integration_test.go
A f49c817def feat: sync ephemeral transient messages
Sync telesrv 570ccf8 (feat(ephemeral): implement Layer 228 transient messages).

Skipped telesrv docs changes per public sync rules; normalized the public appearance seed label.
2026-07-20 16:43:27 +08:00

99 lines
3.7 KiB
Go

package redisstore
import (
"context"
"crypto/sha256"
"os"
"testing"
"time"
"github.com/redis/go-redis/v9"
"telesrv/internal/domain"
"telesrv/internal/store"
)
func TestRedisEphemeralAtomicLifecycleCallbackAndBroker(t *testing.T) {
addr := os.Getenv("TELESRV_TEST_REDIS_ADDR")
if addr == "" {
t.Skip("set TELESRV_TEST_REDIS_ADDR to run redis integration test")
}
client := redis.NewClient(&redis.Options{Addr: addr})
defer client.Close()
ctx := context.Background()
storeImpl := NewEphemeralMessageStore(client)
now := time.Now()
seed := now.UnixNano() & 0x3fffffff
message := domain.EphemeralMessage{
ID: int(seed) + 1, Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: seed + 2},
SenderUserID: seed + 3, ReceiverUserID: seed + 4, Date: int(now.Unix()), RandomID: seed + 5,
Content: domain.EphemeralContent{Message: "/private"}, PayloadHash: sha256.Sum256([]byte("payload")),
Version: 1, CreatedAt: now, ExpiresAt: now.Add(domain.EphemeralMessageRetention),
}
t.Cleanup(func() {
_ = client.Del(context.Background(), ephemeralMessageKey(message.Peer, message.ID), ephemeralRandomKey(message), ephemeralCallbackActionKey(seed+6)).Err()
})
created, fresh, err := storeImpl.CreateEphemeralMessage(ctx, message)
if err != nil || !fresh || created.ID != message.ID {
t.Fatalf("create=%+v fresh=%v err=%v", created, fresh, err)
}
replay, fresh, err := storeImpl.CreateEphemeralMessage(ctx, message)
if err != nil || fresh || replay.ID != message.ID {
t.Fatalf("replay=%+v fresh=%v err=%v", replay, fresh, err)
}
edited, err := storeImpl.EditEphemeralMessage(ctx, message.Peer, message.ID, 1, domain.EphemeralContent{Message: "edited"}, message.Date+1, now)
if err != nil || edited.Version != 2 || edited.Content.Message != "edited" {
t.Fatalf("edit=%+v err=%v", edited, err)
}
deleted, changed, err := storeImpl.DeleteEphemeralMessage(ctx, message.Peer, message.ID, 2, now)
if err != nil || !changed || !deleted.Deleted || deleted.Version != 3 {
t.Fatalf("delete=%+v changed=%v err=%v", deleted, changed, err)
}
action := domain.EphemeralCallbackAction{
QueryID: seed + 6, BotUserID: seed + 3, UserID: seed + 4, Peer: message.Peer,
MessageID: message.ID, TopMessageID: 42,
Device: domain.EphemeralDevice{UserID: seed + 4, BusinessAuthKeyID: [8]byte{7}, SessionID: 8},
CreatedAt: now, ExpiresAt: now.Add(domain.EphemeralReplyWindow),
}
if created, err := storeImpl.PutEphemeralCallbackAction(ctx, action); err != nil || !created {
t.Fatalf("put callback created=%v err=%v", created, err)
}
got, found, err := storeImpl.GetEphemeralCallbackAction(ctx, action.BotUserID, action.QueryID, now)
if err != nil || !found || got.TopMessageID != 42 || got.Device.BusinessAuthKeyID != action.Device.BusinessAuthKeyID {
t.Fatalf("callback=%+v found=%v err=%v", got, found, err)
}
brokerCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
received := make(chan store.EphemeralPush, 1)
go func() {
_ = storeImpl.SubscribeEphemeralPushes(brokerCtx, func(_ context.Context, event store.EphemeralPush) {
select {
case received <- event:
default:
}
})
}()
event := store.EphemeralPush{
SourceID: "redis-test", Kind: store.EphemeralPushDelete,
TargetUserID: message.ReceiverUserID, Message: deleted, Date: int(now.Unix()),
}
ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
for {
if err := storeImpl.PublishEphemeralPush(ctx, event); err != nil {
t.Fatal(err)
}
select {
case got := <-received:
if got.SourceID != event.SourceID || got.Message.ID != event.Message.ID || got.Kind != event.Kind {
t.Fatalf("broker event=%+v", got)
}
return
case <-brokerCtx.Done():
t.Fatal("redis ephemeral broker did not deliver")
case <-ticker.C:
}
}
}