165 lines
6 KiB
Go
165 lines
6 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
func TestLoginCodeDeliveryStoreCommitsMessageEventDialogAndReplay(t *testing.T) {
|
|
ctx := context.Background()
|
|
const userID int64 = 1000000001
|
|
dialogs := NewDialogStore()
|
|
messages := NewMessageStore(dialogs)
|
|
events := NewUpdateEventStore()
|
|
deliveries := NewLoginCodeDeliveryStore(messages, events)
|
|
req := domain.LoginCodeDeliveryRequest{
|
|
UserID: userID,
|
|
PhoneCodeHash: "phone-code-hash-one",
|
|
Code: "12345",
|
|
Date: 1700000000,
|
|
ExpiresAt: 1700000300,
|
|
}
|
|
|
|
first, err := deliveries.DeliverLoginCodeMessage(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("DeliverLoginCodeMessage: %v", err)
|
|
}
|
|
if !first.Created || first.Message.ID != 1 || first.Message.UID != 1 || first.Message.Pts != 1 || first.Message.Out ||
|
|
first.Message.OwnerUserID != userID || first.Message.Peer.ID != domain.OfficialSystemUserID || first.Message.From.ID != domain.OfficialSystemUserID {
|
|
t.Fatalf("first delivery = %+v, want first incoming 777000 message", first)
|
|
}
|
|
if len(messages.m[userID]) != 1 || !reflect.DeepEqual(messages.m[userID][0], first.Message) {
|
|
t.Fatalf("message projection = %+v, want committed message", messages.m[userID])
|
|
}
|
|
if len(events.events[userID]) != 1 {
|
|
t.Fatalf("durable events = %+v, want one new_message", events.events[userID])
|
|
}
|
|
event := events.events[userID][0]
|
|
if event.Type != domain.UpdateEventNewMessage || event.Pts != first.Message.Pts || event.PtsCount != 1 || !reflect.DeepEqual(event.Message, first.Message) {
|
|
t.Fatalf("event = %+v, want message-identical new_message", event)
|
|
}
|
|
list := dialogs.m[userID]
|
|
if len(list.Dialogs) != 1 || list.Dialogs[0].Peer.ID != domain.OfficialSystemUserID || list.Dialogs[0].TopMessage != first.Message.ID || list.Dialogs[0].UnreadCount != 1 {
|
|
t.Fatalf("dialog projection = %+v, want unread 777000 dialog", list.Dialogs)
|
|
}
|
|
if len(list.Users) != 1 || list.Users[0].ID != domain.OfficialSystemUserID {
|
|
t.Fatalf("dialog users = %+v, want official system user", list.Users)
|
|
}
|
|
|
|
replayReq := req
|
|
replayReq.Date++
|
|
replay, err := deliveries.DeliverLoginCodeMessage(ctx, replayReq)
|
|
if err != nil {
|
|
t.Fatalf("replay DeliverLoginCodeMessage: %v", err)
|
|
}
|
|
if replay.Created || !reflect.DeepEqual(replay.Message, first.Message) {
|
|
t.Fatalf("replay = %+v, want immutable first result %+v", replay, first)
|
|
}
|
|
if len(messages.m[userID]) != 1 || len(events.events[userID]) != 1 || len(messages.loginCodeDeliveries) != 1 {
|
|
t.Fatalf("replay created facts: messages=%d events=%d receipts=%d", len(messages.m[userID]), len(events.events[userID]), len(messages.loginCodeDeliveries))
|
|
}
|
|
|
|
second, err := deliveries.DeliverLoginCodeMessage(ctx, domain.LoginCodeDeliveryRequest{
|
|
UserID: userID,
|
|
PhoneCodeHash: "phone-code-hash-two",
|
|
Code: "67890",
|
|
Date: 1700000010,
|
|
ExpiresAt: 1700000310,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("second distinct delivery: %v", err)
|
|
}
|
|
if !second.Created || second.Message.ID != 2 || second.Message.UID != 2 || second.Message.Pts != 2 || len(events.events[userID]) != 2 {
|
|
t.Fatalf("second delivery = %+v events=%+v, want contiguous allocations", second, events.events[userID])
|
|
}
|
|
if got := dialogs.m[userID].Dialogs[0].UnreadCount; got != 2 {
|
|
t.Fatalf("dialog unread = %d, want 2", got)
|
|
}
|
|
}
|
|
|
|
func TestLoginCodeDeliveryStoreConcurrentReplayAndConflict(t *testing.T) {
|
|
ctx := context.Background()
|
|
const userID int64 = 1000000002
|
|
messages := NewMessageStore(NewDialogStore())
|
|
events := NewUpdateEventStore()
|
|
deliveries := NewLoginCodeDeliveryStore(messages, events)
|
|
req := domain.LoginCodeDeliveryRequest{
|
|
UserID: userID,
|
|
PhoneCodeHash: "concurrent-phone-code-hash",
|
|
Code: "24680",
|
|
Date: 1700000100,
|
|
ExpiresAt: 1700000400,
|
|
}
|
|
|
|
const workers = 32
|
|
var created atomic.Int32
|
|
results := make(chan domain.LoginCodeDeliveryResult, workers)
|
|
errs := make(chan error, workers)
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < workers; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
got, err := deliveries.DeliverLoginCodeMessage(ctx, req)
|
|
if err != nil {
|
|
errs <- err
|
|
return
|
|
}
|
|
if got.Created {
|
|
created.Add(1)
|
|
}
|
|
results <- got
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
close(errs)
|
|
close(results)
|
|
for err := range errs {
|
|
t.Fatalf("concurrent delivery: %v", err)
|
|
}
|
|
if created.Load() != 1 {
|
|
t.Fatalf("created calls = %d, want exactly 1", created.Load())
|
|
}
|
|
for got := range results {
|
|
if got.Message.ID != 1 || got.Message.UID != 1 || got.Message.Pts != 1 {
|
|
t.Fatalf("concurrent result = %+v, want the same first allocation", got)
|
|
}
|
|
}
|
|
if len(messages.m[userID]) != 1 || len(events.events[userID]) != 1 || len(messages.loginCodeDeliveries) != 1 {
|
|
t.Fatalf("concurrent facts: messages=%d events=%d receipts=%d", len(messages.m[userID]), len(events.events[userID]), len(messages.loginCodeDeliveries))
|
|
}
|
|
|
|
changedCode := req
|
|
changedCode.Code = "13579"
|
|
if _, err := deliveries.DeliverLoginCodeMessage(ctx, changedCode); !errors.Is(err, domain.ErrLoginCodeDeliveryConflict) {
|
|
t.Fatalf("changed-code replay err = %v, want ErrLoginCodeDeliveryConflict", err)
|
|
}
|
|
changedUser := req
|
|
changedUser.UserID++
|
|
if _, err := deliveries.DeliverLoginCodeMessage(ctx, changedUser); !errors.Is(err, domain.ErrLoginCodeDeliveryConflict) {
|
|
t.Fatalf("changed-user replay err = %v, want ErrLoginCodeDeliveryConflict", err)
|
|
}
|
|
if len(messages.m[userID]) != 1 || len(events.events[userID]) != 1 {
|
|
t.Fatal("conflicting replay changed committed facts")
|
|
}
|
|
}
|
|
|
|
func TestLoginCodeDeliveryStoreRequiresSharedEventStore(t *testing.T) {
|
|
messages := NewMessageStore()
|
|
_, err := NewLoginCodeDeliveryStore(messages, nil).DeliverLoginCodeMessage(context.Background(), domain.LoginCodeDeliveryRequest{
|
|
UserID: 1000000003,
|
|
PhoneCodeHash: "missing-event-store",
|
|
Code: "12345",
|
|
Date: 1700000200,
|
|
ExpiresAt: 1700000500,
|
|
})
|
|
if !errors.Is(err, domain.ErrLoginCodeDeliveryInvalid) {
|
|
t.Fatalf("missing event store err = %v, want ErrLoginCodeDeliveryInvalid", err)
|
|
}
|
|
}
|