Get basic test of accepting a contact request passing

Nathan Sobo created

Change summary

crates/client/src/user.rs      |  24 +++++--
crates/collab/src/db.rs        |  12 ++--
crates/collab/src/rpc.rs       | 105 +++++++++++++++++++++++++++++++++--
crates/collab/src/rpc/store.rs |   2 
crates/rpc/proto/zed.proto     |   6 +-
5 files changed, 125 insertions(+), 24 deletions(-)

Detailed changes

crates/client/src/user.rs 🔗

@@ -125,7 +125,7 @@ impl UserStore {
             user_ids.insert(contact.user_id);
             user_ids.extend(contact.projects.iter().flat_map(|w| &w.guests).copied());
         }
-        user_ids.extend(message.incoming_requests.iter().map(|req| req.user_id));
+        user_ids.extend(message.incoming_requests.iter().map(|req| req.requester_id));
         user_ids.extend(message.outgoing_requests.iter());
 
         let load_users = self.get_users(user_ids.into_iter().collect(), cx);
@@ -144,8 +144,10 @@ impl UserStore {
             let mut incoming_requests = Vec::new();
             for request in message.incoming_requests {
                 incoming_requests.push(
-                    this.update(&mut cx, |this, cx| this.fetch_user(request.user_id, cx))
-                        .await?,
+                    this.update(&mut cx, |this, cx| {
+                        this.fetch_user(request.requester_id, cx)
+                    })
+                    .await?,
                 );
             }
 
@@ -199,12 +201,20 @@ impl UserStore {
             .is_ok()
     }
 
-    pub fn request_contact(&self, to_user_id: u64) -> impl Future<Output = Result<()>> {
+    pub fn incoming_contact_requests(&self) -> &[Arc<User>] {
+        &self.incoming_contact_requests
+    }
+
+    pub fn outgoing_contact_requests(&self) -> &[Arc<User>] {
+        &self.outgoing_contact_requests
+    }
+
+    pub fn request_contact(&self, responder_id: u64) -> impl Future<Output = Result<()>> {
         let client = self.client.upgrade();
         async move {
             client
                 .ok_or_else(|| anyhow!("not logged in"))?
-                .request(proto::RequestContact { to_user_id })
+                .request(proto::RequestContact { responder_id })
                 .await?;
             Ok(())
         }
@@ -212,7 +222,7 @@ impl UserStore {
 
     pub fn respond_to_contact_request(
         &self,
-        from_user_id: u64,
+        requester_id: u64,
         accept: bool,
     ) -> impl Future<Output = Result<()>> {
         let client = self.client.upgrade();
@@ -220,7 +230,7 @@ impl UserStore {
             client
                 .ok_or_else(|| anyhow!("not logged in"))?
                 .request(proto::RespondToContactRequest {
-                    requesting_user_id: from_user_id,
+                    requester_id,
                     response: if accept {
                         proto::ContactRequestResponse::Accept
                     } else {

crates/collab/src/db.rs 🔗

@@ -211,7 +211,7 @@ impl Db for PostgresDb {
                     outgoing_requests.push(user_id_b);
                 } else {
                     incoming_requests.push(IncomingContactRequest {
-                        requesting_user_id: user_id_b,
+                        requester_id: user_id_b,
                         should_notify,
                     });
                 }
@@ -220,7 +220,7 @@ impl Db for PostgresDb {
                     current.push(user_id_a);
                 } else if a_to_b {
                     incoming_requests.push(IncomingContactRequest {
-                        requesting_user_id: user_id_a,
+                        requester_id: user_id_a,
                         should_notify,
                     });
                 } else {
@@ -675,7 +675,7 @@ pub struct Contacts {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct IncomingContactRequest {
-    pub requesting_user_id: UserId,
+    pub requester_id: UserId,
     pub should_notify: bool,
 }
 
@@ -935,7 +935,7 @@ pub mod tests {
                     current: vec![],
                     outgoing_requests: vec![],
                     incoming_requests: vec![IncomingContactRequest {
-                        requesting_user_id: user_1,
+                        requester_id: user_1,
                         should_notify: true
                     }],
                 },
@@ -953,7 +953,7 @@ pub mod tests {
                     current: vec![],
                     outgoing_requests: vec![],
                     incoming_requests: vec![IncomingContactRequest {
-                        requesting_user_id: user_1,
+                        requester_id: user_1,
                         should_notify: false
                     }],
                 },
@@ -1195,7 +1195,7 @@ pub mod tests {
                         current.push(contact.requester_id);
                     } else {
                         incoming_requests.push(IncomingContactRequest {
-                            requesting_user_id: contact.requester_id,
+                            requester_id: contact.requester_id,
                             should_notify: contact.should_notify,
                         });
                     }

crates/collab/src/rpc.rs 🔗

@@ -276,7 +276,7 @@ impl Server {
                 store.add_connection(connection_id, user_id);
                 let update_contacts = store.build_initial_contacts_update(contacts);
                 for connection_id in store.connection_ids_for_user(user_id) {
-                    this.peer.send(connection_id, update_contacts.clone());
+                    this.peer.send(connection_id, update_contacts.clone())?;
                 }
             }
 
@@ -953,11 +953,30 @@ impl Server {
             .read()
             .await
             .user_id_for_connection(request.sender_id)?;
-        let responder_id = UserId::from_proto(request.payload.to_user_id);
+        let responder_id = UserId::from_proto(request.payload.responder_id);
         self.app_state
             .db
             .send_contact_request(requester_id, responder_id)
             .await?;
+        
+
+        // Update outgoing contact requests of requester
+        let mut update = proto::UpdateContacts::default();
+        update.outgoing_requests.push(responder_id.to_proto());
+        for connection_id in self.store().await.connection_ids_for_user(requester_id) {
+            self.peer.send(connection_id, update.clone())?;
+        }
+    
+        // Update incoming contact requests of responder
+        let mut update = proto::UpdateContacts::default();
+        update.incoming_requests.push(proto::IncomingContactRequest {
+            requester_id: requester_id.to_proto(),
+            should_notify: true,
+        });
+        for connection_id in self.store().await.connection_ids_for_user(responder_id) {
+            self.peer.send(connection_id, update.clone())?;
+        }
+        
         response.send(proto::Ack {})?;
         Ok(())
     }
@@ -972,15 +991,45 @@ impl Server {
             .read()
             .await
             .user_id_for_connection(request.sender_id)?;
-        let requester_id = UserId::from_proto(request.payload.requesting_user_id);
+        let requester_id = UserId::from_proto(request.payload.requester_id);
+        let accept = request.payload.response == proto::ContactRequestResponse::Accept as i32;
         self.app_state
             .db
             .respond_to_contact_request(
                 responder_id,
                 requester_id,
-                request.payload.response == proto::ContactRequestResponse::Accept as i32,
+                accept,
             )
             .await?;
+            
+        if accept {
+            // Update responder with new contact
+            let mut update = proto::UpdateContacts::default();
+            update.contacts.push(proto::Contact {
+                user_id: requester_id.to_proto(),
+                projects: Default::default(), // TODO
+                online: true, // TODO
+            });
+            update.remove_incoming_requests.push(requester_id.to_proto());
+            for connection_id in self.store.read().await.connection_ids_for_user(responder_id) {
+                self.peer.send(connection_id, update.clone())?;
+            }
+            
+            // Update requester with new contact
+            let mut update = proto::UpdateContacts::default();
+            update.contacts.push(proto::Contact {
+                user_id: responder_id.to_proto(),
+                projects: Default::default(), // TODO
+                online: true, // TODO
+            });
+            update.remove_outgoing_requests.push(responder_id.to_proto());
+            for connection_id in self.store.read().await.connection_ids_for_user(requester_id) {
+                self.peer.send(connection_id, update.clone())?;
+            }
+        } else {
+            todo!()
+        }
+            
         response.send(proto::Ack {})?;
         Ok(())
     }
@@ -4987,8 +5036,8 @@ mod tests {
         }
     }
 
-    #[gpui::test(iterations = 10)]
-    async fn test_contacts_requests(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
+    #[gpui::test(iterations = 1)] // TODO: More iterations
+    async fn test_contacts_requests(executor: Arc<Deterministic>, cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
         cx_a.foreground().forbid_parking();
 
         // Connect to a server as 3 clients.
@@ -4996,6 +5045,8 @@ mod tests {
         let client_a = server.create_client(cx_a, "user_a").await;
         let client_b = server.create_client(cx_b, "user_b").await;
 
+        // User A requests that user B become their contact
+
         client_a
             .user_store
             .read_with(cx_a, |store, _| {
@@ -5003,15 +5054,55 @@ mod tests {
             })
             .await
             .unwrap();
+        
+        executor.run_until_parked();
+        
+        // Both parties see the pending request appear. User B accepts the request.
+        
+        client_a.user_store.read_with(cx_a, |store, _| {
+            let contacts = store
+                .outgoing_contact_requests()
+                .iter()
+                .map(|contact| contact.github_login.clone())
+                .collect::<Vec<_>>();
+            assert_eq!(contacts, &["user_b"]);
+        });
+                
+        client_b.user_store.read_with(cx_b, |store, _| {
+            let contacts = store
+                .incoming_contact_requests()
+                .iter()
+                .map(|contact| contact.github_login.clone())
+                .collect::<Vec<_>>();
+            assert_eq!(contacts, &["user_a"]);
+            
+            store.respond_to_contact_request(client_a.user_id().unwrap(), true)
+        }).await.unwrap();
+
+        executor.run_until_parked();
+
+        // User B sees user A as their contact now, and the incoming request from them is removed
+        client_b.user_store.read_with(cx_b, |store, _| {
+            let contacts = store
+                .contacts()
+                .iter()
+                .map(|contact| contact.user.github_login.clone())
+                .collect::<Vec<_>>();
+            assert_eq!(contacts, &["user_a"]);
+            assert!(store.incoming_contact_requests().is_empty());
+        });
 
+        // User A sees user B as their contact now, and the outgoing request to them is removed
         client_a.user_store.read_with(cx_a, |store, _| {
             let contacts = store
                 .contacts()
                 .iter()
                 .map(|contact| contact.user.github_login.clone())
                 .collect::<Vec<_>>();
-            assert_eq!(contacts, &["user_b"])
+            assert_eq!(contacts, &["user_b"]);
+            assert!(store.outgoing_contact_requests().is_empty());
         });
+        
     }
 
     #[gpui::test(iterations = 10)]

crates/collab/src/rpc/store.rs 🔗

@@ -225,7 +225,7 @@ impl Store {
             update
                 .incoming_requests
                 .push(proto::IncomingContactRequest {
-                    user_id: request.requesting_user_id.to_proto(),
+                    requester_id: request.requester_id.to_proto(),
                     should_notify: request.should_notify,
                 })
         }

crates/rpc/proto/zed.proto 🔗

@@ -550,11 +550,11 @@ message UsersResponse {
 }
 
 message RequestContact {
-    uint64 to_user_id = 1;
+    uint64 responder_id = 1;
 }
 
 message RespondToContactRequest {
-    uint64 requesting_user_id = 1;
+    uint64 requester_id = 1;
     ContactRequestResponse response = 2;
 }
 
@@ -599,7 +599,7 @@ message UpdateContacts {
 }
 
 message IncomingContactRequest {
-    uint64 user_id = 1;
+    uint64 requester_id = 1;
     bool should_notify = 2;
 }