Ctrl K
ComunidadGuías…

internal/store/postgres/channel_top_message_cache_integration_test.go

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

Archivo original ↗Preguntar a KiBot
package postgres

import (
	"context"
	"os"
	"testing"
	"time"

	"telesrv/internal/domain"
)

func TestChannelTopMessageCacheInvalidatesOnTopPayloadNotify(t *testing.T) {
	pool := testPool(t)
	dsn := os.Getenv("TELESRV_TEST_POSTGRES_DSN")
	ctx := context.Background()
	suffix := randomSuffix(t)

	users := NewUserStore(pool)
	owner, err := users.Create(ctx, domain.User{
		AccessHash: 461,
		Phone:      "+1888" + suffix + "91",
		FirstName:  "TopCacheOwner",
	})
	if err != nil {
		t.Fatalf("create owner: %v", err)
	}
	var channelID int64
	t.Cleanup(func() {
		if channelID != 0 {
			_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", channelID)
		}
		_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
	})

	topCache := NewChannelTopMessageCache(32)
	rowCache := NewChannelRowCache(32)
	channels := NewChannelStore(pool,
		WithChannelRowCache(rowCache),
		WithChannelTopMessageCache(topCache),
	)
	created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
		CreatorUserID: owner.ID,
		Title:         "Top cache " + suffix,
		Megagroup:     true,
		Date:          1700000760,
	})
	if err != nil {
		t.Fatalf("create channel: %v", err)
	}
	channelID = created.Channel.ID
	sent, err := channels.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
		UserID:    owner.ID,
		ChannelID: channelID,
		RandomID:  761,
		Message:   "before cache invalidation",
		Date:      1700000761,
	})
	if err != nil {
		t.Fatalf("send channel message: %v", err)
	}
	key := channelMessageLookupKey{channelID: channelID, id: sent.Message.ID}

	// Seed one sentinel so listener reconnect flush is an observable readiness
	// barrier instead of a timing sleep.
	topCache.cache.Store(key, domain.ChannelMessage{ChannelID: channelID, ID: sent.Message.ID, Body: "sentinel"})
	lctx, cancel := context.WithCancel(ctx)
	defer cancel()
	listener := NewReadModelChangeListener(dsn, ReadModelCacheSet{
		ChannelRows:        rowCache,
		ChannelTopMessages: topCache,
	}, nil)
	go listener.Run(lctx)
	if !waitUntil(2*time.Second, func() bool {
		_, ok := topCache.cache.Peek(key)
		return !ok
	}) {
		t.Fatal("read-model listener did not establish LISTEN and flush sentinel")
	}

	view, err := channels.GetChannelDialogs(ctx, owner.ID, []int64{channelID})
	if err != nil {
		t.Fatalf("warm channel dialog: %v", err)
	}
	if len(view.Messages) != 1 || view.Messages[0].Body != "before cache invalidation" {
		t.Fatalf("warm messages = %+v", view.Messages)
	}
	if cached, ok := topCache.cache.Peek(key); !ok || cached.Body != "before cache invalidation" {
		t.Fatalf("top payload was not cached: ok=%v value=%+v", ok, cached)
	}
	materialized, err := channels.HydrateChannelDialogSnapshot(ctx, owner.ID, view.Dialogs)
	if err != nil {
		t.Fatalf("hydrate materialized owner dialog: %v", err)
	}
	if len(materialized.Dialogs) != 1 || len(materialized.Channels) != 1 || len(materialized.Messages) != 1 ||
		materialized.Dialogs[0].Peer.ID != channelID || materialized.Messages[0].Body != "before cache invalidation" {
		t.Fatalf("materialized channel snapshot = dialogs:%+v channels:%+v messages:%+v",
			materialized.Dialogs, materialized.Channels, materialized.Messages)
	}

	const after = "after cache invalidation"
	if _, err := pool.Exec(ctx, `
UPDATE channel_messages
SET body=$3, edit_date=$4
WHERE channel_id=$1 AND id=$2`, channelID, sent.Message.ID, after, 1700000762); err != nil {
		t.Fatalf("update top payload: %v", err)
	}
	if !waitUntil(3*time.Second, func() bool {
		_, ok := topCache.cache.Peek(key)
		return !ok
	}) {
		t.Fatal("top channel message cache was not invalidated by channel_base")
	}

	view, err = channels.GetChannelDialogs(ctx, owner.ID, []int64{channelID})
	if err != nil {
		t.Fatalf("read channel dialog after invalidation: %v", err)
	}
	if len(view.Messages) != 1 || view.Messages[0].Body != after {
		t.Fatalf("post-invalidation messages = %+v, want body %q", view.Messages, after)
	}
}