Ctrl K
ComunidadGuías…

internal/store/postgres/message_no_forwards_integration_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package postgres

import (
	"context"
	"errors"
	"sync"
	"testing"
	"time"

	"telesrv/internal/domain"
)

func TestPostgresPrivateNoForwardsAtomicStateAndDifference(t *testing.T) {
	pool := testPool(t)
	ctx := context.Background()
	suffix := randomSuffix(t)
	users := NewUserStore(pool)
	alice, err := users.Create(ctx, domain.User{AccessHash: 6101, Phone: "+1668" + suffix + "01", FirstName: "Alice"})
	if err != nil {
		t.Fatal(err)
	}
	bob, err := users.Create(ctx, domain.User{AccessHash: 6102, Phone: "+1668" + suffix + "02", FirstName: "Bob"})
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{alice.ID, bob.ID})
	})

	messages := NewMessageStore(pool)
	baseRandom := time.Now().UnixNano()
	enable, err := messages.TogglePrivateNoForwards(ctx, domain.TogglePrivateNoForwardsRequest{
		ActorUserID: alice.ID, PeerUserID: bob.ID, Enabled: true, RandomID: baseRandom, Date: 1700100000,
	})
	if err != nil {
		t.Fatalf("enable: %v", err)
	}
	request, err := messages.TogglePrivateNoForwards(ctx, domain.TogglePrivateNoForwardsRequest{
		ActorUserID: bob.ID, PeerUserID: alice.ID, RandomID: baseRandom + 1, Date: 1700100001,
	})
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	answer, err := messages.TogglePrivateNoForwards(ctx, domain.TogglePrivateNoForwardsRequest{
		ActorUserID: alice.ID, PeerUserID: bob.ID, RequestMsgID: request.Send.RecipientMessage.ID,
		RandomID: baseRandom + 2, Date: 1700100002,
	})
	if err != nil {
		t.Fatalf("answer: %v", err)
	}
	if enable.Send.SenderMessage.Pts != 1 || request.Send.SenderMessage.Pts != 2 ||
		answer.Send.SenderMessage.Pts != 3 || answer.Send.SenderMessage.ReplyTo == nil ||
		answer.Send.SenderMessage.ReplyTo.MessageID != request.Send.RecipientMessage.ID ||
		answer.Send.RecipientMessage.ReplyTo == nil ||
		answer.Send.RecipientMessage.ReplyTo.MessageID != request.Send.SenderMessage.ID {
		t.Fatalf("pts/reply mapping enable=%+v request=%+v answer=%+v", enable.Send, request.Send, answer.Send)
	}
	state, err := messages.GetPrivateNoForwards(ctx, alice.ID, bob.ID)
	if err != nil || state.Enabled() {
		t.Fatalf("final state=%+v err=%v, want disabled", state, err)
	}
	if _, err := messages.TogglePrivateNoForwards(ctx, domain.TogglePrivateNoForwardsRequest{
		ActorUserID: alice.ID, PeerUserID: bob.ID, RequestMsgID: request.Send.RecipientMessage.ID,
		RandomID: baseRandom + 3, Date: 1700100003,
	}); !errors.Is(err, domain.ErrNoForwardsRequestExpired) {
		t.Fatalf("repeat answer err=%v", err)
	}

	for _, userID := range []int64{alice.ID, bob.ID} {
		events, err := NewUpdateEventStore(pool).ListAfter(ctx, userID, 0, 10)
		if err != nil {
			t.Fatalf("events user %d: %v", userID, err)
		}
		if len(events) != 3 || events[0].Pts != 1 || events[1].Pts != 2 || events[2].Pts != 3 {
			t.Fatalf("events user %d = %+v, want continuous 1..3", userID, events)
		}
	}
	var eventCount, outboxCount int
	if err := pool.QueryRow(ctx, `
SELECT
  (SELECT count(*) FROM user_update_events WHERE user_id = ANY($1::bigint[])),
  (SELECT count(*) FROM dispatch_outbox WHERE target_user_id = ANY($1::bigint[]))`,
		[]int64{alice.ID, bob.ID},
	).Scan(&eventCount, &outboxCount); err != nil {
		t.Fatal(err)
	}
	if eventCount != 6 || outboxCount != 6 {
		t.Fatalf("event/outbox count=%d/%d, want 6/6", eventCount, outboxCount)
	}
	var handledAt int
	var logicalExpired bool
	var expiredBoxes int
	if err := pool.QueryRow(ctx, `
SELECT r.handled_at,
       COALESCE((pm.media #>> '{service_action,no_forwards,expired}')::boolean, false),
       (SELECT count(*)
        FROM message_boxes b
        WHERE b.message_sender_id = r.private_message_sender_user_id
          AND b.private_message_id = r.private_message_id
          AND COALESCE((b.media #>> '{service_action,no_forwards,expired}')::boolean, false))
FROM private_no_forwards_requests r
JOIN private_messages pm
  ON pm.sender_user_id = r.private_message_sender_user_id
 AND pm.id = r.private_message_id
WHERE r.private_message_sender_user_id = $1
  AND r.private_message_id = $2`, bob.ID, request.Send.SenderMessage.UID,
	).Scan(&handledAt, &logicalExpired, &expiredBoxes); err != nil {
		t.Fatal(err)
	}
	if handledAt != 1700100002 || !logicalExpired || expiredBoxes != 2 {
		t.Fatalf("handled request handled_at=%d logical_expired=%v boxes=%d", handledAt, logicalExpired, expiredBoxes)
	}
}

func TestPostgresPrivateNoForwardsConcurrentOwnershipAndOneShotAnswer(t *testing.T) {
	pool := testPool(t)
	ctx := context.Background()
	suffix := randomSuffix(t)
	users := NewUserStore(pool)
	alice, err := users.Create(ctx, domain.User{AccessHash: 6201, Phone: "+1669" + suffix + "01", FirstName: "Alice"})
	if err != nil {
		t.Fatal(err)
	}
	bob, err := users.Create(ctx, domain.User{AccessHash: 6202, Phone: "+1669" + suffix + "02", FirstName: "Bob"})
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{alice.ID, bob.ID})
	})

	messages := NewMessageStore(pool)
	baseRandom := time.Now().UnixNano()
	enableResults := make([]domain.TogglePrivateNoForwardsResult, 2)
	enableErrors := make([]error, 2)
	actors := []int64{alice.ID, bob.ID}
	peers := []int64{bob.ID, alice.ID}
	var wg sync.WaitGroup
	for i := range actors {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			enableResults[i], enableErrors[i] = messages.TogglePrivateNoForwards(ctx, domain.TogglePrivateNoForwardsRequest{
				ActorUserID: actors[i],
				PeerUserID:  peers[i],
				Enabled:     true,
				RandomID:    baseRandom + int64(i),
				Date:        1700200000,
			})
		}(i)
	}
	wg.Wait()
	changed := 0
	for i, err := range enableErrors {
		if err != nil {
			t.Fatalf("concurrent enable %d: %v", i, err)
		}
		if enableResults[i].Changed {
			changed++
		}
	}
	if changed != 1 {
		t.Fatalf("concurrent enable changed=%d, want exactly one service message", changed)
	}
	state, err := messages.GetPrivateNoForwards(ctx, alice.ID, bob.ID)
	if err != nil || (state.EnabledByUserID != alice.ID && state.EnabledByUserID != bob.ID) {
		t.Fatalf("concurrent enable state=%+v err=%v", state, err)
	}

	ownerID := state.EnabledByUserID
	requesterID := alice.ID
	if ownerID == alice.ID {
		requesterID = bob.ID
	}
	request, err := messages.TogglePrivateNoForwards(ctx, domain.TogglePrivateNoForwardsRequest{
		ActorUserID: requesterID,
		PeerUserID:  ownerID,
		RandomID:    baseRandom + 10,
		Date:        1700200001,
	})
	if err != nil {
		t.Fatalf("create disable request: %v", err)
	}

	answerResults := make([]domain.TogglePrivateNoForwardsResult, 2)
	answerErrors := make([]error, 2)
	for i := range answerResults {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			answerResults[i], answerErrors[i] = messages.TogglePrivateNoForwards(ctx, domain.TogglePrivateNoForwardsRequest{
				ActorUserID:  ownerID,
				PeerUserID:   requesterID,
				Enabled:      false,
				RequestMsgID: request.Send.RecipientMessage.ID,
				RandomID:     baseRandom + 20 + int64(i),
				Date:         1700200002,
			})
		}(i)
	}
	wg.Wait()
	successes, expired := 0, 0
	for i, err := range answerErrors {
		switch {
		case err == nil && answerResults[i].Changed:
			successes++
		case errors.Is(err, domain.ErrNoForwardsRequestExpired):
			expired++
		default:
			t.Fatalf("concurrent answer %d result=%+v err=%v", i, answerResults[i], err)
		}
	}
	if successes != 1 || expired != 1 {
		t.Fatalf("concurrent answers successes=%d expired=%d, want 1/1", successes, expired)
	}
	state, err = messages.GetPrivateNoForwards(ctx, alice.ID, bob.ID)
	if err != nil || state.Enabled() {
		t.Fatalf("state after concurrent answer=%+v err=%v, want disabled", state, err)
	}
	for _, userID := range []int64{alice.ID, bob.ID} {
		events, err := NewUpdateEventStore(pool).ListAfter(ctx, userID, 0, 10)
		if err != nil {
			t.Fatalf("events user %d: %v", userID, err)
		}
		if len(events) != 3 || events[0].Pts != 1 || events[1].Pts != 2 || events[2].Pts != 3 {
			t.Fatalf("events user %d = %+v, want one enable/request/answer sequence", userID, events)
		}
	}
}