145 lines
5.1 KiB
Go
145 lines
5.1 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
appreadmodel "telesrv/internal/app/readmodel"
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store"
|
|
)
|
|
|
|
type fakeStorySparseProjectionProvider struct {
|
|
expirations map[domain.Peer]int
|
|
hidden map[int64][]domain.Peer
|
|
activeCalls int
|
|
hiddenCalls int
|
|
err error
|
|
}
|
|
|
|
func (f *fakeStorySparseProjectionProvider) ActiveStoryPeerExpirations(_ context.Context, peers []domain.Peer, _ int) (map[domain.Peer]int, error) {
|
|
f.activeCalls++
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
out := make(map[domain.Peer]int, len(peers))
|
|
for _, peer := range peers {
|
|
if expireAt := f.expirations[peer]; expireAt != 0 {
|
|
out[peer] = expireAt
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (f *fakeStorySparseProjectionProvider) ListHiddenStoryPeers(_ context.Context, viewerUserID int64) ([]domain.Peer, error) {
|
|
f.hiddenCalls++
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
return append([]domain.Peer(nil), f.hidden[viewerUserID]...), nil
|
|
}
|
|
|
|
func storyPeerVersionKey(peer domain.Peer) store.ReadModelKey {
|
|
return store.ReadModelKey{Model: appreadmodel.ModelStoryPeer, PeerType: peer.Type, PeerID: peer.ID}
|
|
}
|
|
|
|
func storyHiddenListVersionKey(viewerUserID int64) store.ReadModelKey {
|
|
return store.ReadModelKey{
|
|
Model: appreadmodel.ModelStoryHiddenList, OwnerUserID: viewerUserID,
|
|
PeerType: domain.PeerTypeUser, PeerID: viewerUserID,
|
|
}
|
|
}
|
|
|
|
func TestStorySparseProjectionCacheSharesNegativeCandidatesAndExpiresPositiveFacts(t *testing.T) {
|
|
ctx := context.Background()
|
|
active := domain.Peer{Type: domain.PeerTypeUser, ID: 11}
|
|
inactive := domain.Peer{Type: domain.PeerTypeChannel, ID: 22}
|
|
versions := &fakeRPCReadModelVersions{hashes: map[store.ReadModelKey]int64{
|
|
storyPeerVersionKey(active): 101,
|
|
storyPeerVersionKey(inactive): 102,
|
|
}}
|
|
provider := &fakeStorySparseProjectionProvider{expirations: map[domain.Peer]int{active: 200}}
|
|
cache := newStorySparseProjectionCache(versions, 10, 10, 1024)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
got, err := cache.activePeers(ctx, provider, []domain.Peer{active, inactive, active}, 100)
|
|
if err != nil || len(got) != 1 || got[0] != active {
|
|
t.Fatalf("activePeers(%d) = %+v, %v", i, got, err)
|
|
}
|
|
}
|
|
if provider.activeCalls != 1 {
|
|
t.Fatalf("shared positive/negative candidate loads = %d, want 1", provider.activeCalls)
|
|
}
|
|
|
|
delete(provider.expirations, active)
|
|
got, err := cache.activePeers(ctx, provider, []domain.Peer{active, inactive}, 200)
|
|
if err != nil || len(got) != 0 {
|
|
t.Fatalf("activePeers at expire boundary = %+v, %v", got, err)
|
|
}
|
|
if provider.activeCalls != 2 {
|
|
t.Fatalf("expired positive candidate loads = %d, want 2", provider.activeCalls)
|
|
}
|
|
}
|
|
|
|
func TestStorySparseProjectionCacheVersionsHiddenViewerSnapshot(t *testing.T) {
|
|
ctx := context.Background()
|
|
const viewerID int64 = 77
|
|
hiddenPeer := domain.Peer{Type: domain.PeerTypeUser, ID: 88}
|
|
key := storyHiddenListVersionKey(viewerID)
|
|
versions := &fakeRPCReadModelVersions{hashes: map[store.ReadModelKey]int64{key: 201}}
|
|
provider := &fakeStorySparseProjectionProvider{hidden: map[int64][]domain.Peer{viewerID: {hiddenPeer}}}
|
|
cache := newStorySparseProjectionCache(versions, 10, 10, 1024)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
got, err := cache.hiddenPeers(ctx, provider, viewerID)
|
|
if err != nil {
|
|
t.Fatalf("hiddenPeers(%d): %v", i, err)
|
|
}
|
|
if _, ok := got[hiddenPeer]; !ok {
|
|
t.Fatalf("hiddenPeers(%d) = %+v, want hidden peer", i, got)
|
|
}
|
|
}
|
|
if provider.hiddenCalls != 1 {
|
|
t.Fatalf("hidden snapshot loads = %d, want 1", provider.hiddenCalls)
|
|
}
|
|
|
|
provider.hidden[viewerID] = nil
|
|
versions.hashes[key] = 202
|
|
got, err := cache.hiddenPeers(ctx, provider, viewerID)
|
|
if err != nil || len(got) != 0 {
|
|
t.Fatalf("hiddenPeers after version bump = %+v, %v", got, err)
|
|
}
|
|
if provider.hiddenCalls != 2 {
|
|
t.Fatalf("hidden snapshot loads after version bump = %d, want 2", provider.hiddenCalls)
|
|
}
|
|
}
|
|
|
|
func TestStorySparseProjectionCacheDoesNotCacheBackendErrors(t *testing.T) {
|
|
ctx := context.Background()
|
|
peer := domain.Peer{Type: domain.PeerTypeUser, ID: 99}
|
|
const viewerID int64 = 100
|
|
versions := &fakeRPCReadModelVersions{hashes: map[store.ReadModelKey]int64{
|
|
storyPeerVersionKey(peer): 301,
|
|
storyHiddenListVersionKey(viewerID): 302,
|
|
}}
|
|
provider := &fakeStorySparseProjectionProvider{expirations: map[domain.Peer]int{peer: 500}, err: errors.New("backend unavailable")}
|
|
cache := newStorySparseProjectionCache(versions, 10, 10, 1024)
|
|
|
|
if _, err := cache.activePeers(ctx, provider, []domain.Peer{peer}, 100); err == nil {
|
|
t.Fatal("active candidate error = nil")
|
|
}
|
|
if _, err := cache.hiddenPeers(ctx, provider, viewerID); err == nil {
|
|
t.Fatal("hidden snapshot error = nil")
|
|
}
|
|
provider.err = nil
|
|
if got, err := cache.activePeers(ctx, provider, []domain.Peer{peer}, 100); err != nil || len(got) != 1 {
|
|
t.Fatalf("active candidate recovery = %+v, %v", got, err)
|
|
}
|
|
if got, err := cache.hiddenPeers(ctx, provider, viewerID); err != nil || len(got) != 0 {
|
|
t.Fatalf("hidden snapshot recovery = %+v, %v", got, err)
|
|
}
|
|
if provider.activeCalls != 2 || provider.hiddenCalls != 2 {
|
|
t.Fatalf("backend retries active=%d hidden=%d, want 2/2", provider.activeCalls, provider.hiddenCalls)
|
|
}
|
|
}
|