From ffa047be6828f1450ceb8e648e524a3b5a8dc469 Mon Sep 17 00:00:00 2001
From: Michael Weimann <michaelw@matrix.org>
Date: Thu, 2 Mar 2023 09:44:12 +0100
Subject: [PATCH] Find DMs with pending third-paty invites (#10256)

---
 src/utils/dm/findDMForUser.ts       | 10 +++++++++-
 test/test-utils/test-utils.ts       | 13 +++++++++++++
 test/utils/dm/findDMForUser-test.ts | 24 ++++++++++++++++++++----
 3 files changed, 42 insertions(+), 5 deletions(-)

diff --git a/src/utils/dm/findDMForUser.ts b/src/utils/dm/findDMForUser.ts
index 3f6fcbfca0..c8b5f615d7 100644
--- a/src/utils/dm/findDMForUser.ts
+++ b/src/utils/dm/findDMForUser.ts
@@ -38,7 +38,15 @@ function extractSuitableRoom(rooms: Room[], userId: string): Room | undefined {
                     (m) => !functionalUsers.includes(m.userId) && m.membership && isJoinedOrNearlyJoined(m.membership),
                 );
                 const otherMember = joinedMembers.find((m) => m.userId === userId);
-                return otherMember && joinedMembers.length === 2;
+
+                if (otherMember && joinedMembers.length === 2) {
+                    return true;
+                }
+
+                const thirdPartyInvites = r.currentState.getStateEvents("m.room.third_party_invite") || [];
+
+                // match room with pending third-party invite
+                return joinedMembers.length === 1 && thirdPartyInvites.length === 1;
             }
             return false;
         })
diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts
index 2e700c2b18..342949e768 100644
--- a/test/test-utils/test-utils.ts
+++ b/test/test-utils/test-utils.ts
@@ -691,6 +691,19 @@ export const mkRoomMemberJoinEvent = (user: string, room: string): MatrixEvent =
     });
 };
 
+export const mkThirdPartyInviteEvent = (user: string, displayName: string, room: string): MatrixEvent => {
+    return mkEvent({
+        event: true,
+        type: EventType.RoomThirdPartyInvite,
+        content: {
+            display_name: displayName,
+        },
+        skey: "test" + Math.random(),
+        user,
+        room,
+    });
+};
+
 export const mkPusher = (extra: Partial<IPusher> = {}): IPusher => ({
     app_display_name: "app",
     app_id: "123",
diff --git a/test/utils/dm/findDMForUser-test.ts b/test/utils/dm/findDMForUser-test.ts
index 60c5b342f4..71058ce39f 100644
--- a/test/utils/dm/findDMForUser-test.ts
+++ b/test/utils/dm/findDMForUser-test.ts
@@ -18,7 +18,7 @@ import { mocked } from "jest-mock";
 import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 
 import DMRoomMap from "../../../src/utils/DMRoomMap";
-import { createTestClient, makeMembershipEvent } from "../../test-utils";
+import { createTestClient, makeMembershipEvent, mkThirdPartyInviteEvent } from "../../test-utils";
 import { LocalRoom } from "../../../src/models/LocalRoom";
 import { findDMForUser } from "../../../src/utils/dm/findDMForUser";
 import { getFunctionalMembers } from "../../../src/utils/room/getFunctionalMembers";
@@ -32,13 +32,15 @@ describe("findDMForUser", () => {
     const userId2 = "@user2:example.com";
     const userId3 = "@user3:example.com";
     const botId = "@bot:example.com";
+    const thirdPartyId = "party@example.com";
     let room1: Room;
     let room2: LocalRoom;
     let room3: Room;
     let room4: Room;
     let room5: Room;
     let room6: Room;
-    const room7Id = "!room7:example.com";
+    let room7: Room;
+    const unknownRoomId = "!unknown:example.com";
     let dmRoomMap: DMRoomMap;
     let mockClient: MatrixClient;
 
@@ -89,6 +91,14 @@ describe("findDMForUser", () => {
             makeMembershipEvent(room6.roomId, userId3, "join"),
         ]);
 
+        // room with pending third-party invite
+        room7 = new Room("!room7:example.com", mockClient, userId1);
+        room7.getMyMembership = () => "join";
+        room7.currentState.setStateEvents([
+            makeMembershipEvent(room7.roomId, userId1, "join"),
+            mkThirdPartyInviteEvent(thirdPartyId, "third-party", room7.roomId),
+        ]);
+
         mocked(mockClient.getRoom).mockImplementation((roomId: string) => {
             return (
                 {
@@ -98,6 +108,7 @@ describe("findDMForUser", () => {
                     [room4.roomId]: room4,
                     [room5.roomId]: room5,
                     [room6.roomId]: room6,
+                    [room7.roomId]: room7,
                 }[roomId] || null
             );
         });
@@ -113,14 +124,15 @@ describe("findDMForUser", () => {
                     room4.roomId,
                     room5.roomId,
                     room6.roomId,
-                    room7Id, // this room does not exist in client
+                    room7.roomId,
+                    unknownRoomId, // this room does not exist in client
                 ]),
             ),
         } as unknown as DMRoomMap;
         jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
         mocked(dmRoomMap.getDMRoomsForUserId).mockImplementation((userId: string) => {
             if (userId === userId1) {
-                return [room1.roomId, room2.roomId, room3.roomId, room4.roomId, room5.roomId, room7Id];
+                return [room1.roomId, room2.roomId, room3.roomId, room4.roomId, room5.roomId, unknownRoomId];
             }
 
             return [];
@@ -158,4 +170,8 @@ describe("findDMForUser", () => {
 
         expect(findDMForUser(mockClient, userId3)).toBe(room6);
     });
+
+    it("should find a room with a pending third-party invite", () => {
+        expect(findDMForUser(mockClient, thirdPartyId)).toBe(room7);
+    });
 });