owpengram-server/internal/rpc/peer_identity_cache_test.go
2026-09-01 12:06:31 +03:00

165 lines
7.7 KiB
Go

package rpc
import (
"context"
"errors"
"testing"
"telesrv/internal/domain"
"telesrv/internal/store"
)
func peerIdentityVersionKey(peer domain.Peer) store.ReadModelKey {
return store.ReadModelKey{Model: peerIdentityReadModel, PeerType: peer.Type, PeerID: peer.ID}
}
func TestPeerUsernameIdentityCacheVersionsPositiveAndNegativeResults(t *testing.T) {
positive := domain.Peer{Type: domain.PeerTypeUser, ID: 11}
negative := domain.Peer{Type: domain.PeerTypeChannel, ID: 22}
versions := &fakeRPCReadModelVersions{hashes: map[store.ReadModelKey]int64{
peerIdentityVersionKey(positive): 101,
peerIdentityVersionKey(negative): 202,
}}
registry := newFakeUsernameRegistry()
registry.byPeer[positive] = []domain.Username{{Username: "first", Active: true, CollectibleID: 1}}
r := &Router{
deps: Deps{Usernames: registry, ReadModelVersions: versions},
peerIdentityCache: newPeerIdentityCache(8),
}
first := r.usernameRegistryMap(context.Background(), []domain.Peer{positive, negative})
if registry.batchCalls != 1 || len(first[positive]) != 1 {
t.Fatalf("first load calls=%d result=%+v", registry.batchCalls, first)
}
first[positive][0].Username = "caller-mutated"
second := r.usernameRegistryMap(context.Background(), []domain.Peer{positive, negative})
if registry.batchCalls != 1 {
t.Fatalf("positive/negative cache miss: batch calls=%d, want 1", registry.batchCalls)
}
if got := second[positive][0].Username; got != "first" {
t.Fatalf("cached username aliased caller mutation: got %q", got)
}
if _, ok := second[negative]; ok {
t.Fatalf("negative peer unexpectedly projected: %+v", second[negative])
}
registry.byPeer[positive] = []domain.Username{{Username: "second", Active: true, CollectibleID: 2}}
versions.hashes[peerIdentityVersionKey(positive)] = 303
third := r.usernameRegistryMap(context.Background(), []domain.Peer{positive, negative})
if registry.batchCalls != 1 || registry.peerCalls != 1 || len(third[positive]) != 1 || third[positive][0].Username != "second" {
t.Fatalf("version advance did not reload: batch=%d peer=%d result=%+v", registry.batchCalls, registry.peerCalls, third)
}
}
func TestPeerVerificationIdentityCacheVersionsPositiveAndNegativeResults(t *testing.T) {
positive := domain.Peer{Type: domain.PeerTypeChannel, ID: 31}
negative := domain.Peer{Type: domain.PeerTypeUser, ID: 32}
versions := &fakeRPCReadModelVersions{hashes: map[store.ReadModelKey]int64{
peerIdentityVersionKey(positive): 401,
peerIdentityVersionKey(negative): 402,
}}
verifications := newFakeBotVerifications()
verifications.marks[positive] = domain.CustomVerification{Peer: positive, IconDocumentID: 9001}
r := &Router{
deps: Deps{BotVerifications: verifications, ReadModelVersions: versions},
peerIdentityCache: newPeerIdentityCache(8),
}
first := r.botVerificationMap(context.Background(), []domain.Peer{positive, negative})
if verifications.batchCalls != 1 || first[positive].IconDocumentID != 9001 {
t.Fatalf("first load calls=%d result=%+v", verifications.batchCalls, first)
}
second := r.botVerificationMap(context.Background(), []domain.Peer{positive, negative})
if verifications.batchCalls != 1 || len(second) != 1 {
t.Fatalf("positive/negative cache miss: calls=%d result=%+v", verifications.batchCalls, second)
}
delete(verifications.marks, positive)
versions.hashes[peerIdentityVersionKey(positive)] = 403
third := r.botVerificationMap(context.Background(), []domain.Peer{positive, negative})
if verifications.batchCalls != 1 || verifications.peerCalls != 1 || len(third) != 0 {
t.Fatalf("version advance did not reload removal: batch=%d peer=%d result=%+v", verifications.batchCalls, verifications.peerCalls, third)
}
}
func TestPeerIdentityCachesDoNotTurnReadErrorsIntoNegativeEntries(t *testing.T) {
peer := domain.Peer{Type: domain.PeerTypeUser, ID: 51}
versions := &fakeRPCReadModelVersions{hashes: map[store.ReadModelKey]int64{
peerIdentityVersionKey(peer): 501,
}}
registry := newFakeUsernameRegistry()
registry.err = errors.New("username backend unavailable")
verifications := newFakeBotVerifications()
verifications.err = errors.New("verification backend unavailable")
r := &Router{
deps: Deps{
Usernames: registry,
BotVerifications: verifications,
ReadModelVersions: versions,
},
peerIdentityCache: newPeerIdentityCache(8),
}
if got := r.usernameRegistryMap(context.Background(), []domain.Peer{peer}); len(got) != 0 {
t.Fatalf("username error result = %+v, want empty overlay", got)
}
if got := r.botVerificationMap(context.Background(), []domain.Peer{peer}); len(got) != 0 {
t.Fatalf("verification error result = %+v, want empty overlay", got)
}
registry.err = nil
registry.byPeer[peer] = []domain.Username{{Username: "recovered", Active: true, CollectibleID: 7}}
verifications.err = nil
verifications.marks[peer] = domain.CustomVerification{Peer: peer, IconDocumentID: 9007}
if got := r.usernameRegistryMap(context.Background(), []domain.Peer{peer}); len(got[peer]) != 1 || got[peer][0].Username != "recovered" {
t.Fatalf("username backend recovery hidden by negative cache: %+v", got)
}
if got := r.botVerificationMap(context.Background(), []domain.Peer{peer}); got[peer].IconDocumentID != 9007 {
t.Fatalf("verification backend recovery hidden by negative cache: %+v", got)
}
if registry.peerCalls != 3 || verifications.peerCalls != 3 {
t.Fatalf("backend recovery did not retry: usernames=%d verifications=%d", registry.peerCalls, verifications.peerCalls)
}
}
func TestPeerIdentityCacheCombinesFacetsAndIgnoresBaseProjectionInvalidation(t *testing.T) {
userPeer := domain.Peer{Type: domain.PeerTypeUser, ID: 61}
channelPeer := domain.Peer{Type: domain.PeerTypeChannel, ID: 62}
peers := []domain.Peer{userPeer, channelPeer}
versions := &fakeRPCReadModelVersions{hashes: map[store.ReadModelKey]int64{
peerIdentityVersionKey(userPeer): 601,
peerIdentityVersionKey(channelPeer): 602,
}}
registry := newFakeUsernameRegistry()
registry.byPeer[userPeer] = []domain.Username{{Username: "stable", Active: true}}
verifications := newFakeBotVerifications()
verifications.marks[channelPeer] = domain.CustomVerification{Peer: channelPeer, IconDocumentID: 9062}
r := &Router{
deps: Deps{
Usernames: registry,
BotVerifications: verifications,
ReadModelVersions: versions,
},
peerIdentityCache: newPeerIdentityCache(8),
}
usernames, marks := r.peerIdentityMaps(context.Background(), peers, true, true)
if registry.batchCalls != 1 || verifications.batchCalls != 1 || usernames[userPeer][0].Username != "stable" || marks[channelPeer].IconDocumentID != 9062 {
t.Fatalf("combined load registry=%d verification=%d usernames=%+v marks=%+v", registry.batchCalls, verifications.batchCalls, usernames, marks)
}
registry.byPeer[userPeer] = []domain.Username{{Username: "must-not-leak", Active: true}}
delete(verifications.marks, channelPeer)
r.InvalidateRPCProjectionReadModelForUser(userPeer.ID)
r.InvalidateRPCProjectionReadModelForChannel(channelPeer.ID)
usernames, marks = r.peerIdentityMaps(context.Background(), peers, true, true)
if registry.batchCalls != 1 || verifications.batchCalls != 1 || usernames[userPeer][0].Username != "stable" || marks[channelPeer].IconDocumentID != 9062 {
t.Fatalf("base invalidation evicted identity registry=%d verification=%d usernames=%+v marks=%+v", registry.batchCalls, verifications.batchCalls, usernames, marks)
}
versions.hashes[peerIdentityVersionKey(userPeer)] = 603
r.InvalidatePeerIdentityReadModel(userPeer)
usernames = r.usernameRegistryMap(context.Background(), peers)
if registry.peerCalls != 1 || usernames[userPeer][0].Username != "must-not-leak" {
t.Fatalf("exact identity invalidation did not reload user: peerCalls=%d usernames=%+v", registry.peerCalls, usernames)
}
}