Sync telesrv 570ccf8 (feat(ephemeral): implement Layer 228 transient messages). Skipped telesrv docs changes per public sync rules; normalized the public appearance seed label.
184 lines
7.2 KiB
Go
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)
|
|
}
|
|
}
|
|
})
|
|
}
|