diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index 5deae3dac..3343cc510 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -906,8 +906,11 @@ impl FfiConversations { }; let convo = if account_addresses.is_empty() { - self.inner_client - .create_group(group_permissions, metadata_options)? + let group = self + .inner_client + .create_group(group_permissions, metadata_options)?; + group.sync().await?; + group } else { self.inner_client .create_group_with_members(&account_addresses, group_permissions, metadata_options) @@ -5650,4 +5653,51 @@ mod tests { FfiReactionSchema::Unicode )); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 5)] + async fn test_update_policies_empty_group() { + let amal = new_test_client().await; + let bola = new_test_client().await; + + // Create a group with amal and bola with admin-only permissions + let admin_only_options = FfiCreateGroupOptions { + permissions: Some(FfiGroupPermissionsOptions::AdminOnly), + ..Default::default() + }; + let amal_group = amal + .conversations() + .create_group( + vec![bola.account_address.clone()], + admin_only_options.clone(), + ) + .await + .unwrap(); + + // Verify we can update the group name without syncing first + amal_group + .update_group_name("New Group Name 1".to_string()) + .await + .unwrap(); + + // Verify the name is updated + amal_group.sync().await.unwrap(); + assert_eq!(amal_group.group_name().unwrap(), "New Group Name 1"); + + // Create a group with just amal + let amal_solo_group = amal + .conversations() + .create_group(vec![], admin_only_options) + .await + .unwrap(); + + // Verify we can update the group name + amal_solo_group + .update_group_name("New Group Name 2".to_string()) + .await + .unwrap(); + + // Verify the name is updated + amal_solo_group.sync().await.unwrap(); + assert_eq!(amal_solo_group.group_name().unwrap(), "New Group Name 2"); + } } diff --git a/bindings_node/src/conversations.rs b/bindings_node/src/conversations.rs index b9be3580c..7111e7fcc 100644 --- a/bindings_node/src/conversations.rs +++ b/bindings_node/src/conversations.rs @@ -201,10 +201,15 @@ impl Conversations { }; let convo = if account_addresses.is_empty() { - self + let group = self .inner_client .create_group(group_permissions, metadata_options) - .map_err(|e| Error::from_reason(format!("ClientError: {}", e)))? + .map_err(|e| Error::from_reason(format!("ClientError: {}", e)))?; + group + .sync() + .await + .map_err(|e| Error::from_reason(format!("ClientError: {}", e)))?; + group } else { self .inner_client diff --git a/bindings_node/test/Conversations.test.ts b/bindings_node/test/Conversations.test.ts index eccf8b0c1..ad280521b 100644 --- a/bindings_node/test/Conversations.test.ts +++ b/bindings_node/test/Conversations.test.ts @@ -619,4 +619,37 @@ describe('Conversations', () => { await group2.send(encodeTextMessage('gm!')) expect(group2.consentState()).toBe(ConsentState.Allowed) }) + + it('should update group metadata in empty group', async () => { + const user1 = createUser() + const client1 = await createRegisteredClient(user1) + + // Create empty group with admin-only permissions + const group = await client1.conversations().createGroup([], { + permissions: GroupPermissionsOptions.AdminOnly, + }) + expect(group).toBeDefined() + + // Update group name without syncing first + await group.updateGroupName('New Group Name 1') + expect(group.groupName()).toBe('New Group Name 1') + + // Verify name persists after sync + await group.sync() + expect(group.groupName()).toBe('New Group Name 1') + + // Create another empty group + const soloGroup = await client1.conversations().createGroup([], { + permissions: GroupPermissionsOptions.AdminOnly, + }) + expect(soloGroup).toBeDefined() + + // Update and verify name + await soloGroup.updateGroupName('New Group Name 2') + expect(soloGroup.groupName()).toBe('New Group Name 2') + + // Verify name persists after sync + await soloGroup.sync() + expect(soloGroup.groupName()).toBe('New Group Name 2') + }) }) diff --git a/bindings_wasm/src/conversations.rs b/bindings_wasm/src/conversations.rs index aa926aef5..6eeb0f426 100644 --- a/bindings_wasm/src/conversations.rs +++ b/bindings_wasm/src/conversations.rs @@ -254,10 +254,15 @@ impl Conversations { }; let convo = if account_addresses.is_empty() { - self + let group = self .inner_client .create_group(group_permissions, metadata_options) - .map_err(|e| JsError::new(format!("{}", e).as_str()))? + .map_err(|e| JsError::new(format!("{}", e).as_str()))?; + group + .sync() + .await + .map_err(|e| JsError::new(format!("{}", e).as_str()))?; + group } else { self .inner_client diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 7a68b708f..5180c8082 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -2745,43 +2745,68 @@ pub(crate) mod tests { #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_update_policies_empty_group() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; + let bola_wallet = generate_local_wallet(); + let _bola = ClientBuilder::new_test_client(&bola_wallet).await; - // Create a group and verify it has the default group name + // Create a group with amal and bola let policy_set = Some(PreconfiguredPolicies::AdminsOnly.to_policy_set()); let amal_group = amal - .create_group(policy_set, GroupMetadataOptions::default()) + .create_group_with_members( + &[bola_wallet.get_address()], + policy_set, + GroupMetadataOptions::default(), + ) + .await .unwrap(); - amal_group.sync().await.unwrap(); + // Verify we can update the group name without syncing first + amal_group + .update_group_name("New Group Name 1".to_string()) + .await + .unwrap(); + + // Verify the name is updated + amal_group.sync().await.unwrap(); let group_mutable_metadata = amal_group .mutable_metadata(&amal_group.mls_provider().unwrap()) .unwrap(); - assert!(group_mutable_metadata.attributes.len().eq(&4)); - assert!(group_mutable_metadata + let group_name_1 = group_mutable_metadata .attributes .get(&MetadataField::GroupName.to_string()) - .unwrap() - .is_empty()); + .unwrap(); + assert_eq!(group_name_1, "New Group Name 1"); - amal_group - .update_permission_policy( - PermissionUpdateType::AddMember, - PermissionPolicyOption::Allow, - None, - ) - .await + // Create a group with just amal + let policy_set_2 = Some(PreconfiguredPolicies::AdminsOnly.to_policy_set()); + let amal_group_2 = amal + .create_group(policy_set_2, GroupMetadataOptions::default()) .unwrap(); - // Update group name - amal_group - .update_group_name("New Group Name 1".to_string()) + // Verify empty group fails to update metadata before syncing + amal_group_2 + .update_group_name("New Group Name 2".to_string()) .await - .unwrap(); + .expect_err("Should fail to update group name before first sync"); - amal_group.send_message("hello".as_bytes()).await.unwrap(); + // Sync the group + amal_group_2.sync().await.unwrap(); - // Verify amal group sees update - amal_group.sync().await.unwrap(); + //Verify we can now update the group name + amal_group_2 + .update_group_name("New Group Name 2".to_string()) + .await + .unwrap(); + + // Verify the name is updated + amal_group_2.sync().await.unwrap(); + let group_mutable_metadata = amal_group_2 + .mutable_metadata(&amal_group_2.mls_provider().unwrap()) + .unwrap(); + let group_name_2 = group_mutable_metadata + .attributes + .get(&MetadataField::GroupName.to_string()) + .unwrap(); + assert_eq!(group_name_2, "New Group Name 2"); } #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))]