From 20f67c42b1568e34e74c6620da4dcddf756d32d9 Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 24 Oct 2025 10:26:05 +0000 Subject: [PATCH 1/3] feat: allow adding second transport --- .../src/deltachat_rpc_client/pytestplugin.py | 4 ++++ deltachat-rpc-client/tests/test_multitransport.py | 7 +++++++ src/configure.rs | 6 ------ src/login_param.rs | 10 ++-------- 4 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 deltachat-rpc-client/tests/test_multitransport.py diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py index da92f430f3..188e17ffc5 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py @@ -40,6 +40,10 @@ def get_credentials(self) -> (str, str): username = "ci-" + "".join(random.choice("2345789acdefghjkmnpqrstuvwxyz") for i in range(6)) return f"{username}@{domain}", f"{username}${username}" + def get_account_qr(self): + domain = os.getenv("CHATMAIL_DOMAIN") + return f"dcaccount:https://{domain}/new" + @futuremethod def new_configured_account(self): """Create a new configured account.""" diff --git a/deltachat-rpc-client/tests/test_multitransport.py b/deltachat-rpc-client/tests/test_multitransport.py new file mode 100644 index 0000000000..a69eaec777 --- /dev/null +++ b/deltachat-rpc-client/tests/test_multitransport.py @@ -0,0 +1,7 @@ +def test_add_second_address(acfactory) -> None: + account = acfactory.new_configured_account() + assert len(account.list_transports()) == 1 + + qr = acfactory.get_account_qr() + account.add_transport_from_qr(qr) + assert len(account.list_transports()) == 2 diff --git a/src/configure.rs b/src/configure.rs index fafe5bfa84..41c3de74bd 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -128,12 +128,6 @@ impl Context { "cannot configure, database not opened." ); param.addr = addr_normalize(¶m.addr); - let old_addr = self.get_config(Config::ConfiguredAddr).await?; - if self.is_configured().await? && !addr_cmp(&old_addr.unwrap_or_default(), ¶m.addr) { - let error_msg = "Changing your email address is not supported right now. Check back in a few months!"; - progress!(self, 0, Some(error_msg.to_string())); - bail!(error_msg); - } let cancel_channel = self.alloc_ongoing().await?; let res = self diff --git a/src/login_param.rs b/src/login_param.rs index 5356a11a03..53b7c4b71a 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -2,8 +2,8 @@ use std::fmt; -use anyhow::{Context as _, Result, bail, ensure, format_err}; -use deltachat_contact_tools::{EmailAddress, addr_cmp, addr_normalize}; +use anyhow::{Context as _, Result, bail, format_err}; +use deltachat_contact_tools::{EmailAddress, addr_normalize}; use num_traits::ToPrimitive as _; use serde::{Deserialize, Serialize}; @@ -807,12 +807,6 @@ impl ConfiguredLoginParam { let addr = addr_normalize(&self.addr); let provider_id = self.provider.map(|provider| provider.id); let configured_addr = context.get_config(Config::ConfiguredAddr).await?; - if let Some(configured_addr) = &configured_addr { - ensure!( - addr_cmp(configured_addr, &addr), - "Adding a second transport is not supported right now." - ); - } context .sql .execute( From 4421eb6dd54eaf46b77e9ae7757d8f6404c0e27d Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 25 Oct 2025 01:08:19 +0000 Subject: [PATCH 2/3] feat: allow deleting transports --- .../src/deltachat_rpc_client/account.py | 4 +++ .../tests/test_multitransport.py | 17 +++++++++++++ src/configure.rs | 25 +++++++++++++++---- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/account.py b/deltachat-rpc-client/src/deltachat_rpc_client/account.py index fd3f53daca..d0109a9f02 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/account.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/account.py @@ -130,6 +130,10 @@ def add_transport_from_qr(self, qr: str): """Add a new transport using a QR code.""" yield self._rpc.add_transport_from_qr.future(self.id, qr) + def delete_transport(self, addr: str): + """Delete a transport.""" + self._rpc.delete_transport(self.id, addr) + @futuremethod def list_transports(self): """Return the list of all email accounts that are used as a transport in the current profile.""" diff --git a/deltachat-rpc-client/tests/test_multitransport.py b/deltachat-rpc-client/tests/test_multitransport.py index a69eaec777..347036ce63 100644 --- a/deltachat-rpc-client/tests/test_multitransport.py +++ b/deltachat-rpc-client/tests/test_multitransport.py @@ -1,3 +1,7 @@ +import pytest + +from deltachat_rpc_client.rpc import JsonRpcError + def test_add_second_address(acfactory) -> None: account = acfactory.new_configured_account() assert len(account.list_transports()) == 1 @@ -5,3 +9,16 @@ def test_add_second_address(acfactory) -> None: qr = acfactory.get_account_qr() account.add_transport_from_qr(qr) assert len(account.list_transports()) == 2 + + account.add_transport_from_qr(qr) + assert len(account.list_transports()) == 3 + + first_addr = account.list_transports()[0]["addr"] + second_addr = account.list_transports()[1]["addr"] + + # Cannot delete the first address. + with pytest.raises(JsonRpcError): + account.delete_transport(first_addr) + + account.delete_transport(second_addr) + assert len(account.list_transports()) == 2 diff --git a/src/configure.rs b/src/configure.rs index 41c3de74bd..67b6e95826 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -203,11 +203,26 @@ impl Context { /// Removes the transport with the specified email address /// (i.e. [EnteredLoginParam::addr]). - #[expect(clippy::unused_async)] - pub async fn delete_transport(&self, _addr: &str) -> Result<()> { - bail!( - "Adding and removing additional transports is not supported yet. Check back in a few months!" - ) + pub async fn delete_transport(&self, addr: &str) -> Result<()> { + self.sql + .transaction(|transaction| { + let current_addr = transaction.query_row( + "SELECT value FROM config WHERE keyname='configured_addr'", + (), + |row| { + let addr: String = row.get(0)?; + Ok(addr) + }, + )?; + + if current_addr == addr { + bail!("Cannot delete current transport"); + } + transaction.execute("DELETE FROM transports WHERE addr=?", (addr,))?; + Ok(()) + }) + .await?; + Ok(()) } async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> { From 687262f2edd37301c294e0a0d4c278b947622126 Mon Sep 17 00:00:00 2001 From: link2xt Date: Sun, 26 Oct 2025 07:34:34 +0000 Subject: [PATCH 3/3] feat: allow to set another transport as primary --- .../tests/test_multitransport.py | 30 +++++++++++++++++++ src/config.rs | 28 ++++++++--------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/deltachat-rpc-client/tests/test_multitransport.py b/deltachat-rpc-client/tests/test_multitransport.py index 347036ce63..6e451f9ee7 100644 --- a/deltachat-rpc-client/tests/test_multitransport.py +++ b/deltachat-rpc-client/tests/test_multitransport.py @@ -2,6 +2,7 @@ from deltachat_rpc_client.rpc import JsonRpcError + def test_add_second_address(acfactory) -> None: account = acfactory.new_configured_account() assert len(account.list_transports()) == 1 @@ -22,3 +23,32 @@ def test_add_second_address(acfactory) -> None: account.delete_transport(second_addr) assert len(account.list_transports()) == 2 + + +def test_change_address(acfactory) -> None: + """Test Alice configuring a second transport and setting it as a primary one.""" + alice, bob = acfactory.get_online_accounts(2) + + alice_chat_bob = alice.create_chat(bob) + alice_chat_bob.send_text("Hello!") + + msg1 = bob.wait_for_incoming_msg().get_snapshot() + sender_addr1 = msg1.sender.get_snapshot().address + + alice.stop_io() + old_alice_addr = alice.get_config("configured_addr") + qr = acfactory.get_account_qr() + alice.add_transport_from_qr(qr) + new_alice_addr = alice.list_transports()[1]["addr"] + alice.set_config("configured_addr", new_alice_addr) + alice.start_io() + + alice_chat_bob.send_text("Hello again!") + + msg2 = bob.wait_for_incoming_msg().get_snapshot() + sender_addr2 = msg2.sender.get_snapshot().address + + assert msg1.sender == msg2.sender + assert sender_addr1 != sender_addr2 + assert sender_addr1 == old_alice_addr + assert sender_addr2 == new_alice_addr diff --git a/src/config.rs b/src/config.rs index a251108602..05d3d65c9b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use std::env; use std::path::Path; use std::str::FromStr; -use anyhow::{Context as _, Result, bail, ensure}; +use anyhow::{Context as _, Result, ensure}; use base64::Engine as _; use deltachat_contact_tools::{addr_cmp, sanitize_single_line}; use serde::{Deserialize, Serialize}; @@ -791,20 +791,20 @@ impl Context { .await?; } Config::ConfiguredAddr => { - if self.is_configured().await? { - bail!("Cannot change ConfiguredAddr"); - } - if let Some(addr) = value { - info!( - self, - "Creating a pseudo configured account which will not be able to send or receive messages. Only meant for tests!" - ); - ConfiguredLoginParam::from_json(&format!( - r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"# - ))? - .save_to_transports_table(self, &EnteredLoginParam::default()) - .await?; + if !self.is_configured().await? { + if let Some(addr) = value { + info!( + self, + "Creating a pseudo configured account which will not be able to send or receive messages. Only meant for tests!" + ); + ConfiguredLoginParam::from_json(&format!( + r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"# + ))? + .save_to_transports_table(self, &EnteredLoginParam::default()) + .await?; + } } + self.sql.set_raw_config(key.as_ref(), value).await?; } _ => { self.sql.set_raw_config(key.as_ref(), value).await?;