package logic import ( "errors" "testing" "spark-server/internal/infrastructure/id" convlogic "spark-server/internal/module/conversation/logic" convmodel "spark-server/internal/module/conversation/model" "spark-server/internal/module/friend/entity" friendmodel "spark-server/internal/module/friend/model" msgmodel "spark-server/internal/module/message/model" userentity "spark-server/internal/module/user/entity" "spark-server/internal/module/user/logictest" "spark-server/internal/pkg/errs" ) func TestOneWayFollowDoesNotFriend(t *testing.T) { svc, users, g := testFriends(t) ctx := t.Context() a, b := twoUsers(t, users) view, err := svc.Follow(ctx, a.ID, b.ID) if err != nil || !view.Following || view.FollowedBy || view.Mutual || view.Friend { t.Fatalf("%+v %v", view, err) } if g.n != 0 { t.Fatalf("greeting %d", g.n) } ok, err := svc.AreFriends(ctx, a.ID, b.ID) if err != nil || ok { t.Fatalf("friends %v %v", ok, err) } } func TestMutualFollowCreatesFriendsOnce(t *testing.T) { svc, users, g := testFriends(t) ctx := t.Context() a, b := twoUsers(t, users) if _, err := svc.Follow(ctx, a.ID, b.ID); err != nil { t.Fatal(err) } view, err := svc.Follow(ctx, b.ID, a.ID) if err != nil || !view.Mutual || !view.Friend || !view.Following || !view.FollowedBy { t.Fatalf("%+v %v", view, err) } if g.n != 1 { t.Fatalf("greeting %d", g.n) } again, err := svc.Follow(ctx, b.ID, a.ID) if err != nil || !again.Friend || g.n != 1 { t.Fatalf("repeat %+v greet %d %v", again, g.n, err) } rel, err := svc.Relation(ctx, a.ID, b.ID) if err != nil || rel.Source != entity.SourceFollow { t.Fatalf("source %+v %v", rel, err) } } func TestUnfollowMutualDropsFriendsKeepsMessage(t *testing.T) { ids, err := id.New(1) if err != nil { t.Fatal(err) } users := logictest.Users(ids, nil) 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) } chats := convmodel.NewMemoryP2P() msgs := msgmodel.NewMemoryRepo() p2p := convlogic.NewP2PService(chats, ids) svc := NewService(friendmodel.NewMemoryRepo(), users, ids, NewGreeter(p2p, msgs, ids, nil)) if _, err := svc.Follow(ctx, a.ID, b.ID); err != nil { t.Fatal(err) } if _, err := svc.Follow(ctx, b.ID, a.ID); err != nil { t.Fatal(err) } conv, err := chats.GetP2P(ctx, a.ID, b.ID) if err != nil || conv == nil || conv.ID <= 0 { t.Fatalf("conversation %+v %v", conv, err) } before, err := msgs.ListByConversation(ctx, conv.ID, a.ID, 0, 20) if err != nil || len(before) != 1 { t.Fatalf("before %d %v", len(before), err) } view, err := svc.Unfollow(ctx, a.ID, b.ID) if err != nil || view.Following || view.Mutual || view.Friend || !view.FollowedBy { t.Fatalf("%+v %v", view, err) } peer, err := svc.GetFollow(ctx, b.ID, a.ID) if err != nil || peer.Friend || peer.Mutual { t.Fatalf("peer %+v %v", peer, err) } if _, err := svc.Relation(ctx, a.ID, b.ID); !errors.Is(err, errs.ErrNotFound) { t.Fatalf("edge a->b %v", err) } if _, err := svc.Relation(ctx, b.ID, a.ID); !errors.Is(err, errs.ErrNotFound) { t.Fatalf("edge b->a %v", err) } after, err := msgs.ListByConversation(ctx, conv.ID, a.ID, 0, 20) if err != nil || len(after) != 1 || after[0].ID != before[0].ID { t.Fatalf("after %d %v", len(after), err) } } func TestOtherSideUnfollowRemovesBothEdges(t *testing.T) { svc, users, _ := testFriends(t) ctx := t.Context() a, b := twoUsers(t, users) if _, err := svc.Follow(ctx, a.ID, b.ID); err != nil { t.Fatal(err) } if view, err := svc.Follow(ctx, b.ID, a.ID); err != nil || !view.Friend { t.Fatalf("mutual %+v %v", view, err) } view, err := svc.Unfollow(ctx, b.ID, a.ID) if err != nil || view.Friend || view.Mutual { t.Fatalf("unfollow %+v %v", view, err) } if _, err := svc.Relation(ctx, a.ID, b.ID); !errors.Is(err, errs.ErrNotFound) { t.Fatalf("edge a->b %v", err) } if _, err := svc.Relation(ctx, b.ID, a.ID); !errors.Is(err, errs.ErrNotFound) { t.Fatalf("edge b->a %v", err) } left, err := svc.GetFollow(ctx, a.ID, b.ID) if err != nil || left.Friend || !left.Following { t.Fatalf("remaining follow %+v %v", left, err) } } func TestUnfollowOneWayKeepsAcceptedFriend(t *testing.T) { svc, users, g := testFriends(t) ctx := t.Context() a, b := twoUsers(t, users) applied, err := svc.Apply(ctx, a.ID, &ApplyInput{UID: b.ID}) if err != nil { t.Fatal(err) } if _, err := svc.Accept(ctx, b.ID, AcceptInput{RequestID: applied.RequestID}); err != nil { t.Fatal(err) } if _, err := svc.Follow(ctx, a.ID, b.ID); err != nil { t.Fatal(err) } view, err := svc.Unfollow(ctx, a.ID, b.ID) if err != nil || view.Following || !view.Friend || view.Mutual { t.Fatalf("%+v %v", view, err) } if g.n != 1 { t.Fatalf("greeting %d", g.n) } } func TestFollowRejectsSelfAndBlock(t *testing.T) { svc, users, _ := testFriends(t) ctx := t.Context() a, b := twoUsers(t, users) if _, err := svc.Follow(ctx, a.ID, a.ID); !errors.Is(err, errs.ErrInvalidArgument) { t.Fatalf("self %v", err) } if err := svc.Block(ctx, a.ID, b.ID); err != nil { t.Fatal(err) } if _, err := svc.Follow(ctx, a.ID, b.ID); !errors.Is(err, errs.ErrForbidden) { t.Fatalf("blocked by me %v", err) } if _, err := svc.Follow(ctx, b.ID, a.ID); !errors.Is(err, errs.ErrNotFound) { t.Fatalf("blocked by peer %v", err) } }