owpengram-server/internal/store/memory/album_group_test.go

131 lines
4.7 KiB
Go

package memory
import (
"context"
"crypto/sha256"
"errors"
"sync"
"testing"
"telesrv/internal/domain"
)
func albumIntent(label string) []byte {
sum := sha256.Sum256([]byte(label))
return sum[:]
}
func albumReq(sender int64, peer domain.Peer, groupedID int64, items ...domain.AlbumGroupReservationItem) domain.AlbumGroupReservationRequest {
return domain.AlbumGroupReservationRequest{
SenderUserID: sender,
Peer: peer,
Items: items,
ProposedGroupedID: groupedID,
}
}
func albumItem(randomID int64, label string) domain.AlbumGroupReservationItem {
return domain.AlbumGroupReservationItem{RandomID: randomID, IntentHash: albumIntent(label)}
}
func TestAlbumGroupReservationFullThenSubsetAndIntentConflict(t *testing.T) {
ctx := context.Background()
messages := NewMessageStore()
peer := domain.Peer{Type: domain.PeerTypeUser, ID: 2002}
full := []domain.AlbumGroupReservationItem{albumItem(1, "one"), albumItem(2, "two"), albumItem(3, "three")}
groupedID, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 101, full...))
if err != nil || groupedID != 101 {
t.Fatalf("reserve full = %d err=%v, want 101", groupedID, err)
}
replayed, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 202, full[1:]...))
if err != nil || replayed != groupedID {
t.Fatalf("reserve subset = %d err=%v, want original %d", replayed, err, groupedID)
}
for _, item := range full {
got, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 303, item))
if err != nil || got != groupedID {
t.Fatalf("single random_id %d = %d err=%v, want %d", item.RandomID, got, err, groupedID)
}
}
changed := albumItem(2, "changed payload")
if _, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 404, changed)); !errors.Is(err, domain.ErrMessageRandomIDDuplicate) {
t.Fatalf("changed intent err=%v, want ErrMessageRandomIDDuplicate", err)
}
}
func TestAlbumGroupReservationConcurrentOverlapConverges(t *testing.T) {
ctx := context.Background()
messages := NewMessageStore()
peer := domain.Peer{Type: domain.PeerTypeChannel, ID: 9001}
requests := []domain.AlbumGroupReservationRequest{
albumReq(1001, peer, 111, albumItem(11, "one"), albumItem(12, "shared")),
albumReq(1001, peer, 222, albumItem(12, "shared"), albumItem(13, "three")),
}
results := make([]int64, 2)
errs := make([]error, 2)
start := make(chan struct{})
var wg sync.WaitGroup
for i := range requests {
wg.Add(1)
go func(i int) {
defer wg.Done()
<-start
results[i], errs[i] = messages.ReserveAlbumGroup(ctx, requests[i])
}(i)
}
close(start)
wg.Wait()
if errs[0] != nil || errs[1] != nil || results[0] == 0 || results[0] != results[1] {
t.Fatalf("concurrent results=%v errs=%v, want same non-zero group", results, errs)
}
for _, item := range []domain.AlbumGroupReservationItem{albumItem(11, "one"), albumItem(12, "shared"), albumItem(13, "three")} {
got, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 333, item))
if err != nil || got != results[0] {
t.Fatalf("converged random_id %d = %d err=%v, want %d", item.RandomID, got, err, results[0])
}
}
}
func TestAlbumGroupReservationRejectsMixedOldGroupsAtomically(t *testing.T) {
ctx := context.Background()
messages := NewMessageStore()
peer := domain.Peer{Type: domain.PeerTypeUser, ID: 2002}
one := albumItem(21, "one")
two := albumItem(22, "two")
three := albumItem(23, "three")
if _, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 121, one)); err != nil {
t.Fatal(err)
}
if _, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 122, two)); err != nil {
t.Fatal(err)
}
if _, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 123, one, two, three)); !errors.Is(err, domain.ErrMessageRandomIDDuplicate) {
t.Fatalf("mixed old groups err=%v, want ErrMessageRandomIDDuplicate", err)
}
// 失败批次不能把尚未存在的 random_id 23 偷绑到任一旧组。
got, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, peer, 124, three))
if err != nil || got != 124 {
t.Fatalf("post-conflict unbound item = %d err=%v, want fresh 124", got, err)
}
}
func TestAlbumGroupReservationScopeIncludesPeer(t *testing.T) {
ctx := context.Background()
messages := NewMessageStore()
item := albumItem(31, "same intent")
tests := []struct {
peer domain.Peer
group int64
}{
{peer: domain.Peer{Type: domain.PeerTypeUser, ID: 2002}, group: 131},
{peer: domain.Peer{Type: domain.PeerTypeUser, ID: 2003}, group: 132},
{peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 2002}, group: 133},
}
for _, tc := range tests {
got, err := messages.ReserveAlbumGroup(ctx, albumReq(1001, tc.peer, tc.group, item))
if err != nil || got != tc.group {
t.Fatalf("peer %+v = %d err=%v, want isolated %d", tc.peer, got, err, tc.group)
}
}
}