owpengram-server/internal/store/memory/ephemeral_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

184 lines
7.2 KiB
Go

package memory
import (
"context"
"crypto/sha256"
"errors"
"sync/atomic"
"testing"
"time"
"telesrv/internal/domain"
)
func TestEphemeralMessageStoreCreateReplayEditDeleteAndExpiry(t *testing.T) {
ctx := context.Background()
store := NewEphemeralMessageStore()
now := time.Unix(1_800_000_000, 0)
message := testEphemeralMessage(now)
created, fresh, err := store.CreateEphemeralMessage(ctx, message)
if err != nil || !fresh || created.ID != message.ID {
t.Fatalf("create = %+v fresh=%v err=%v", created, fresh, err)
}
replayed, fresh, err := store.CreateEphemeralMessage(ctx, message)
if err != nil || fresh || replayed.Version != 1 {
t.Fatalf("replay = %+v fresh=%v err=%v", replayed, fresh, err)
}
conflict := message
conflict.ID++
conflict.PayloadHash = sha256.Sum256([]byte("different"))
if _, _, err := store.CreateEphemeralMessage(ctx, conflict); !errors.Is(err, domain.ErrEphemeralRandomIDConflict) {
t.Fatalf("random-id conflict err=%v", err)
}
edited, err := store.EditEphemeralMessage(ctx, message.Peer, message.ID, 1, domain.EphemeralContent{Message: "edited"}, int(now.Unix())+1, now)
if err != nil || edited.Version != 2 || edited.Content.Message != "edited" {
t.Fatalf("edit = %+v err=%v", edited, err)
}
if _, err := store.EditEphemeralMessage(ctx, message.Peer, message.ID, 1, domain.EphemeralContent{Message: "stale"}, int(now.Unix())+2, now); !errors.Is(err, domain.ErrEphemeralVersionConflict) {
t.Fatalf("stale edit err=%v", err)
}
deleted, changed, err := store.DeleteEphemeralMessage(ctx, message.Peer, message.ID, 2, now)
if err != nil || !changed || !deleted.Deleted || deleted.Version != 3 || deleted.Content.Message != "" {
t.Fatalf("delete = %+v changed=%v err=%v", deleted, changed, err)
}
deleted, changed, err = store.DeleteEphemeralMessage(ctx, message.Peer, message.ID, 3, now)
if err != nil || changed || !deleted.Deleted {
t.Fatalf("repeat delete = %+v changed=%v err=%v", deleted, changed, err)
}
if _, err := store.EditEphemeralMessage(ctx, message.Peer, message.ID, 3, domain.EphemeralContent{Message: "resurrect"}, int(now.Unix())+3, now); !errors.Is(err, domain.ErrEphemeralDeleted) {
t.Fatalf("edit deleted err=%v", err)
}
if _, found, err := store.GetEphemeralMessage(ctx, message.Peer, message.ID, message.ExpiresAt); err != nil || found {
t.Fatalf("expired found=%v err=%v", found, err)
}
}
func TestEphemeralMessageStoreIDCollisionAndBoundedPrune(t *testing.T) {
ctx := context.Background()
store := NewEphemeralMessageStore()
now := time.Unix(1_800_000_100, 0)
first := testEphemeralMessage(now)
if _, _, err := store.CreateEphemeralMessage(ctx, first); err != nil {
t.Fatal(err)
}
second := first
second.RandomID++
second.PayloadHash = sha256.Sum256([]byte("second"))
if _, _, err := store.CreateEphemeralMessage(ctx, second); !errors.Is(err, domain.ErrEphemeralIDCollision) {
t.Fatalf("id collision err=%v", err)
}
if got, err := store.PruneExpiredEphemeralMessages(ctx, first.ExpiresAt, 1); err != nil || got != 1 {
t.Fatalf("prune=%d err=%v", got, err)
}
}
func TestEphemeralCallbackActionExactBotAndExpiry(t *testing.T) {
ctx := context.Background()
store := NewEphemeralMessageStore()
now := time.Unix(1_800_000_000, 0)
action := domain.EphemeralCallbackAction{
QueryID: 81, BotUserID: 2001, UserID: 3001,
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 1001}, MessageID: 17, TopMessageID: 42,
Device: domain.EphemeralDevice{UserID: 3001, BusinessAuthKeyID: [8]byte{1}, SessionID: 9},
CreatedAt: now, ExpiresAt: now.Add(domain.EphemeralReplyWindow),
}
if created, err := store.PutEphemeralCallbackAction(ctx, action); err != nil || !created {
t.Fatalf("put created=%v err=%v", created, err)
}
if created, err := store.PutEphemeralCallbackAction(ctx, action); err != nil || created {
t.Fatalf("duplicate created=%v err=%v", created, err)
}
if _, found, err := store.GetEphemeralCallbackAction(ctx, action.BotUserID+1, action.QueryID, now); err != nil || found {
t.Fatalf("wrong bot found=%v err=%v", found, err)
}
got, found, err := store.GetEphemeralCallbackAction(ctx, action.BotUserID, action.QueryID, now)
if err != nil || !found || got.TopMessageID != 42 {
t.Fatalf("get=%+v found=%v err=%v", got, found, err)
}
if _, found, err := store.GetEphemeralCallbackAction(ctx, action.BotUserID, action.QueryID, action.ExpiresAt); err != nil || found {
t.Fatalf("expired found=%v err=%v", found, err)
}
}
func TestEphemeralCallbackActionBoundedHeapPrune(t *testing.T) {
ctx := context.Background()
store := NewEphemeralMessageStore()
now := time.Unix(1_800_000_000, 0)
action := domain.EphemeralCallbackAction{
QueryID: 82, BotUserID: 2001, UserID: 3001,
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 1001}, MessageID: 17,
Device: domain.EphemeralDevice{UserID: 3001, BusinessAuthKeyID: [8]byte{1}, SessionID: 9},
CreatedAt: now, ExpiresAt: now.Add(domain.EphemeralReplyWindow),
}
if created, err := store.PutEphemeralCallbackAction(ctx, action); err != nil || !created {
t.Fatalf("put created=%v err=%v", created, err)
}
if _, err := store.PruneExpiredEphemeralMessages(ctx, action.ExpiresAt, 1); err != nil {
t.Fatalf("prune err=%v", err)
}
shard := &store.callbackActions[uint64(action.QueryID)&(ephemeralShardCount-1)]
shard.mu.RLock()
_, found := shard.actions[action.QueryID]
shard.mu.RUnlock()
if found {
t.Fatal("expired callback action survived bounded heap prune")
}
}
func TestEphemeralReportStoreIdempotency(t *testing.T) {
store := NewEphemeralReportStore()
now := time.Unix(1_800_000_000, 0)
message := testEphemeralMessage(now)
message.ReceiverUserID = 3001
report := domain.NewEphemeralAbuseReport(message.ReceiverUserID, "spam", "evidence", message, now)
if created, err := store.CreateEphemeralReport(context.Background(), report); err != nil || !created {
t.Fatalf("create=%v err=%v", created, err)
}
if created, err := store.CreateEphemeralReport(context.Background(), report); err != nil || created {
t.Fatalf("retry create=%v err=%v", created, err)
}
reports := store.Reports()
if len(reports) != 1 || reports[0].Evidence.Content.Message != message.Content.Message {
t.Fatalf("reports=%+v", reports)
}
}
func testEphemeralMessage(now time.Time) domain.EphemeralMessage {
return domain.EphemeralMessage{
ID: 17,
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 1001},
SenderUserID: 2001,
ReceiverUserID: 3001,
Date: int(now.Unix()),
RandomID: 99,
Content: domain.EphemeralContent{Message: "/private"},
PayloadHash: sha256.Sum256([]byte("payload")),
Version: 1,
CreatedAt: now,
ExpiresAt: now.Add(domain.EphemeralMessageRetention),
}
}
func BenchmarkEphemeralMessageStoreParallelCreate(b *testing.B) {
store := NewEphemeralMessageStore()
base := time.Unix(1_800_000_000, 0)
ctx := context.Background()
var sequence atomic.Int64
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
n := sequence.Add(1)
message := testEphemeralMessage(base)
message.ID = int(n%1_000_000) + 1
message.Peer.ID += n
message.RandomID += n
message.PayloadHash = sha256.Sum256([]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)})
if _, _, err := store.CreateEphemeralMessage(ctx, message); err != nil {
b.Errorf("create: %v", err)
}
}
})
}