spark-server/internal/module/friend/logic/friend_test.go

287 lines
9.0 KiB
Go

package logic
import (
"context"
"errors"
"strconv"
"testing"
"spark-server/internal/infrastructure/id"
convlogic "spark-server/internal/module/conversation/logic"
convmodel "spark-server/internal/module/conversation/model"
delivermodel "spark-server/internal/module/deliver/model"
"spark-server/internal/module/friend/entity"
friendmodel "spark-server/internal/module/friend/model"
msgentity "spark-server/internal/module/message/entity"
msgmodel "spark-server/internal/module/message/model"
userentity "spark-server/internal/module/user/entity"
userlogic "spark-server/internal/module/user/logic"
usermodel "spark-server/internal/module/user/model"
"spark-server/internal/pkg/errs"
)
type captureGreeter struct {
n int
a, b int64
}
func (c *captureGreeter) BecameFriends(_ context.Context, a, b int64) error {
c.n++
c.a, c.b = a, b
return nil
}
func testFriends(t *testing.T) (*Service, *userlogic.Service, *captureGreeter) {
t.Helper()
ids, err := id.New(1)
if err != nil {
t.Fatal(err)
}
users := userlogic.NewService(usermodel.NewMemoryRepo(), usermodel.NewMemoryCodes(), ids, nil)
g := &captureGreeter{}
return NewService(friendmodel.NewMemoryRepo(), users, ids, g), users, g
}
func twoUsers(t *testing.T, users *userlogic.Service) (*userentity.User, *userentity.User) {
t.Helper()
ctx := t.Context()
a, err := users.AuthenticateEmail(ctx, "test01@spark.com", "1", &userentity.LoginDevice{DeviceID: 1, DeviceType: userentity.DeviceIOS})
if err != nil {
t.Fatal(err)
}
b, err := users.AuthenticateEmail(ctx, "test02@spark.com", "1", &userentity.LoginDevice{DeviceID: 2, DeviceType: userentity.DeviceWeb})
if err != nil {
t.Fatal(err)
}
return a, b
}
func TestApplyByEmailSparkIDQRAndDedupe(t *testing.T) {
svc, users, _ := testFriends(t)
ctx := t.Context()
a, b := twoUsers(t, users)
first, err := svc.Apply(ctx, a.ID, &ApplyInput{Email: "test02@spark.com", Reason: "hi", Remark: "work"})
if err != nil || first.Status != entity.RequestPending || first.RequestID == 0 {
t.Fatalf("email apply %+v %v", first, err)
}
second, err := svc.Apply(ctx, a.ID, &ApplyInput{SparkID: b.SparkID, Reason: "again"})
if err != nil {
t.Fatal(err)
}
if second.RequestID != first.RequestID {
t.Fatalf("dedupe %d %d", first.RequestID, second.RequestID)
}
third, err := svc.Apply(ctx, a.ID, &ApplyInput{QRCode: b.QRCode, Reason: "qr"})
if err != nil || third.RequestID != first.RequestID {
t.Fatalf("qr %+v %v", third, err)
}
list, err := svc.ListRequests(ctx, b.ID, 20)
if err != nil || len(list.Items) != 1 || list.Items[0].Reason != "qr" || list.Items[0].From.UID != a.ID {
t.Fatalf("inbox %+v %v", list, err)
}
}
func TestAcceptBecomesFriendsAndGreeting(t *testing.T) {
svc, users, g := testFriends(t)
ctx := t.Context()
a, b := twoUsers(t, users)
applied, err := svc.Apply(ctx, a.ID, &ApplyInput{Email: "test02@spark.com", Remark: "from-a"})
if err != nil {
t.Fatal(err)
}
got, err := svc.Accept(ctx, b.ID, AcceptInput{RequestID: applied.RequestID, Remark: "from-b"})
if err != nil || got.UID != a.ID {
t.Fatalf("accept %+v %v", got, err)
}
if g.n != 1 {
t.Fatalf("greeting %d", g.n)
}
if err := svc.CanSend(ctx, a.ID, b.ID); err != nil {
t.Fatal(err)
}
view, err := svc.Get(ctx, a.ID, b.ID)
if err != nil || view.Remark != "from-a" || view.Relation != entity.RelationFriend {
t.Fatalf("a view %+v %v", view, err)
}
view, err = svc.Get(ctx, b.ID, a.ID)
if err != nil || view.Remark != "from-b" {
t.Fatalf("b view %+v %v", view, err)
}
if _, err := svc.Get(ctx, a.ID, a.ID); !errors.Is(err, errs.ErrNotFound) {
t.Fatalf("self %v", err)
}
}
func TestMutualApplyAutoAccept(t *testing.T) {
svc, users, g := testFriends(t)
ctx := t.Context()
a, b := twoUsers(t, users)
if _, err := svc.Apply(ctx, a.ID, &ApplyInput{UID: b.ID, Remark: "a-note"}); err != nil {
t.Fatal(err)
}
out, err := svc.Apply(ctx, b.ID, &ApplyInput{Email: "test01@spark.com", Remark: "b-note"})
if err != nil || out.Status != entity.RequestAccepted {
t.Fatalf("%+v %v", out, err)
}
if g.n != 1 {
t.Fatalf("greeting %d", g.n)
}
if err := svc.CanSend(ctx, b.ID, a.ID); err != nil {
t.Fatal(err)
}
}
func TestNoUnidirectionalDeleteAndBlock(t *testing.T) {
svc, users, _ := testFriends(t)
ctx := t.Context()
a, b := twoUsers(t, users)
applied, _ := svc.Apply(ctx, a.ID, &ApplyInput{Email: "test02@spark.com"})
if _, err := svc.Accept(ctx, b.ID, AcceptInput{RequestID: applied.RequestID}); err != nil {
t.Fatal(err)
}
if err := svc.Delete(ctx, a.ID, b.ID); err != nil {
t.Fatal(err)
}
if err := svc.CanSend(ctx, b.ID, a.ID); !errors.Is(err, errs.ErrForbidden) {
t.Fatalf("after delete %v", err)
}
applied, _ = svc.Apply(ctx, a.ID, &ApplyInput{Email: "test02@spark.com"})
if _, err := svc.Accept(ctx, b.ID, AcceptInput{RequestID: applied.RequestID}); err != nil {
t.Fatal(err)
}
if err := svc.Block(ctx, a.ID, b.ID); err != nil {
t.Fatal(err)
}
ok, err := svc.AreFriends(ctx, a.ID, b.ID)
if err != nil || !ok {
t.Fatalf("block keeps friendship %v %v", ok, err)
}
view, err := svc.Get(ctx, a.ID, b.ID)
if err != nil || !view.Blocked || view.Relation != entity.RelationBlock {
t.Fatalf("blocked view %+v %v", view, err)
}
if err := svc.CanSend(ctx, a.ID, b.ID); !errors.Is(err, errs.ErrForbidden) {
t.Fatalf("blocked send %v", err)
}
if _, err := svc.Apply(ctx, a.ID, &ApplyInput{Email: "test02@spark.com"}); !errors.Is(err, errs.ErrForbidden) {
t.Fatalf("apply blocked %v", err)
}
if _, err := svc.Apply(ctx, b.ID, &ApplyInput{Email: "test01@spark.com"}); !errors.Is(err, errs.ErrNotFound) {
t.Fatalf("apply blocked-by %v", err)
}
}
func TestPinOrdersList(t *testing.T) {
svc, users, _ := testFriends(t)
ctx := t.Context()
a, b := twoUsers(t, users)
c, err := users.AuthenticateEmail(ctx, "test03@spark.com", "1", &userentity.LoginDevice{DeviceID: 3, DeviceType: userentity.DeviceIOS})
if err != nil {
t.Fatal(err)
}
for _, peer := range []int64{b.ID, c.ID} {
applied, err := svc.Apply(ctx, a.ID, &ApplyInput{UID: peer})
if err != nil {
t.Fatal(err)
}
if _, err := svc.Accept(ctx, peer, AcceptInput{RequestID: applied.RequestID}); err != nil {
t.Fatal(err)
}
}
if err := svc.Pin(ctx, a.ID, c.ID); err != nil {
t.Fatal(err)
}
list, err := svc.List(ctx, a.ID, 20)
if err != nil || len(list.Items) != 2 || list.Items[0].UID != c.ID || !list.Items[0].Pinned {
t.Fatalf("pinned first %+v %v", list, err)
}
if err := svc.Unpin(ctx, a.ID, c.ID); err != nil {
t.Fatal(err)
}
list, err = svc.List(ctx, a.ID, 20)
if err != nil || list.Items[0].Pinned {
t.Fatalf("unpinned %+v %v", list, err)
}
}
func TestRemarkReportLookup(t *testing.T) {
svc, users, _ := testFriends(t)
ctx := t.Context()
a, b := twoUsers(t, users)
look, err := svc.Lookup(ctx, a.ID, LookupInput{Q: b.SparkID})
if err != nil || look.UID != b.ID || look.Relation != entity.RelationNone {
t.Fatalf("lookup %+v %v", look, err)
}
applied, _ := svc.Apply(ctx, a.ID, &ApplyInput{SparkID: b.SparkID})
if _, err := svc.Accept(ctx, b.ID, AcceptInput{RequestID: applied.RequestID}); err != nil {
t.Fatal(err)
}
if err := svc.SetRemark(ctx, a.ID, b.ID, RemarkInput{Remark: "boss"}); err != nil {
t.Fatal(err)
}
view, err := svc.Get(ctx, a.ID, b.ID)
if err != nil || view.Remark != "boss" {
t.Fatalf("remark %+v %v", view, err)
}
if err := svc.Report(ctx, a.ID, b.ID, ReportInput{Reason: entity.ReportSpam, Detail: "ads"}); err != nil {
t.Fatal(err)
}
if _, err := svc.Apply(ctx, a.ID, &ApplyInput{Email: "test02@spark.com"}); !errors.Is(err, errs.ErrDuplicate) {
t.Fatalf("already %v", err)
}
}
func TestLookupQueryClassifies(t *testing.T) {
svc, users, _ := testFriends(t)
ctx := t.Context()
a, b := twoUsers(t, users)
queries := []string{
"test02@spark.com",
strconv.FormatInt(b.ID, 10),
b.SparkID,
b.QRCode,
}
for _, q := range queries {
look, err := svc.Lookup(ctx, a.ID, LookupInput{Q: q})
if err != nil || look.UID != b.ID {
t.Fatalf("q=%q %+v %v", q, look, err)
}
}
if _, err := svc.Lookup(ctx, a.ID, LookupInput{Q: ""}); !errors.Is(err, errs.ErrInvalidArgument) {
t.Fatalf("empty %v", err)
}
if _, err := svc.Lookup(ctx, a.ID, LookupInput{Q: "nobody_xx"}); !errors.Is(err, errs.ErrNotFound) {
t.Fatalf("missing %v", err)
}
}
func TestSystemGreeterWritesMessage(t *testing.T) {
ids, err := id.New(1)
if err != nil {
t.Fatal(err)
}
msgs := msgmodel.NewMemoryRepo()
chats := convlogic.NewP2PService(convmodel.NewMemoryP2P(), ids)
g := NewGreeter(chats, msgs, ids, delivermodel.NewMemoryQueue())
if err := g.BecameFriends(t.Context(), 11, 22); err != nil {
t.Fatal(err)
}
convID, err := chats.EnsureP2P(t.Context(), 11, 22)
if err != nil || convID == 0 {
t.Fatalf("conv %d %v", convID, err)
}
found := false
for _, msg := range msgs.All() {
if msg.ConversationID == convID && msg.Type == msgentity.TypeSystem {
found = true
payload, err := msg.Payload()
if err != nil || payload.System == nil || payload.System.Kind != entity.SystemKindBecameFriends {
t.Fatalf("payload %+v %v", payload, err)
}
}
}
if !found {
t.Fatal("missing system message")
}
}