Ctrl K
ComunidadGuías…

internal/app/userprojection/contact_cache_test.go

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

Archivo original ↗Preguntar a KiBot
package userprojection

import (
	"context"
	"fmt"
	"reflect"
	"sync"
	"testing"
	"time"
	"unsafe"

	"telesrv/internal/domain"
	"telesrv/internal/store"
	"telesrv/internal/store/memory"
)

type blockingFirstListContactStore struct {
	store.ContactStore
	started chan struct{}
	release chan struct{}
	first   domain.ContactList

	mu        sync.Mutex
	firstUsed bool
	listCalls int
}

type blockingFirstPersonalPhotoStore struct {
	store.ContactStore
	started chan struct{}
	release chan struct{}
	first   map[int64]domain.ProfilePhotoRef

	mu        sync.Mutex
	firstUsed bool
}

type stalePersonalPhotoWritebackContextKey struct{}

// stalePersonalPhotoWritebackStore deterministically models an older mutation
// that commits first but returns to the cache wrapper after a newer mutation.
// The old implementation performed a post-commit PersonalPhotos read and could
// publish this captured old value after the newer mutation had completed.
type stalePersonalPhotoWritebackStore struct {
	store.ContactStore
	started chan struct{}
	release chan struct{}

	mu             sync.Mutex
	staleReadCalls int
}

func (s *stalePersonalPhotoWritebackStore) SetPersonalPhoto(ctx context.Context, userID, contactUserID int64, photoID int64, date int) (domain.Contact, bool, error) {
	contact, found, err := s.ContactStore.SetPersonalPhoto(ctx, userID, contactUserID, photoID, date)
	if err != nil || !found || ctx.Value(stalePersonalPhotoWritebackContextKey{}) != true {
		return contact, found, err
	}
	close(s.started)
	select {
	case <-s.release:
	case <-ctx.Done():
		return domain.Contact{}, false, ctx.Err()
	}
	return contact, found, nil
}

func (s *stalePersonalPhotoWritebackStore) PersonalPhotos(ctx context.Context, userID int64, contactUserIDs []int64) (map[int64]domain.ProfilePhotoRef, error) {
	if len(contactUserIDs) > 0 && ctx.Value(stalePersonalPhotoWritebackContextKey{}) == true {
		s.mu.Lock()
		s.staleReadCalls++
		s.mu.Unlock()
		return map[int64]domain.ProfilePhotoRef{
			contactUserIDs[0]: {PhotoID: 9001, Personal: true},
		}, nil
	}
	return s.ContactStore.PersonalPhotos(ctx, userID, contactUserIDs)
}

func (s *stalePersonalPhotoWritebackStore) staleReads() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.staleReadCalls
}

func (s *blockingFirstListContactStore) ListByUser(ctx context.Context, userID int64) (domain.ContactList, error) {
	s.mu.Lock()
	s.listCalls++
	if !s.firstUsed {
		s.firstUsed = true
		s.mu.Unlock()
		close(s.started)
		select {
		case <-s.release:
		case <-ctx.Done():
			return domain.ContactList{}, ctx.Err()
		}
		return domain.ContactList{Contacts: cloneCachedContacts(s.first.Contacts), Hash: s.first.Hash}, nil
	}
	s.mu.Unlock()
	return s.ContactStore.ListByUser(ctx, userID)
}

func (s *blockingFirstListContactStore) callCount() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.listCalls
}

func (s *blockingFirstPersonalPhotoStore) PersonalPhotos(ctx context.Context, userID int64, contactUserIDs []int64) (map[int64]domain.ProfilePhotoRef, error) {
	s.mu.Lock()
	if !s.firstUsed {
		s.firstUsed = true
		first := cloneCachedProfilePhotoRefs(s.first)
		s.mu.Unlock()
		close(s.started)
		select {
		case <-s.release:
		case <-ctx.Done():
			return nil, ctx.Err()
		}
		return first, nil
	}
	s.mu.Unlock()
	return s.ContactStore.PersonalPhotos(ctx, userID, contactUserIDs)
}

func (s *blockingFirstPersonalPhotoStore) SetPersonalPhoto(ctx context.Context, userID, contactUserID int64, photoID int64, date int) (domain.Contact, bool, error) {
	return s.ContactStore.SetPersonalPhoto(ctx, userID, contactUserID, photoID, date)
}

func waitForCacheTestSignal(t *testing.T, ch <-chan struct{}) {
	t.Helper()
	select {
	case <-ch:
	case <-time.After(2 * time.Second):
		t.Fatal("timed out waiting for cache test signal")
	}
}

type countingContactStore struct {
	store.ContactStore
	listCalls           int
	getManyCalls        int
	reverseCalls        int
	projectionCalls     int
	personalPhotoCalls  int
	setPersonalPhotoHit int
}

func (s *countingContactStore) ListByUser(ctx context.Context, userID int64) (domain.ContactList, error) {
	s.listCalls++
	return s.ContactStore.ListByUser(ctx, userID)
}

func (s *countingContactStore) GetMany(ctx context.Context, userID int64, contactUserIDs []int64) (map[int64]domain.Contact, error) {
	s.getManyCalls++
	return s.ContactStore.GetMany(ctx, userID, contactUserIDs)
}

func (s *countingContactStore) GetReverseContacts(ctx context.Context, userID int64, ownerUserIDs []int64) (map[int64]domain.Contact, error) {
	s.reverseCalls++
	return s.ContactStore.GetReverseContacts(ctx, userID, ownerUserIDs)
}

func (s *countingContactStore) ContactProjectionForViewers(ctx context.Context, viewerUserIDs, contactUserIDs []int64) (domain.ContactProjectionBatch, error) {
	s.projectionCalls++
	return s.ContactStore.ContactProjectionForViewers(ctx, viewerUserIDs, contactUserIDs)
}

func (s *countingContactStore) PersonalPhotos(ctx context.Context, userID int64, contactUserIDs []int64) (map[int64]domain.ProfilePhotoRef, error) {
	s.personalPhotoCalls++
	return s.ContactStore.PersonalPhotos(ctx, userID, contactUserIDs)
}

func (s *countingContactStore) SetPersonalPhoto(ctx context.Context, userID, contactUserID int64, photoID int64, date int) (domain.Contact, bool, error) {
	s.setPersonalPhotoHit++
	return s.ContactStore.SetPersonalPhoto(ctx, userID, contactUserID, photoID, date)
}

func TestCachedContactStoreCachesProjectionReads(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{
		ContactUserID: 2,
		FirstName:     "Alice",
		Phone:         "111",
		Note:          "private note",
		NoteEntities:  []domain.MessageEntity{{Type: domain.MessageEntityBold, Offset: 0, Length: 7}},
	}); err != nil {
		t.Fatalf("upsert contact: %v", err)
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)

	first, err := cached.GetMany(ctx, 1, []int64{2, 3})
	if err != nil {
		t.Fatalf("get many first: %v", err)
	}
	if first[2].FirstName != "Alice" || first[2].Note != "private note" || len(first[2].NoteEntities) != 1 {
		t.Fatalf("first contact = %+v, want Alice with private note", first[2])
	}
	first[2].NoteEntities[0].Length = 99
	second, err := cached.GetMany(ctx, 1, []int64{2, 3})
	if err != nil {
		t.Fatalf("get many second: %v", err)
	}
	if second[2].FirstName != "Alice" || second[2].Note != "private note" || len(second[2].NoteEntities) != 1 || second[2].NoteEntities[0].Length != 7 {
		t.Fatalf("second contact = %+v, want isolated cached Alice note", second[2])
	}
	if counting.listCalls != 1 {
		t.Fatalf("ListByUser calls = %d, want 1 account snapshot load", counting.listCalls)
	}
	if counting.getManyCalls != 0 {
		t.Fatalf("GetMany calls = %d, want 0 with account snapshot", counting.getManyCalls)
	}

	reverse, err := cached.GetReverseContacts(ctx, 2, []int64{1})
	if err != nil {
		t.Fatalf("get reverse: %v", err)
	}
	if reverse[1].FirstName != "Alice" {
		t.Fatalf("reverse contact = %+v, want Alice", reverse[1])
	}
	if counting.reverseCalls != 0 {
		t.Fatalf("GetReverseContacts calls = %d, want 0 from shared contact cache", counting.reverseCalls)
	}
}

func TestCachedContactStoreContactSnapshotLRUEvictsOnlyOldestViewer(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	for viewerID := int64(1); viewerID <= 3; viewerID++ {
		if _, err := base.Upsert(ctx, viewerID, domain.ContactInput{
			ContactUserID: 100 + viewerID,
			FirstName:     fmt.Sprintf("viewer-%d", viewerID),
		}); err != nil {
			t.Fatalf("seed viewer %d: %v", viewerID, err)
		}
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStoreWithMaxViewers(counting, time.Hour, 2)

	for _, viewerID := range []int64{1, 2, 1, 3} {
		if _, err := cached.ListByUser(ctx, viewerID); err != nil {
			t.Fatalf("list viewer %d: %v", viewerID, err)
		}
	}
	if counting.listCalls != 3 {
		t.Fatalf("ListByUser calls = %d, want 3 before evicted viewer is read", counting.listCalls)
	}
	cached.mu.RLock()
	_, hasOne := cached.contacts[1]
	_, hasTwo := cached.contacts[2]
	_, hasThree := cached.contacts[3]
	contactEntries := cached.contactLRU.Len()
	cached.mu.RUnlock()
	if !hasOne || hasTwo || !hasThree || contactEntries != 2 {
		t.Fatalf("contact LRU state = one:%v two:%v three:%v len:%d, want one+three only", hasOne, hasTwo, hasThree, contactEntries)
	}

	if _, err := cached.ListByUser(ctx, 1); err != nil {
		t.Fatalf("list retained viewer 1: %v", err)
	}
	if counting.listCalls != 3 {
		t.Fatalf("retained viewer caused cold load: calls=%d, want 3", counting.listCalls)
	}
	if _, err := cached.ListByUser(ctx, 2); err != nil {
		t.Fatalf("list evicted viewer 2: %v", err)
	}
	if counting.listCalls != 4 {
		t.Fatalf("evicted viewer did not cold load exactly once: calls=%d, want 4", counting.listCalls)
	}
}

func TestCachedContactStoreContactAndPersonalPhotoLRUsAreIndependent(t *testing.T) {
	cached := NewCachedContactStoreWithMaxViewers(memory.NewContactStore(), time.Hour, 2)
	expireAt := time.Now().Add(time.Hour)
	contactSnap := func(userID int64) contactAccountSnapshot {
		return buildContactAccountSnapshot(domain.ContactList{Contacts: []domain.Contact{{
			User: domain.User{ID: 100 + userID},
		}}}, expireAt)
	}
	photoSnap := func(userID int64) personalPhotoSnapshot {
		return personalPhotoSnapshot{
			refs:     map[int64]domain.ProfilePhotoRef{100 + userID: {PhotoID: 9000 + userID}},
			expireAt: expireAt,
		}
	}

	cached.mu.Lock()
	cached.storeContactSnapshotLocked(1, contactSnap(1))
	cached.storeContactSnapshotLocked(2, contactSnap(2))
	cached.storePersonalPhotoSnapshotLocked(1, photoSnap(1))
	cached.storePersonalPhotoSnapshotLocked(2, photoSnap(2))
	cached.mu.Unlock()
	if _, ok := cached.lookupContactSnapshot(1, time.Now()); !ok {
		t.Fatal("contact viewer 1 missing before LRU touch")
	}
	cached.mu.Lock()
	cached.storeContactSnapshotLocked(3, contactSnap(3))
	cached.mu.Unlock()

	cached.mu.RLock()
	_, contactOne := cached.contacts[1]
	_, contactTwo := cached.contacts[2]
	_, contactThree := cached.contacts[3]
	_, photoOne := cached.personalPhotos[1]
	_, photoTwo := cached.personalPhotos[2]
	cached.mu.RUnlock()
	if !contactOne || contactTwo || !contactThree {
		t.Fatalf("contact LRU = one:%v two:%v three:%v, want one+three", contactOne, contactTwo, contactThree)
	}
	if !photoOne || !photoTwo {
		t.Fatalf("contact eviction crossed into personal-photo LRU: one:%v two:%v", photoOne, photoTwo)
	}

	if _, ok := cached.lookupPersonalPhotoSnapshot(2, time.Now()); !ok {
		t.Fatal("personal-photo viewer 2 missing before LRU touch")
	}
	cached.mu.Lock()
	cached.storePersonalPhotoSnapshotLocked(3, photoSnap(3))
	cached.mu.Unlock()
	cached.mu.RLock()
	_, photoOne = cached.personalPhotos[1]
	_, photoTwo = cached.personalPhotos[2]
	_, photoThree := cached.personalPhotos[3]
	_, contactOne = cached.contacts[1]
	_, contactThree = cached.contacts[3]
	cached.mu.RUnlock()
	if photoOne || !photoTwo || !photoThree {
		t.Fatalf("personal-photo LRU = one:%v two:%v three:%v, want two+three", photoOne, photoTwo, photoThree)
	}
	if !contactOne || !contactThree {
		t.Fatalf("personal-photo eviction crossed into contact LRU: one:%v three:%v", contactOne, contactThree)
	}

	cached.InvalidateViewers(3)
	cached.mu.RLock()
	_, contactThree = cached.contacts[3]
	_, photoThree = cached.personalPhotos[3]
	_, contactElement := cached.contactElements[3]
	_, photoElement := cached.personalElements[3]
	cached.mu.RUnlock()
	if contactThree || photoThree || contactElement || photoElement {
		t.Fatalf("viewer invalidation left LRU state: contact=%v photo=%v contactElement=%v photoElement=%v",
			contactThree, photoThree, contactElement, photoElement)
	}
}

func TestCachedContactStoreUnrelatedViewerInvalidationDoesNotRejectRefill(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 2, domain.ContactInput{ContactUserID: 20, FirstName: "current"}); err != nil {
		t.Fatalf("seed current contact: %v", err)
	}
	blocking := &blockingFirstListContactStore{
		ContactStore: base,
		started:      make(chan struct{}),
		release:      make(chan struct{}),
		first: domain.ContactList{Contacts: []domain.Contact{{
			User:      domain.User{ID: 20},
			FirstName: "captured",
		}}},
	}
	cached := NewCachedContactStore(blocking, time.Hour)

	type readResult struct {
		contacts map[int64]domain.Contact
		err      error
	}
	resultCh := make(chan readResult, 1)
	go func() {
		contacts, err := cached.GetMany(ctx, 2, []int64{20})
		resultCh <- readResult{contacts: contacts, err: err}
	}()
	waitForCacheTestSignal(t, blocking.started)
	cached.InvalidateViewers(1)
	close(blocking.release)

	select {
	case result := <-resultCh:
		if result.err != nil {
			t.Fatalf("contact read: %v", result.err)
		}
		if got := result.contacts[20].FirstName; got != "captured" {
			t.Fatalf("unrelated invalidation rejected captured refill: got %q", got)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("timed out waiting for contact read")
	}
	if calls := blocking.callCount(); calls != 1 {
		t.Fatalf("ListByUser calls = %d, want 1 after unrelated invalidation", calls)
	}
}

func TestCachedContactStoreContactProjectionForViewersUsesViewerOwnedPairCache(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("seed viewer 1 contact: %v", err)
	}
	if _, _, err := base.SetPersonalPhoto(ctx, 1, 2, 9101, 100); err != nil {
		t.Fatalf("seed viewer 1 personal photo: %v", err)
	}
	if _, err := base.Upsert(ctx, 3, domain.ContactInput{ContactUserID: 2, FirstName: "Bob"}); err != nil {
		t.Fatalf("seed viewer 3 contact: %v", err)
	}
	if _, _, err := base.SetPersonalPhoto(ctx, 3, 2, 9103, 100); err != nil {
		t.Fatalf("seed viewer 3 personal photo: %v", err)
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)

	if _, err := cached.GetMany(ctx, 1, []int64{2}); err != nil {
		t.Fatalf("prime viewer 1 contacts: %v", err)
	}
	if _, err := cached.PersonalPhotos(ctx, 1, []int64{2}); err != nil {
		t.Fatalf("prime viewer 1 photos: %v", err)
	}

	first, err := cached.ContactProjectionForViewers(ctx, []int64{1, 3}, []int64{2})
	if err != nil {
		t.Fatalf("first projection: %v", err)
	}
	if first.Contacts[1][2].FirstName != "Alice" || first.PersonalPhotos[1][2].PhotoID != 9101 {
		t.Fatalf("viewer 1 projection = %+v %+v, want warm Alice/9101", first.Contacts[1][2], first.PersonalPhotos[1][2])
	}
	if first.Contacts[3][2].FirstName != "Bob" || first.PersonalPhotos[3][2].PhotoID != 9103 {
		t.Fatalf("viewer 3 projection = %+v %+v, want cold Bob/9103", first.Contacts[3][2], first.PersonalPhotos[3][2])
	}
	if counting.projectionCalls != 1 {
		t.Fatalf("projection calls after first = %d, want 1", counting.projectionCalls)
	}

	second, err := cached.ContactProjectionForViewers(ctx, []int64{3}, []int64{2})
	if err != nil {
		t.Fatalf("second projection: %v", err)
	}
	if second.Contacts[3][2].FirstName != "Bob" || second.PersonalPhotos[3][2].PhotoID != 9103 {
		t.Fatalf("cached viewer 3 projection = %+v %+v, want Bob/9103", second.Contacts[3][2], second.PersonalPhotos[3][2])
	}
	if counting.projectionCalls != 1 {
		t.Fatalf("projection calls after cached read = %d, want 1", counting.projectionCalls)
	}

	cached.InvalidateViewers(3)
	if _, err := cached.ContactProjectionForViewers(ctx, []int64{3}, []int64{2}); err != nil {
		t.Fatalf("projection after invalidation: %v", err)
	}
	if counting.projectionCalls != 2 {
		t.Fatalf("projection calls after invalidation = %d, want 2", counting.projectionCalls)
	}
}

func TestCachedContactStorePairSnapshotsAreCompact(t *testing.T) {
	pointerSize := unsafe.Sizeof(uintptr(0))
	timeSize := unsafe.Sizeof(time.Time{})
	if got, max := unsafe.Sizeof(reverseContactSnapshot{}), timeSize+2*pointerSize; got > max {
		t.Fatalf("reverseContactSnapshot size = %d, want <= %d (one value pointer plus expiry)", got, max)
	}
	if got, max := unsafe.Sizeof(contactProjectionSnapshot{}), timeSize+3*pointerSize; got > max {
		t.Fatalf("contactProjectionSnapshot size = %d, want <= %d (two value pointers plus expiry)", got, max)
	}
	if got, large := unsafe.Sizeof(reverseContactSnapshot{}), unsafe.Sizeof(domain.Contact{}); got >= large {
		t.Fatalf("reverseContactSnapshot size = %d, must not embed %d-byte domain.Contact", got, large)
	}
	if got, large := unsafe.Sizeof(contactProjectionSnapshot{}), unsafe.Sizeof(domain.Contact{}); got >= large {
		t.Fatalf("contactProjectionSnapshot size = %d, must not embed %d-byte domain.Contact", got, large)
	}
	if got, max := unsafe.Sizeof(cachedContactProjectionOverlay{}), uintptr(128); got > max {
		t.Fatalf("cachedContactProjectionOverlay size = %d, want <= %d bytes", got, max)
	}
	if got, large := unsafe.Sizeof(cachedContactProjectionOverlay{}), unsafe.Sizeof(domain.Contact{}); got >= large {
		t.Fatalf("cachedContactProjectionOverlay size = %d, must be smaller than %d-byte domain.Contact", got, large)
	}
}

func TestCachedContactStorePairSnapshotsUseNilForNegativeAndClonePositiveValues(t *testing.T) {
	cached := NewCachedContactStore(memory.NewContactStore(), time.Hour)
	now := time.Unix(1000, 0)
	expireAt := now.Add(time.Hour)
	contact := domain.Contact{
		User: domain.User{
			ID: 2, AccessHash: 2002, Phone: "global-phone", FirstName: "Global", LastName: "User",
			Username: "global_user", Mutual: true, PhotoStripped: []byte{1, 2, 3},
		},
		FirstName: "Local",
		LastName:  "Name",
		Phone:     "known-phone",
		Note:      "private note",
		NoteEntities: []domain.MessageEntity{{
			Type: domain.MessageEntityBold, Offset: 0, Length: 3,
		}},
		CloseFriend: true,
	}
	photo := domain.ProfilePhotoRef{PhotoID: 9001, Stripped: []byte{4, 5, 6}, Personal: true}
	positiveReverseKey := reverseContactKey{ownerUserID: 1, contactUserID: 2}
	negativeReverseKey := reverseContactKey{ownerUserID: 3, contactUserID: 2}
	positiveProjectionKey := contactProjectionKey{viewerUserID: 1, contactUserID: 2}
	negativeProjectionKey := contactProjectionKey{viewerUserID: 1, contactUserID: 99}

	cached.mu.Lock()
	cached.storeReverseContactLocked(positiveReverseKey, contact, true, expireAt)
	cached.storeReverseContactLocked(negativeReverseKey, contact, false, expireAt)
	cached.storeContactProjectionPairLocked(positiveProjectionKey, contact, true, photo, true, expireAt)
	cached.storeContactProjectionPairLocked(negativeProjectionKey, contact, false, photo, false, expireAt)
	positiveReverse := cached.reverse[positiveReverseKey].Value.(*reverseContactEntry).snapshot
	negativeReverse := cached.reverse[negativeReverseKey].Value.(*reverseContactEntry).snapshot
	positiveProjection := cached.projection[positiveProjectionKey].Value.(*contactProjectionEntry).snapshot
	negativeProjection := cached.projection[negativeProjectionKey].Value.(*contactProjectionEntry).snapshot
	cached.mu.Unlock()

	if positiveReverse.contact == nil || positiveProjection.contact == nil || positiveProjection.personalPhoto == nil {
		t.Fatalf("positive snapshots lost values: reverse=%+v projection=%+v", positiveReverse, positiveProjection)
	}
	if negativeReverse.contact != nil || negativeProjection.contact != nil || negativeProjection.personalPhoto != nil {
		t.Fatalf("negative snapshots retained value allocations: reverse=%+v projection=%+v", negativeReverse, negativeProjection)
	}

	// Publication clones inputs; subsequent caller mutation cannot alter cache.
	contact.User.PhotoStripped[0] = 10
	contact.NoteEntities[0].Length = 10
	photo.Stripped[0] = 10

	reverse, found, hit := cached.lookupReverseContact(1, 2, now)
	if !hit || !found || reverse.User.PhotoStripped[0] != 1 || reverse.NoteEntities[0].Length != 3 {
		t.Fatalf("positive reverse lookup = %+v found=%v hit=%v", reverse, found, hit)
	}
	reverse.User.PhotoStripped[0] = 11
	reverse.NoteEntities[0].Length = 11
	reverseAgain, found, hit := cached.lookupReverseContact(1, 2, now)
	if !hit || !found || reverseAgain.User.PhotoStripped[0] != 1 || reverseAgain.NoteEntities[0].Length != 3 {
		t.Fatalf("reverse lookup shared mutable slices: %+v found=%v hit=%v", reverseAgain, found, hit)
	}
	if _, found, hit := cached.lookupReverseContact(3, 2, now); !hit || found {
		t.Fatalf("negative reverse lookup found=%v hit=%v, want false/true", found, hit)
	}

	pair, hit := cached.lookupContactProjectionPair(1, 2, now)
	if !hit || !pair.contactFound || !pair.personalPhotoFound || pair.personalPhoto.Stripped[0] != 4 {
		t.Fatalf("positive projection lookup = %+v hit=%v", pair, hit)
	}
	if !reflect.DeepEqual(pair.contact.User, domain.User{ID: 2}) {
		t.Fatalf("projection pair retained base user data: %+v", pair.contact.User)
	}
	if pair.contact.FirstName != "Local" || pair.contact.LastName != "Name" || pair.contact.Phone != "known-phone" ||
		pair.contact.Note != "private note" || !pair.contact.Mutual || !pair.contact.CloseFriend {
		t.Fatalf("projection pair lost viewer-owned overlay: %+v", pair.contact)
	}
	pair.contact.NoteEntities[0].Length = 12
	pair.personalPhoto.Stripped[0] = 12
	pairAgain, hit := cached.lookupContactProjectionPair(1, 2, now)
	if !hit || !reflect.DeepEqual(pairAgain.contact.User, domain.User{ID: 2}) || pairAgain.contact.NoteEntities[0].Length != 3 || pairAgain.personalPhoto.Stripped[0] != 4 {
		t.Fatalf("projection lookup shared mutable slices: %+v hit=%v", pairAgain, hit)
	}
	negative, hit := cached.lookupContactProjectionPair(1, 99, now)
	if !hit || negative.contactFound || negative.personalPhotoFound {
		t.Fatalf("negative projection lookup = %+v hit=%v, want cached miss", negative, hit)
	}
}

func TestCachedContactStoreLargeDenseProjectionDoesNotPollutePairCache(t *testing.T) {
	if !admitDenseContactProjectionPairs(1, contactProjectionDenseAdmissionMaxCells) {
		t.Fatal("admission rejected the documented cell limit")
	}
	if admitDenseContactProjectionPairs(1, contactProjectionDenseAdmissionMaxCells+1) {
		t.Fatal("admission accepted a batch above the documented cell limit")
	}

	counting := &countingContactStore{ContactStore: memory.NewContactStore()}
	cached := NewCachedContactStore(counting, time.Hour)
	seedKey := contactProjectionKey{viewerUserID: 1, contactUserID: 2}
	cached.mu.Lock()
	cached.storeContactProjectionPairLocked(
		seedKey,
		domain.Contact{User: domain.User{ID: 2}, FirstName: "seed"}, true,
		domain.ProfilePhotoRef{}, false,
		cached.now().Add(time.Hour),
	)
	cached.mu.Unlock()

	viewers := []int64{1001, 1002}
	targets := make([]int64, contactProjectionDenseAdmissionMaxCells/len(viewers)+1)
	for i := range targets {
		targets[i] = int64(100000 + i)
	}
	for call := 1; call <= 2; call++ {
		got, err := cached.ContactProjectionForViewers(context.Background(), viewers, targets)
		if err != nil {
			t.Fatalf("large dense projection call %d: %v", call, err)
		}
		if len(got.Contacts) != 0 || len(got.PersonalPhotos) != 0 {
			t.Fatalf("large empty projection call %d = %+v", call, got)
		}
		cached.mu.Lock()
		_, seedPresent := cached.projection[seedKey]
		pairCount := len(cached.projection)
		lruCount := cached.projectionLRU.Len()
		cached.mu.Unlock()
		if !seedPresent || pairCount != 1 || lruCount != 1 {
			t.Fatalf("large dense load polluted pair cache: seed=%v pairs=%d lru=%d", seedPresent, pairCount, lruCount)
		}
	}
	if counting.projectionCalls != 2 {
		t.Fatalf("projection calls = %d, want 2 because oversized results are returned but not admitted", counting.projectionCalls)
	}
}

func TestCachedContactStoreContactProjectionSkipsColdReadForKnownNonContact(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("seed contact: %v", err)
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)

	if _, err := cached.GetMany(ctx, 1, []int64{99}); err != nil {
		t.Fatalf("prime viewer contact snapshot: %v", err)
	}
	got, err := cached.ContactProjectionForViewers(ctx, []int64{1}, []int64{99})
	if err != nil {
		t.Fatalf("projection: %v", err)
	}
	if len(got.Contacts[1]) != 0 || len(got.PersonalPhotos[1]) != 0 {
		t.Fatalf("known non-contact projection = %+v", got)
	}
	if counting.projectionCalls != 0 {
		t.Fatalf("projection calls = %d, want 0 for known non-contact", counting.projectionCalls)
	}
	if counting.personalPhotoCalls != 0 {
		t.Fatalf("personal photo calls = %d, want 0 for known non-contact", counting.personalPhotoCalls)
	}
}

func TestCachedContactStoreCachesLargeReverseContactBatch(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	owners := make([]int64, 32)
	for i := range owners {
		owners[i] = int64(i + 1)
		if i%2 == 0 {
			if _, err := base.Upsert(ctx, owners[i], domain.ContactInput{
				ContactUserID: 9001,
				FirstName:     "Viewer",
			}); err != nil {
				t.Fatalf("seed owner %d: %v", owners[i], err)
			}
		}
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)

	first, err := cached.GetReverseContacts(ctx, 9001, owners)
	if err != nil {
		t.Fatalf("first reverse lookup: %v", err)
	}
	if len(first) != 16 || counting.reverseCalls != 1 || counting.listCalls != 0 {
		t.Fatalf("first reverse hits=%d reverseCalls=%d listCalls=%d, want 16/1/0", len(first), counting.reverseCalls, counting.listCalls)
	}
	second, err := cached.GetReverseContacts(ctx, 9001, owners)
	if err != nil {
		t.Fatalf("second reverse lookup: %v", err)
	}
	if len(second) != 16 || counting.reverseCalls != 1 || counting.listCalls != 0 {
		t.Fatalf("cached reverse hits=%d reverseCalls=%d listCalls=%d, want 16/1/0", len(second), counting.reverseCalls, counting.listCalls)
	}

	cached.InvalidateViewers(owners[0])
	third, err := cached.GetReverseContacts(ctx, 9001, owners)
	if err != nil {
		t.Fatalf("reverse lookup after owner invalidation: %v", err)
	}
	if len(third) != 16 || counting.reverseCalls != 2 {
		t.Fatalf("invalidated reverse hits=%d reverseCalls=%d, want 16/2", len(third), counting.reverseCalls)
	}
}

func TestCachedContactStoreReversePairsUsePerEntryLRU(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	for ownerID := int64(1); ownerID <= 3; ownerID++ {
		if _, err := base.Upsert(ctx, ownerID, domain.ContactInput{
			ContactUserID: 9001,
			FirstName:     "Viewer",
		}); err != nil {
			t.Fatalf("seed owner %d: %v", ownerID, err)
		}
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)
	cached.reverseCap = 2

	for _, ownerID := range []int64{1, 2, 1, 3, 1, 2} {
		got, err := cached.GetReverseContacts(ctx, 9001, []int64{ownerID})
		if err != nil {
			t.Fatalf("reverse owner %d: %v", ownerID, err)
		}
		if _, ok := got[ownerID]; !ok {
			t.Fatalf("reverse owner %d missing", ownerID)
		}
	}
	if counting.reverseCalls != 4 {
		t.Fatalf("reverse calls = %d, want 4 (owner 1 touched, owner 2 evicted only)", counting.reverseCalls)
	}
	if len(cached.reverse) != 2 || cached.reverseLRU.Len() != 2 {
		t.Fatalf("reverse cache map/list = %d/%d, want 2/2", len(cached.reverse), cached.reverseLRU.Len())
	}
}

func TestCachedContactStoreInvalidatesAccountSnapshotAfterMutation(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("upsert contact: %v", err)
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)

	first, err := cached.GetMany(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("get first: %v", err)
	}
	if first[2].FirstName != "Alice" {
		t.Fatalf("first = %+v, want Alice", first[2])
	}
	if _, err := cached.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alicia"}); err != nil {
		t.Fatalf("upsert through cache: %v", err)
	}
	second, err := cached.GetMany(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("get second: %v", err)
	}
	if second[2].FirstName != "Alicia" {
		t.Fatalf("second = %+v, want Alicia after invalidation", second[2])
	}
	if counting.listCalls != 2 {
		t.Fatalf("ListByUser calls = %d, want 2 after safe invalidation and reload", counting.listCalls)
	}
}

func TestCachedContactStorePublishedSnapshotsStayImmutableDuringMutations(t *testing.T) {
	tests := []struct {
		name   string
		mutate func(context.Context, *CachedContactStore) error
	}{
		{
			name: "upsert",
			mutate: func(ctx context.Context, cached *CachedContactStore) error {
				_, err := cached.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "After"})
				return err
			},
		},
		{
			name: "delete",
			mutate: func(ctx context.Context, cached *CachedContactStore) error {
				_, err := cached.Delete(ctx, 1, []int64{2})
				return err
			},
		},
		{
			name: "close_friends",
			mutate: func(ctx context.Context, cached *CachedContactStore) error {
				_, err := cached.SetCloseFriends(ctx, 1, []int64{2})
				return err
			},
		},
		{
			name: "personal_photo",
			mutate: func(ctx context.Context, cached *CachedContactStore) error {
				_, found, err := cached.SetPersonalPhoto(ctx, 1, 2, 9002, 101)
				if err == nil && !found {
					return fmt.Errorf("contact not found")
				}
				return err
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ctx := context.Background()
			base := memory.NewContactStore()
			if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Before"}); err != nil {
				t.Fatalf("seed contact: %v", err)
			}
			if _, found, err := base.SetPersonalPhoto(ctx, 1, 2, 9001, 100); err != nil || !found {
				t.Fatalf("seed personal photo: %v found=%v", err, found)
			}
			cached := NewCachedContactStore(base, 0)
			if _, err := cached.GetMany(ctx, 1, []int64{2}); err != nil {
				t.Fatalf("warm contacts: %v", err)
			}
			if _, err := cached.PersonalPhotos(ctx, 1, []int64{2}); err != nil {
				t.Fatalf("warm personal photos: %v", err)
			}

			cached.mu.RLock()
			contactSnap, contactsWarm := cached.contacts[1]
			photoSnap, photosWarm := cached.personalPhotos[1]
			cached.mu.RUnlock()
			if !contactsWarm || !photosWarm {
				t.Fatal("snapshots were not warm before mutation")
			}

			started := make(chan struct{})
			stop := make(chan struct{})
			done := make(chan struct{})
			go func() {
				defer close(done)
				signaled := false
				for {
					contact := contactSnap.contacts[2]
					for i := range contactSnap.ordered {
						_ = contactSnap.ordered[i].User.ID
					}
					ref := photoSnap.refs[2]
					_, _ = contact.FirstName, ref.PhotoID
					if !signaled {
						close(started)
						signaled = true
					}
					select {
					case <-stop:
						return
					default:
					}
				}
			}()
			waitForCacheTestSignal(t, started)
			if err := tc.mutate(ctx, cached); err != nil {
				close(stop)
				<-done
				t.Fatalf("mutation: %v", err)
			}
			close(stop)
			<-done

			// A snapshot obtained before invalidation remains a valid immutable
			// value for an in-flight reader; only the outer cache entry is removed.
			if got := contactSnap.contacts[2]; got.FirstName != "Before" || got.CloseFriend {
				t.Fatalf("published contact snapshot mutated in place: %+v", got)
			}
			if got := photoSnap.refs[2]; got.PhotoID != 9001 {
				t.Fatalf("published photo snapshot mutated in place: %+v", got)
			}
			cached.mu.RLock()
			_, contactsWarm = cached.contacts[1]
			_, photosWarm = cached.personalPhotos[1]
			cached.mu.RUnlock()
			if contactsWarm || photosWarm {
				t.Fatalf("mutation left stale snapshots published: contacts=%v photos=%v", contactsWarm, photosWarm)
			}
		})
	}
}

func TestCachedContactStoreExternalInvalidationAndFlush(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("upsert contact: %v", err)
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)

	if _, err := cached.GetMany(ctx, 1, []int64{2}); err != nil {
		t.Fatalf("prime get: %v", err)
	}
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alicia"}); err != nil {
		t.Fatalf("direct upsert: %v", err)
	}
	cached.InvalidateViewers(1)
	got, err := cached.GetMany(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("get after external invalidation: %v", err)
	}
	if got[2].FirstName != "Alicia" {
		t.Fatalf("after invalidation = %+v, want Alicia", got[2])
	}

	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Ally"}); err != nil {
		t.Fatalf("direct upsert 2: %v", err)
	}
	cached.FlushReadModelCache()
	got, err = cached.GetMany(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("get after flush: %v", err)
	}
	if got[2].FirstName != "Ally" {
		t.Fatalf("after flush = %+v, want Ally", got[2])
	}
	if counting.listCalls != 3 {
		t.Fatalf("ListByUser calls = %d, want 3 after prime+invalidate+flush", counting.listCalls)
	}
}

func TestCachedContactStoreDoesNotRefillStaleSnapshotAfterInvalidation(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("seed contact: %v", err)
	}
	first, err := base.ListByUser(ctx, 1)
	if err != nil {
		t.Fatalf("snapshot first contact list: %v", err)
	}
	blocking := &blockingFirstListContactStore{
		ContactStore: base,
		started:      make(chan struct{}),
		release:      make(chan struct{}),
		first:        first,
	}
	cached := NewCachedContactStore(blocking, 0)

	type readResult struct {
		contacts map[int64]domain.Contact
		err      error
	}
	resultCh := make(chan readResult, 1)
	go func() {
		got, err := cached.GetMany(ctx, 1, []int64{2})
		resultCh <- readResult{contacts: got, err: err}
	}()
	waitForCacheTestSignal(t, blocking.started)

	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alicia"}); err != nil {
		t.Fatalf("update contact while first load is blocked: %v", err)
	}
	cached.InvalidateViewers(1)
	close(blocking.release)

	var result readResult
	select {
	case result = <-resultCh:
	case <-time.After(2 * time.Second):
		t.Fatal("timed out waiting for contact read")
	}
	if result.err != nil {
		t.Fatalf("contact read: %v", result.err)
	}
	if result.contacts[2].FirstName != "Alicia" {
		t.Fatalf("contact after concurrent invalidation = %+v, want Alicia", result.contacts[2])
	}

	cachedHit, err := cached.GetMany(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("cached hit after stale load retry: %v", err)
	}
	if cachedHit[2].FirstName != "Alicia" {
		t.Fatalf("cached value after stale load retry = %+v, want Alicia", cachedHit[2])
	}
	if calls := blocking.callCount(); calls != 2 {
		t.Fatalf("ListByUser calls = %d, want stale load plus exact-viewer retry", calls)
	}
}

func TestCachedContactStoreFlushRejectsEveryInFlightRefill(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("seed contact: %v", err)
	}
	first, err := base.ListByUser(ctx, 1)
	if err != nil {
		t.Fatalf("snapshot first contact list: %v", err)
	}
	blocking := &blockingFirstListContactStore{
		ContactStore: base,
		started:      make(chan struct{}),
		release:      make(chan struct{}),
		first:        first,
	}
	cached := NewCachedContactStore(blocking, time.Hour)

	type readResult struct {
		contacts map[int64]domain.Contact
		err      error
	}
	resultCh := make(chan readResult, 1)
	go func() {
		contacts, err := cached.GetMany(ctx, 1, []int64{2})
		resultCh <- readResult{contacts: contacts, err: err}
	}()
	waitForCacheTestSignal(t, blocking.started)
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alicia"}); err != nil {
		t.Fatalf("update contact while first load is blocked: %v", err)
	}
	cached.FlushReadModelCache()
	close(blocking.release)

	select {
	case result := <-resultCh:
		if result.err != nil {
			t.Fatalf("contact read: %v", result.err)
		}
		if got := result.contacts[2].FirstName; got != "Alicia" {
			t.Fatalf("flush allowed stale refill: got %q", got)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("timed out waiting for contact read")
	}
	if calls := blocking.callCount(); calls != 2 {
		t.Fatalf("ListByUser calls = %d, want stale load plus post-flush retry", calls)
	}
}

func TestCachedContactStoreInvalidatesPersonalPhoto(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("upsert contact: %v", err)
	}
	if _, found, err := base.SetPersonalPhoto(ctx, 1, 2, 9001, 100); err != nil || !found {
		t.Fatalf("set personal photo: %v found=%v", err, found)
	}
	counting := &countingContactStore{ContactStore: base}
	cached := NewCachedContactStore(counting, 0)

	first, err := cached.PersonalPhotos(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("personal photos first: %v", err)
	}
	if first[2].PhotoID != 9001 || !first[2].Personal {
		t.Fatalf("first personal photo = %+v, want 9001", first[2])
	}
	second, err := cached.PersonalPhotos(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("personal photos second: %v", err)
	}
	if second[2].PhotoID != 9001 {
		t.Fatalf("second personal photo = %+v, want 9001", second[2])
	}
	if counting.listCalls != 1 {
		t.Fatalf("ListByUser calls = %d, want 1 personal-photo account snapshot load", counting.listCalls)
	}
	if counting.personalPhotoCalls != 1 {
		t.Fatalf("PersonalPhotos calls = %d, want 1", counting.personalPhotoCalls)
	}

	if _, found, err := cached.SetPersonalPhoto(ctx, 1, 2, 9002, 101); err != nil || !found {
		t.Fatalf("cached set personal photo: %v found=%v", err, found)
	}
	third, err := cached.PersonalPhotos(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("personal photos third: %v", err)
	}
	if third[2].PhotoID != 9002 {
		t.Fatalf("third personal photo = %+v, want 9002 after invalidation", third[2])
	}
	if counting.personalPhotoCalls != 2 {
		t.Fatalf("PersonalPhotos calls after invalidation = %d, want 2", counting.personalPhotoCalls)
	}
	if counting.listCalls != 2 {
		t.Fatalf("ListByUser calls after mutation = %d, want 2 after safe invalidation and reload", counting.listCalls)
	}
	if counting.setPersonalPhotoHit != 1 {
		t.Fatalf("SetPersonalPhoto calls = %d, want 1", counting.setPersonalPhotoHit)
	}
}

func TestCachedContactStoreDoesNotRefillStalePersonalPhotoAfterInvalidation(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("upsert contact: %v", err)
	}
	if _, found, err := base.SetPersonalPhoto(ctx, 1, 2, 9001, 100); err != nil || !found {
		t.Fatalf("seed personal photo: %v found=%v", err, found)
	}
	first, err := base.PersonalPhotos(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("snapshot first personal photo: %v", err)
	}
	blocking := &blockingFirstPersonalPhotoStore{
		ContactStore: base,
		started:      make(chan struct{}),
		release:      make(chan struct{}),
		first:        first,
	}
	cached := NewCachedContactStore(blocking, 0)

	type readResult struct {
		refs map[int64]domain.ProfilePhotoRef
		err  error
	}
	resultCh := make(chan readResult, 1)
	go func() {
		refs, err := cached.PersonalPhotos(ctx, 1, []int64{2})
		resultCh <- readResult{refs: refs, err: err}
	}()
	waitForCacheTestSignal(t, blocking.started)

	if _, found, err := cached.SetPersonalPhoto(ctx, 1, 2, 9002, 101); err != nil || !found {
		t.Fatalf("update personal photo while first load is blocked: %v found=%v", err, found)
	}
	close(blocking.release)

	var result readResult
	select {
	case result = <-resultCh:
	case <-time.After(2 * time.Second):
		t.Fatal("timed out waiting for personal photo read")
	}
	if result.err != nil {
		t.Fatalf("personal photo read: %v", result.err)
	}
	if result.refs[2].PhotoID != 9002 {
		t.Fatalf("personal photo after concurrent invalidation = %+v, want 9002", result.refs[2])
	}
}

func TestCachedContactStoreOlderPersonalPhotoMutationCannotReinsertStalePair(t *testing.T) {
	ctx := context.Background()
	base := memory.NewContactStore()
	if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice"}); err != nil {
		t.Fatalf("seed contact: %v", err)
	}
	if _, found, err := base.SetPersonalPhoto(ctx, 1, 2, 9000, 99); err != nil || !found {
		t.Fatalf("seed personal photo: %v found=%v", err, found)
	}
	inner := &stalePersonalPhotoWritebackStore{
		ContactStore: base,
		started:      make(chan struct{}),
		release:      make(chan struct{}),
	}
	cached := NewCachedContactStore(inner, 0)
	if refs, err := cached.PersonalPhotos(ctx, 1, []int64{2}); err != nil || refs[2].PhotoID != 9000 {
		t.Fatalf("warm personal photo = %+v err=%v, want 9000", refs[2], err)
	}

	olderCtx := context.WithValue(ctx, stalePersonalPhotoWritebackContextKey{}, true)
	type setResult struct {
		found bool
		err   error
	}
	olderResult := make(chan setResult, 1)
	go func() {
		_, found, err := cached.SetPersonalPhoto(olderCtx, 1, 2, 9001, 100)
		olderResult <- setResult{found: found, err: err}
	}()
	waitForCacheTestSignal(t, inner.started)

	// The newer DB commit completes and invalidates the warm snapshot first.
	if _, found, err := cached.SetPersonalPhoto(ctx, 1, 2, 9002, 101); err != nil || !found {
		t.Fatalf("newer personal photo mutation: %v found=%v", err, found)
	}
	close(inner.release)
	select {
	case result := <-olderResult:
		if result.err != nil || !result.found {
			t.Fatalf("older personal photo mutation: %v found=%v", result.err, result.found)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("timed out waiting for older personal photo mutation")
	}

	if calls := inner.staleReads(); calls != 0 {
		t.Fatalf("post-commit stale PersonalPhotos reads = %d, want 0", calls)
	}
	cached.mu.RLock()
	_, contactsWarm := cached.contacts[1]
	_, photosWarm := cached.personalPhotos[1]
	_, pairWarm := cached.projection[contactProjectionKey{viewerUserID: 1, contactUserID: 2}]
	cached.mu.RUnlock()
	if contactsWarm || photosWarm || pairWarm {
		t.Fatalf("older mutation reinserted stale cache state: contacts=%v photos=%v pair=%v", contactsWarm, photosWarm, pairWarm)
	}
	refs, err := cached.PersonalPhotos(ctx, 1, []int64{2})
	if err != nil {
		t.Fatalf("reload current personal photo: %v", err)
	}
	if got := refs[2].PhotoID; got != 9002 {
		t.Fatalf("personal photo after out-of-order completions = %d, want 9002", got)
	}
}