owpengram-server/internal/store/postgres/contact_sparse_integration_test.go
2026-09-01 12:06:31 +03:00

157 lines
6 KiB
Go

package postgres
import (
"context"
"reflect"
"testing"
"time"
"telesrv/internal/domain"
)
func TestContactProjectionForViewerUserIDsPostgresDoesNotCrossPairs(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
users := NewUserStore(pool)
viewerA := createTestUser(t, ctx, users, "+1910"+suffix+"01", "Viewer", "A")
viewerB := createTestUser(t, ctx, users, "+1910"+suffix+"02", "Viewer", "B")
ownerA := createTestUser(t, ctx, users, "+1910"+suffix+"03", "Owner", "A")
ownerB := createTestUser(t, ctx, users, "+1910"+suffix+"04", "Owner", "B")
userIDs := []int64{viewerA.ID, viewerB.ID, ownerA.ID, ownerB.ID}
photoBase := time.Now().UnixNano() & 0x3fffffffffffffff
photoIDs := []int64{photoBase + 1, photoBase + 2, photoBase + 3, photoBase + 4}
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", userIDs)
_, _ = pool.Exec(ctx, "DELETE FROM photos WHERE id = ANY($1::bigint[])", photoIDs)
})
media := NewMediaStore(pool)
for _, photoID := range photoIDs {
if err := media.PutPhoto(ctx, domain.Photo{
ID: photoID, AccessHash: photoID + 100, FileReference: []byte("sparse-ref"), Date: 1700000000, DCID: 2,
Sizes: []domain.PhotoSize{{Kind: domain.PhotoSizeKindStripped, Type: "i", Bytes: []byte{1, 2, byte(photoID)}}},
}); err != nil {
t.Fatalf("PutPhoto(%d): %v", photoID, err)
}
}
contacts := NewContactStore(pool)
rows := []struct {
viewer int64
owner int64
name string
photo int64
}{
{viewerA.ID, ownerA.ID, "A expected", photoIDs[0]},
{viewerA.ID, ownerB.ID, "B cross", photoIDs[1]},
{viewerB.ID, ownerA.ID, "A cross", photoIDs[2]},
{viewerB.ID, ownerB.ID, "B expected", photoIDs[3]},
}
for _, row := range rows {
if _, err := contacts.Upsert(ctx, row.viewer, domain.ContactInput{
ContactUserID: row.owner,
FirstName: row.name,
Phone: "known-phone",
Note: "private note",
NoteEntities: []domain.MessageEntity{{
Type: domain.MessageEntityBold, Length: 7,
}},
}); err != nil {
t.Fatalf("Upsert %d->%d: %v", row.viewer, row.owner, err)
}
if _, found, err := contacts.SetPersonalPhoto(ctx, row.viewer, row.owner, row.photo, 1700000001); err != nil || !found {
t.Fatalf("SetPersonalPhoto %d->%d: found=%v err=%v", row.viewer, row.owner, found, err)
}
}
got, err := contacts.ContactProjectionForViewerUserIDs(ctx, map[int64][]int64{
viewerA.ID: {ownerA.ID},
viewerB.ID: {ownerB.ID},
})
if err != nil {
t.Fatalf("ContactProjectionForViewerUserIDs: %v", err)
}
if len(got.Contacts[viewerA.ID]) != 1 || got.Contacts[viewerA.ID][ownerA.ID].FirstName != "A expected" {
t.Fatalf("viewer A contacts = %+v", got.Contacts[viewerA.ID])
}
contactA := got.Contacts[viewerA.ID][ownerA.ID]
if !reflect.DeepEqual(contactA.User, domain.User{ID: ownerA.ID}) {
t.Fatalf("viewer A sparse projection retained joined base user data: %+v", contactA.User)
}
if contactA.Phone != "known-phone" || contactA.Note != "private note" || len(contactA.NoteEntities) != 1 || contactA.NoteEntities[0].Length != 7 {
t.Fatalf("viewer A sparse overlay = %+v", contactA)
}
if len(got.Contacts[viewerB.ID]) != 1 || got.Contacts[viewerB.ID][ownerB.ID].FirstName != "B expected" {
t.Fatalf("viewer B contacts = %+v", got.Contacts[viewerB.ID])
}
if _, ok := got.Contacts[viewerA.ID][ownerB.ID]; ok {
t.Fatal("viewer A received crossed owner B")
}
if _, ok := got.Contacts[viewerB.ID][ownerA.ID]; ok {
t.Fatal("viewer B received crossed owner A")
}
if got.PersonalPhotos[viewerA.ID][ownerA.ID].PhotoID != photoIDs[0] || len(got.PersonalPhotos[viewerA.ID]) != 1 {
t.Fatalf("viewer A personal photos = %+v", got.PersonalPhotos[viewerA.ID])
}
if got.PersonalPhotos[viewerB.ID][ownerB.ID].PhotoID != photoIDs[3] || len(got.PersonalPhotos[viewerB.ID]) != 1 {
t.Fatalf("viewer B personal photos = %+v", got.PersonalPhotos[viewerB.ID])
}
}
func TestGetReverseContactsForViewerUserIDsPostgresDoesNotCrossPairs(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
users := NewUserStore(pool)
viewerA := createTestUser(t, ctx, users, "+1911"+suffix+"01", "Viewer", "A")
viewerB := createTestUser(t, ctx, users, "+1911"+suffix+"02", "Viewer", "B")
ownerA := createTestUser(t, ctx, users, "+1911"+suffix+"03", "Owner", "A")
ownerB := createTestUser(t, ctx, users, "+1911"+suffix+"04", "Owner", "B")
userIDs := []int64{viewerA.ID, viewerB.ID, ownerA.ID, ownerB.ID}
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", userIDs)
})
contacts := NewContactStore(pool)
for _, row := range []struct {
owner int64
viewer int64
name string
}{
{ownerA.ID, viewerA.ID, "A expected"},
{ownerA.ID, viewerB.ID, "A cross"},
{ownerB.ID, viewerA.ID, "B cross"},
{ownerB.ID, viewerB.ID, "B expected"},
} {
if _, err := contacts.Upsert(ctx, row.owner, domain.ContactInput{
ContactUserID: row.viewer,
FirstName: row.name,
Note: "relationship-only",
}); err != nil {
t.Fatalf("Upsert %d->%d: %v", row.owner, row.viewer, err)
}
}
if _, err := contacts.SetCloseFriends(ctx, ownerA.ID, []int64{viewerA.ID}); err != nil {
t.Fatal(err)
}
got, err := contacts.GetReverseContactsForViewerUserIDs(ctx, map[int64][]int64{
ownerA.ID: {viewerA.ID},
ownerB.ID: {viewerB.ID},
})
if err != nil {
t.Fatalf("GetReverseContactsForViewerUserIDs: %v", err)
}
contactA, found := got[ownerA.ID][viewerA.ID]
if !found || contactA.User.ID != viewerA.ID || contactA.FirstName != "A expected" || !contactA.CloseFriend {
t.Fatalf("owner A exact relationship = %+v found=%v", contactA, found)
}
contactB, found := got[ownerB.ID][viewerB.ID]
if !found || contactB.User.ID != viewerB.ID || contactB.FirstName != "B expected" || contactB.CloseFriend {
t.Fatalf("owner B exact relationship = %+v found=%v", contactB, found)
}
if _, found := got[ownerA.ID][viewerB.ID]; found {
t.Fatal("owner A received crossed viewer B")
}
if _, found := got[ownerB.ID][viewerA.ID]; found {
t.Fatal("owner B received crossed viewer A")
}
}