chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -2,6 +2,7 @@ package userprojection
import (
"context"
"reflect"
"testing"
privacyapp "telesrv/internal/app/privacy"
@ -113,6 +114,114 @@ func TestProjectorUsesFallbackWhenProfilePhotoHidden(t *testing.T) {
}
}
// TestForViewersEquivalentToForViewer 锁定 fan-out 模板化的核心安全网:ForViewers(viewers, users)
// 的每个 viewer 切片必须与逐 viewer 的 ForViewer(viewer, users) 字节等价(隐私/改名/头像投影
// 不能因 O(owner) 模板化而漂移泄漏)。**唯一允许的差异是 personal photo overlay**:v1 模板不做
// per-viewer personal photo,故对「该 viewer 给该 owner 设过 personal photo」的对,比较前 mask 掉
// 5 个头像字段;其余对做完整字节比较。覆盖:默认规则陌生人/联系人改名+电话/status 隐藏/profile
// 头像隐藏走 fallback/self/bot/系统账号/viewer 自身也作为 owner 出现。
func TestForViewersEquivalentToForViewer(t *testing.T) {
ctx := context.Background()
const (
v1 = int64(5001)
v2 = int64(5002)
o1 = int64(5101) // 陌生人,默认规则
o2 = int64(5102) // v1 的联系人(改名+电话),且 v1 给 o2 设了 personal photo
o3 = int64(5103) // status 隐藏
o4 = int64(5104) // profile photo 隐藏 → 走 fallback
bot = int64(5105)
)
viewers := []int64{v1, v2}
contacts := memory.NewContactStore()
rules := memory.NewPrivacyStore()
privacy := privacyapp.NewService(rules, contacts)
if _, err := contacts.Upsert(ctx, v1, domain.ContactInput{ContactUserID: o2, Phone: "1111", FirstName: "Alice", LastName: "Friend"}); err != nil {
t.Fatalf("upsert contact: %v", err)
}
// v1 给 o2 设 personal photo(仅 v1 视角生效 → ForViewer 会带它,ForViewers v1 跳过 → 该对需 mask)。
if _, _, err := contacts.SetPersonalPhoto(ctx, v1, o2, 9300, 300); err != nil {
t.Fatalf("set personal photo: %v", err)
}
if _, err := privacy.SetRules(ctx, o3, domain.PrivacyKeyStatusTimestamp, []domain.PrivacyRule{{Kind: domain.PrivacyRuleDisallowAll}}); err != nil {
t.Fatalf("set o3 status: %v", err)
}
if _, err := privacy.SetRules(ctx, o4, domain.PrivacyKeyProfilePhoto, []domain.PrivacyRule{{Kind: domain.PrivacyRuleDisallowAll}}); err != nil {
t.Fatalf("set o4 photo: %v", err)
}
projector := New(
WithContactStore(contacts),
WithPrivacyEvaluator(privacy),
WithPhotoProvider(fakeProfilePhotos{
profile: map[int64]domain.ProfilePhotoRef{
o1: {PhotoID: 9001, DCID: 1, Stripped: []byte{1}},
o2: {PhotoID: 9002, DCID: 2, Stripped: []byte{2}},
o3: {PhotoID: 9003, DCID: 3, Stripped: []byte{3}},
o4: {PhotoID: 9004, DCID: 4, Stripped: []byte{4}},
v1: {PhotoID: 9005, DCID: 5, Stripped: []byte{5}},
v2: {PhotoID: 9006, DCID: 6, Stripped: []byte{6}},
},
fallback: map[int64]domain.ProfilePhotoRef{
o4: {PhotoID: 9404, DCID: 4, Stripped: []byte{44}},
},
}),
)
users := []domain.User{
{ID: o1, AccessHash: 11, Phone: "15550000001", FirstName: "Stranger", Status: domain.UserStatus{Kind: domain.UserStatusOnline}},
{ID: o2, AccessHash: 12, Phone: "15550000002", FirstName: "PublicO2", Status: domain.UserStatus{Kind: domain.UserStatusOnline}},
{ID: o3, AccessHash: 13, Phone: "15550000003", FirstName: "O3", Status: domain.UserStatus{Kind: domain.UserStatusOnline}, LastSeenAt: 123},
{ID: o4, AccessHash: 14, Phone: "15550000004", FirstName: "O4"},
{ID: bot, AccessHash: 15, FirstName: "Bot", Bot: true},
{ID: domain.OfficialSystemUserID, FirstName: "System"},
{ID: v1, AccessHash: 16, Phone: "15550000016", FirstName: "Viewer1"}, // viewer 自身也作为 owner 出现
}
// 哪些 (viewer, owner) 对存在 personal photo —— 比较时需 mask 头像字段(v1 模板有意跳过)。
personalPairs := map[[2]int64]bool{{v1, o2}: true}
batch, err := projector.ForViewers(ctx, viewers, users)
if err != nil {
t.Fatalf("ForViewers: %v", err)
}
for _, viewer := range viewers {
want, err := projector.ForViewer(ctx, viewer, users)
if err != nil {
t.Fatalf("ForViewer(%d): %v", viewer, err)
}
got, ok := batch[viewer]
if !ok {
t.Fatalf("ForViewers missing viewer %d", viewer)
}
if len(got) != len(want) {
t.Fatalf("viewer %d len(got)=%d len(want)=%d", viewer, len(got), len(want))
}
for i := range want {
w, g := want[i], got[i]
if w.ID != g.ID {
t.Fatalf("viewer %d idx %d id mismatch got=%d want=%d", viewer, i, g.ID, w.ID)
}
if personalPairs[[2]int64{viewer, w.ID}] {
maskPhoto(&w)
maskPhoto(&g)
}
if !reflect.DeepEqual(w, g) {
t.Fatalf("viewer %d owner %d: ForViewers != ForViewer\n got=%+v\nwant=%+v", viewer, w.ID, g, w)
}
}
}
}
func maskPhoto(u *domain.User) {
u.PhotoID = 0
u.PhotoDCID = 0
u.PhotoStripped = nil
u.PhotoPersonal = false
u.PhotoHasVideo = false
}
func projectionUser(t *testing.T, users []domain.User, id int64) domain.User {
t.Helper()
for _, user := range users {