218 lines
7.7 KiB
Go
218 lines
7.7 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
func TestModerationFlagsRejectImpossibleStateAtPostgresBoundary(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
user := createTestUser(t, ctx, users, "+1781"+suffix+"71", "ModerationFlags", "")
|
|
|
|
if _, err := users.SetScamFake(ctx, user.ID, true, true); !errors.Is(err, domain.ErrPeerModerationFlagsInvalid) {
|
|
t.Fatalf("user store error=%v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `UPDATE users SET scam=true,fake=true WHERE id=$1`, user.ID); err == nil {
|
|
t.Fatal("users CHECK constraint accepted scam=true,fake=true")
|
|
}
|
|
|
|
channels := NewChannelStore(pool)
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: user.ID,
|
|
Title: "Moderation " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700002000,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
if _, err := channels.SetChannelScamFake(ctx, created.Channel.ID, true, true); !errors.Is(err, domain.ErrPeerModerationFlagsInvalid) {
|
|
t.Fatalf("channel store error=%v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `UPDATE channels SET scam=true,fake=true WHERE id=$1`, created.Channel.ID); err == nil {
|
|
t.Fatal("channels CHECK constraint accepted scam=true,fake=true")
|
|
}
|
|
|
|
gotUser, found, err := users.ByID(ctx, user.ID)
|
|
if err != nil || !found || gotUser.Scam || gotUser.Fake {
|
|
t.Fatalf("user after rejected writes=%+v found=%v err=%v", gotUser, found, err)
|
|
}
|
|
gotChannel, err := channels.GetChannelByID(ctx, created.Channel.ID)
|
|
if err != nil || gotChannel.Scam || gotChannel.Fake {
|
|
t.Fatalf("channel after rejected writes=%+v err=%v", gotChannel, err)
|
|
}
|
|
}
|
|
|
|
func TestUserModerationFlagsDoNotAdvanceAccountPts(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
contacts := NewContactStore(pool)
|
|
events := NewUpdateEventStore(pool)
|
|
|
|
target := createTestUser(t, ctx, users, "+1782"+suffix+"71", "FlagTarget", "")
|
|
savedTargetViewer := createTestUser(t, ctx, users, "+1782"+suffix+"72", "SavedTarget", "")
|
|
savedByTargetViewer := createTestUser(t, ctx, users, "+1782"+suffix+"73", "SavedByTarget", "")
|
|
unrelated := createTestUser(t, ctx, users, "+1782"+suffix+"74", "Unrelated", "")
|
|
|
|
if _, err := contacts.Upsert(ctx, savedTargetViewer.ID, domain.ContactInput{
|
|
ContactUserID: target.ID, FirstName: target.FirstName,
|
|
}); err != nil {
|
|
t.Fatalf("save target contact: %v", err)
|
|
}
|
|
if _, err := contacts.Upsert(ctx, target.ID, domain.ContactInput{
|
|
ContactUserID: savedByTargetViewer.ID, FirstName: savedByTargetViewer.FirstName,
|
|
}); err != nil {
|
|
t.Fatalf("save reverse contact: %v", err)
|
|
}
|
|
viewers := []domain.User{target, savedTargetViewer, savedByTargetViewer, unrelated}
|
|
baseline := make(map[int64]int, len(viewers))
|
|
for _, viewer := range viewers {
|
|
pts, err := events.MaxContiguousPts(ctx, viewer.ID)
|
|
if err != nil {
|
|
t.Fatalf("viewer %d baseline pts: %v", viewer.ID, err)
|
|
}
|
|
baseline[viewer.ID] = pts
|
|
}
|
|
|
|
updated, err := users.SetScamFake(ctx, target.ID, true, false)
|
|
if err != nil {
|
|
t.Fatalf("set scam: %v", err)
|
|
}
|
|
if !updated.Scam || updated.Fake {
|
|
t.Fatalf("updated flags = scam:%v fake:%v", updated.Scam, updated.Fake)
|
|
}
|
|
|
|
for _, viewer := range viewers {
|
|
pts, err := events.MaxContiguousPts(ctx, viewer.ID)
|
|
if err != nil || pts != baseline[viewer.ID] {
|
|
t.Fatalf("viewer %d pts=%d want=%d err=%v", viewer.ID, pts, baseline[viewer.ID], err)
|
|
}
|
|
got, err := events.ListAfter(ctx, viewer.ID, baseline[viewer.ID], 10)
|
|
if err != nil || len(got) != 0 {
|
|
t.Fatalf("viewer %d moderation events=%+v err=%v", viewer.ID, got, err)
|
|
}
|
|
}
|
|
var outboxCount int
|
|
if err := pool.QueryRow(ctx, `
|
|
SELECT count(*)
|
|
FROM dispatch_outbox
|
|
WHERE target_user_id = ANY($1::bigint[])
|
|
AND event_type = 'user_profile'`,
|
|
[]int64{target.ID, savedTargetViewer.ID, savedByTargetViewer.ID, unrelated.ID},
|
|
).Scan(&outboxCount); err != nil || outboxCount != 0 {
|
|
t.Fatalf("profile outbox count=%d err=%v", outboxCount, err)
|
|
}
|
|
|
|
audience, err := users.ModerationFlagAudience(ctx, target.ID, 4096)
|
|
if err != nil {
|
|
t.Fatalf("moderation audience: %v", err)
|
|
}
|
|
audienceSet := make(map[int64]struct{}, len(audience))
|
|
for _, userID := range audience {
|
|
audienceSet[userID] = struct{}{}
|
|
}
|
|
for _, viewer := range []domain.User{target, savedTargetViewer, savedByTargetViewer} {
|
|
if _, ok := audienceSet[viewer.ID]; !ok {
|
|
t.Fatalf("viewer %d missing from audience %v", viewer.ID, audience)
|
|
}
|
|
}
|
|
if _, ok := audienceSet[unrelated.ID]; ok {
|
|
t.Fatalf("unrelated viewer included in audience %v", audience)
|
|
}
|
|
|
|
if _, err := users.SetScamFake(ctx, target.ID, true, false); err != nil {
|
|
t.Fatalf("repeat same flags: %v", err)
|
|
}
|
|
if _, err := users.SetScamFake(ctx, target.ID, false, true); err != nil {
|
|
t.Fatalf("switch to fake: %v", err)
|
|
}
|
|
for _, viewer := range viewers {
|
|
pts, err := events.MaxContiguousPts(ctx, viewer.ID)
|
|
if err != nil || pts != baseline[viewer.ID] {
|
|
t.Fatalf("viewer %d final pts=%d want=%d err=%v", viewer.ID, pts, baseline[viewer.ID], err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChannelModerationFlagsDoNotAdvanceMemberAccountPts(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
channels := NewChannelStore(pool)
|
|
events := NewUpdateEventStore(pool)
|
|
|
|
owner := createTestUser(t, ctx, users, "+1783"+suffix+"71", "FlagOwner", "")
|
|
member := createTestUser(t, ctx, users, "+1783"+suffix+"72", "FlagMember", "")
|
|
unrelated := createTestUser(t, ctx, users, "+1783"+suffix+"73", "FlagUnrelated", "")
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
MemberUserIDs: []int64{member.ID},
|
|
Title: "Flagged Channel " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700003000,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
|
|
baseline := make(map[int64]int)
|
|
for _, viewer := range []domain.User{owner, member, unrelated} {
|
|
pts, err := events.MaxContiguousPts(ctx, viewer.ID)
|
|
if err != nil {
|
|
t.Fatalf("viewer %d baseline pts: %v", viewer.ID, err)
|
|
}
|
|
baseline[viewer.ID] = pts
|
|
}
|
|
|
|
updated, err := channels.SetChannelScamFake(ctx, created.Channel.ID, true, false)
|
|
if err != nil {
|
|
t.Fatalf("set channel scam: %v", err)
|
|
}
|
|
if !updated.Scam || updated.Fake {
|
|
t.Fatalf("updated flags = scam:%v fake:%v", updated.Scam, updated.Fake)
|
|
}
|
|
for _, viewer := range []domain.User{owner, member, unrelated} {
|
|
pts, err := events.MaxContiguousPts(ctx, viewer.ID)
|
|
if err != nil || pts != baseline[viewer.ID] {
|
|
t.Fatalf("viewer %d pts=%d want=%d err=%v", viewer.ID, pts, baseline[viewer.ID], err)
|
|
}
|
|
got, err := events.ListAfter(ctx, viewer.ID, baseline[viewer.ID], 10)
|
|
if err != nil || len(got) != 0 {
|
|
t.Fatalf("viewer %d moderation events=%+v err=%v", viewer.ID, got, err)
|
|
}
|
|
}
|
|
|
|
var outboxCount int
|
|
if err := pool.QueryRow(ctx, `
|
|
SELECT count(*)
|
|
FROM dispatch_outbox
|
|
WHERE target_user_id = ANY($1::bigint[])
|
|
AND event_type = 'channel_state'
|
|
AND pts > 0`,
|
|
[]int64{owner.ID, member.ID, unrelated.ID},
|
|
).Scan(&outboxCount); err != nil || outboxCount != 0 {
|
|
t.Fatalf("channel state outbox count=%d err=%v", outboxCount, err)
|
|
}
|
|
|
|
if _, err := channels.SetChannelScamFake(ctx, created.Channel.ID, true, false); err != nil {
|
|
t.Fatalf("repeat same channel flags: %v", err)
|
|
}
|
|
if _, err := channels.SetChannelScamFake(ctx, created.Channel.ID, false, true); err != nil {
|
|
t.Fatalf("switch channel to fake: %v", err)
|
|
}
|
|
for _, viewer := range []domain.User{owner, member, unrelated} {
|
|
pts, err := events.MaxContiguousPts(ctx, viewer.ID)
|
|
if err != nil || pts != baseline[viewer.ID] {
|
|
t.Fatalf("viewer %d final pts=%d want=%d err=%v", viewer.ID, pts, baseline[viewer.ID], err)
|
|
}
|
|
}
|
|
}
|