From a93d1925ac9fffa9ab53ffc2b28f221323ea6e07 Mon Sep 17 00:00:00 2001 From: boxdot Date: Mon, 20 Jan 2025 09:37:47 +0100 Subject: [PATCH] chore: replace TrySafeInto by FromStr (#293) --- .../src/api/conversation_details_cubit.rs | 7 +- applogic/src/api/conversation_list_cubit.rs | 2 +- applogic/src/api/conversations.rs | 6 +- applogic/src/api/user/connections.rs | 5 +- applogic/src/api/user/mod.rs | 4 +- ...0db4ec0f65ad92677e6f87366c9161a12958d.json | 14 + ...bafb1929a95bdf63fd120ffbf0cc0af07990.json} | 4 +- ...201a8d3787159c5e0beb81b07991f69e605ea.json | 14 - ...9fd3bae5e8a9a59f5ad57036d2f4093a64ca1.json | 17 - ...3aaa58d931fcd4b7187edd1d0d2e6bd7c7375.json | 17 + ...4b03f4c30e0450b3943049f72c889849092a5.json | 17 + ...15ec143acb28b99ee319486e5a348aebf8853.json | 17 - backend/src/ds/group_state/persistence.rs | 31 +- coreclient/src/clients/mod.rs | 10 +- coreclient/src/clients/tests/mod.rs | 7 +- coreclient/src/conversations/mod.rs | 28 +- server/src/main.rs | 17 +- server/tests/mod.rs | 301 ++++++++---------- server/tests/qs/ws.rs | 9 +- test_harness/src/docker/mod.rs | 8 +- test_harness/src/main.rs | 2 +- .../test_scenarios/basic_group_operations.rs | 31 +- .../federated_group_operations.rs | 21 +- .../test_scenarios/randomized_operations.rs | 22 +- test_harness/src/utils/mod.rs | 2 +- test_harness/src/utils/setup.rs | 237 ++++++-------- types/src/identifiers/mod.rs | 186 +++++------ types/src/identifiers/tls_codec_impls.rs | 25 +- 28 files changed, 479 insertions(+), 582 deletions(-) create mode 100644 backend/.sqlx/query-29234191c66fe0f885e5d43e2970db4ec0f65ad92677e6f87366c9161a12958d.json rename backend/.sqlx/{query-08608d054543d33b2ccb358ce29ae5273495842d1c51fe7c335267953db8bdcd.json => query-4c8dac26fef5c235476dca6fd0fbbafb1929a95bdf63fd120ffbf0cc0af07990.json} (69%) delete mode 100644 backend/.sqlx/query-59ed45df5cc9e196eb26f03dac3201a8d3787159c5e0beb81b07991f69e605ea.json delete mode 100644 backend/.sqlx/query-b53328370790a73e5329450bf079fd3bae5e8a9a59f5ad57036d2f4093a64ca1.json create mode 100644 backend/.sqlx/query-c889289c8c35c831ed6faab25cc3aaa58d931fcd4b7187edd1d0d2e6bd7c7375.json create mode 100644 backend/.sqlx/query-ca471c422d4e4c8f7f4db7f66274b03f4c30e0450b3943049f72c889849092a5.json delete mode 100644 backend/.sqlx/query-e69944cfbad908cd11673ef89f015ec143acb28b99ee319486e5a348aebf8853.json diff --git a/applogic/src/api/conversation_details_cubit.rs b/applogic/src/api/conversation_details_cubit.rs index 2d924314..644b4825 100644 --- a/applogic/src/api/conversation_details_cubit.rs +++ b/applogic/src/api/conversation_details_cubit.rs @@ -10,7 +10,6 @@ use phnxcoreclient::{ store::{Store, StoreEntityId, StoreOperation}, }; use phnxcoreclient::{store::StoreNotification, ConversationId}; -use phnxtypes::identifiers::SafeTryInto; use tokio::sync::watch; use tokio_stream::{Stream, StreamExt}; use tokio_util::sync::CancellationToken; @@ -91,10 +90,10 @@ impl ConversationDetailsCubitBase { .map(|c| c.conversation_type.clone()); match conversation_type { Some( - UiConversationType::UnconfirmedConnection(username) - | UiConversationType::Connection(username), + UiConversationType::UnconfirmedConnection(user_name) + | UiConversationType::Connection(user_name), ) => { - let qualified_username = SafeTryInto::try_into(username)?; + let qualified_username = user_name.parse()?; let profile = self.store.user_profile(&qualified_username).await?; Ok(profile.map(|profile| UiUserProfile::from_profile(&profile))) } diff --git a/applogic/src/api/conversation_list_cubit.rs b/applogic/src/api/conversation_list_cubit.rs index a662aad5..f7077368 100644 --- a/applogic/src/api/conversation_list_cubit.rs +++ b/applogic/src/api/conversation_list_cubit.rs @@ -72,7 +72,7 @@ impl ConversationListCubitBase { // Cubit methods pub async fn create_connection(&self, user_name: String) -> anyhow::Result { - let id = self.context.store.add_contact(user_name).await?; + let id = self.context.store.add_contact(user_name.parse()?).await?; self.context.load_and_emit_state().await; Ok(id) } diff --git a/applogic/src/api/conversations.rs b/applogic/src/api/conversations.rs index 544fc6c7..5c430aa6 100644 --- a/applogic/src/api/conversations.rs +++ b/applogic/src/api/conversations.rs @@ -6,7 +6,7 @@ use std::future::Future; use anyhow::{anyhow, Result}; use phnxcoreclient::{store::Store, Conversation, ConversationId}; -use phnxtypes::identifiers::{QualifiedUserName, SafeTryInto}; +use phnxtypes::identifiers::QualifiedUserName; use crate::notifier::dispatch_message_notifications; @@ -62,7 +62,7 @@ impl User { conversation_id, &user_names .into_iter() - .map(>::try_into) + .map(|s| s.parse()) .collect::, _>>()?, ) .await?; @@ -81,7 +81,7 @@ impl User { conversation_id, &user_names .into_iter() - .map(>::try_into) + .map(|s| s.parse()) .collect::, _>>()?, ) .await?; diff --git a/applogic/src/api/user/connections.rs b/applogic/src/api/user/connections.rs index 496b894a..8b96c6af 100644 --- a/applogic/src/api/user/connections.rs +++ b/applogic/src/api/user/connections.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use anyhow::Result; -use phnxtypes::identifiers::{QualifiedUserName, SafeTryInto}; use crate::api::types::{UiContact, UiUserProfile}; @@ -21,13 +20,13 @@ impl User { } pub async fn contact(&self, user_name: String) -> Option { - let user_name = >::try_into(user_name).unwrap(); + let user_name = user_name.parse().unwrap(); self.user.contact(&user_name).await.map(|c| c.into()) } /// Get the user profile of the user with the given [`QualifiedUserName`]. pub async fn user_profile(&self, user_name: String) -> Result> { - let user_name = SafeTryInto::try_into(user_name)?; + let user_name = user_name.parse()?; let user_profile = self .user .user_profile(&user_name) diff --git a/applogic/src/api/user/mod.rs b/applogic/src/api/user/mod.rs index 78722cfa..fc0bf9b7 100644 --- a/applogic/src/api/user/mod.rs +++ b/applogic/src/api/user/mod.rs @@ -9,7 +9,7 @@ use phnxcoreclient::{ Asset, UserProfile, }; use phnxtypes::{ - identifiers::{QualifiedUserName, SafeTryInto}, + identifiers::QualifiedUserName, messages::{client_ds::QsWsMessage, push_token::PushTokenOperator}, }; use tracing::error; @@ -89,7 +89,7 @@ impl User { display_name: Option, profile_picture: Option>, ) -> Result { - let user_name: QualifiedUserName = SafeTryInto::try_into(user_name)?; + let user_name: QualifiedUserName = user_name.parse()?; let user_profile = UserProfile::new( user_name.clone(), display_name.map(TryFrom::try_from).transpose()?, diff --git a/backend/.sqlx/query-29234191c66fe0f885e5d43e2970db4ec0f65ad92677e6f87366c9161a12958d.json b/backend/.sqlx/query-29234191c66fe0f885e5d43e2970db4ec0f65ad92677e6f87366c9161a12958d.json new file mode 100644 index 00000000..409af897 --- /dev/null +++ b/backend/.sqlx/query-29234191c66fe0f885e5d43e2970db4ec0f65ad92677e6f87366c9161a12958d.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM\n encrypted_groups\n WHERE\n group_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "29234191c66fe0f885e5d43e2970db4ec0f65ad92677e6f87366c9161a12958d" +} diff --git a/backend/.sqlx/query-08608d054543d33b2ccb358ce29ae5273495842d1c51fe7c335267953db8bdcd.json b/backend/.sqlx/query-4c8dac26fef5c235476dca6fd0fbbafb1929a95bdf63fd120ffbf0cc0af07990.json similarity index 69% rename from backend/.sqlx/query-08608d054543d33b2ccb358ce29ae5273495842d1c51fe7c335267953db8bdcd.json rename to backend/.sqlx/query-4c8dac26fef5c235476dca6fd0fbbafb1929a95bdf63fd120ffbf0cc0af07990.json index b455e7b8..eaf9f89c 100644 --- a/backend/.sqlx/query-08608d054543d33b2ccb358ce29ae5273495842d1c51fe7c335267953db8bdcd.json +++ b/backend/.sqlx/query-4c8dac26fef5c235476dca6fd0fbbafb1929a95bdf63fd120ffbf0cc0af07990.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT \n group_id, ciphertext, last_used, deleted_queues\n FROM \n encrypted_groups\n WHERE \n group_id = $1", + "query": "SELECT\n group_id, ciphertext, last_used, deleted_queues\n FROM\n encrypted_groups\n WHERE\n group_id = $1", "describe": { "columns": [ { @@ -36,5 +36,5 @@ false ] }, - "hash": "08608d054543d33b2ccb358ce29ae5273495842d1c51fe7c335267953db8bdcd" + "hash": "4c8dac26fef5c235476dca6fd0fbbafb1929a95bdf63fd120ffbf0cc0af07990" } diff --git a/backend/.sqlx/query-59ed45df5cc9e196eb26f03dac3201a8d3787159c5e0beb81b07991f69e605ea.json b/backend/.sqlx/query-59ed45df5cc9e196eb26f03dac3201a8d3787159c5e0beb81b07991f69e605ea.json deleted file mode 100644 index 541f9edc..00000000 --- a/backend/.sqlx/query-59ed45df5cc9e196eb26f03dac3201a8d3787159c5e0beb81b07991f69e605ea.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM \n encrypted_groups\n WHERE \n group_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "59ed45df5cc9e196eb26f03dac3201a8d3787159c5e0beb81b07991f69e605ea" -} diff --git a/backend/.sqlx/query-b53328370790a73e5329450bf079fd3bae5e8a9a59f5ad57036d2f4093a64ca1.json b/backend/.sqlx/query-b53328370790a73e5329450bf079fd3bae5e8a9a59f5ad57036d2f4093a64ca1.json deleted file mode 100644 index 38bcdcc6..00000000 --- a/backend/.sqlx/query-b53328370790a73e5329450bf079fd3bae5e8a9a59f5ad57036d2f4093a64ca1.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO \n encrypted_groups \n (group_id, ciphertext, last_used, deleted_queues)\n VALUES \n ($1, $2, $3, $4)\n ON CONFLICT (group_id) DO NOTHING", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Bytea", - "Timestamptz", - "Bytea" - ] - }, - "nullable": [] - }, - "hash": "b53328370790a73e5329450bf079fd3bae5e8a9a59f5ad57036d2f4093a64ca1" -} diff --git a/backend/.sqlx/query-c889289c8c35c831ed6faab25cc3aaa58d931fcd4b7187edd1d0d2e6bd7c7375.json b/backend/.sqlx/query-c889289c8c35c831ed6faab25cc3aaa58d931fcd4b7187edd1d0d2e6bd7c7375.json new file mode 100644 index 00000000..78fddc69 --- /dev/null +++ b/backend/.sqlx/query-c889289c8c35c831ed6faab25cc3aaa58d931fcd4b7187edd1d0d2e6bd7c7375.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO\n encrypted_groups\n (group_id, ciphertext, last_used, deleted_queues)\n VALUES\n ($1, $2, $3, $4)\n ON CONFLICT (group_id) DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Bytea", + "Timestamptz", + "Bytea" + ] + }, + "nullable": [] + }, + "hash": "c889289c8c35c831ed6faab25cc3aaa58d931fcd4b7187edd1d0d2e6bd7c7375" +} diff --git a/backend/.sqlx/query-ca471c422d4e4c8f7f4db7f66274b03f4c30e0450b3943049f72c889849092a5.json b/backend/.sqlx/query-ca471c422d4e4c8f7f4db7f66274b03f4c30e0450b3943049f72c889849092a5.json new file mode 100644 index 00000000..ce059784 --- /dev/null +++ b/backend/.sqlx/query-ca471c422d4e4c8f7f4db7f66274b03f4c30e0450b3943049f72c889849092a5.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE\n encrypted_groups\n SET\n ciphertext = $2, last_used = $3, deleted_queues = $4\n WHERE\n group_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Bytea", + "Timestamptz", + "Bytea" + ] + }, + "nullable": [] + }, + "hash": "ca471c422d4e4c8f7f4db7f66274b03f4c30e0450b3943049f72c889849092a5" +} diff --git a/backend/.sqlx/query-e69944cfbad908cd11673ef89f015ec143acb28b99ee319486e5a348aebf8853.json b/backend/.sqlx/query-e69944cfbad908cd11673ef89f015ec143acb28b99ee319486e5a348aebf8853.json deleted file mode 100644 index 717b546b..00000000 --- a/backend/.sqlx/query-e69944cfbad908cd11673ef89f015ec143acb28b99ee319486e5a348aebf8853.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE \n encrypted_groups\n SET \n ciphertext = $2, last_used = $3, deleted_queues = $4\n WHERE \n group_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Bytea", - "Timestamptz", - "Bytea" - ] - }, - "nullable": [] - }, - "hash": "e69944cfbad908cd11673ef89f015ec143acb28b99ee319486e5a348aebf8853" -} diff --git a/backend/src/ds/group_state/persistence.rs b/backend/src/ds/group_state/persistence.rs index 4805200b..ff554e12 100644 --- a/backend/src/ds/group_state/persistence.rs +++ b/backend/src/ds/group_state/persistence.rs @@ -16,10 +16,10 @@ use super::StorableDsGroupData; impl StorableDsGroupData { pub(super) async fn store(&self, connection: impl PgExecutor<'_>) -> Result<(), StorageError> { sqlx::query!( - "INSERT INTO - encrypted_groups + "INSERT INTO + encrypted_groups (group_id, ciphertext, last_used, deleted_queues) - VALUES + VALUES ($1, $2, $3, $4) ON CONFLICT (group_id) DO NOTHING", self.group_id, @@ -37,11 +37,11 @@ impl StorableDsGroupData { qgid: &QualifiedGroupId, ) -> Result, StorageError> { let Some(group_data_record) = sqlx::query!( - "SELECT + "SELECT group_id, ciphertext, last_used, deleted_queues - FROM + FROM encrypted_groups - WHERE + WHERE group_id = $1", qgid.group_uuid() ) @@ -61,11 +61,11 @@ impl StorableDsGroupData { pub(crate) async fn update(&self, connection: impl PgExecutor<'_>) -> Result<(), StorageError> { sqlx::query!( - "UPDATE + "UPDATE encrypted_groups - SET + SET ciphertext = $2, last_used = $3, deleted_queues = $4 - WHERE + WHERE group_id = $1", self.group_id, PhnxCodec::to_vec(&self.encrypted_group_state)?, @@ -82,9 +82,9 @@ impl StorableDsGroupData { qgid: &QualifiedGroupId, ) -> Result<(), StorageError> { sqlx::query!( - "DELETE FROM + "DELETE FROM encrypted_groups - WHERE + WHERE group_id = $1", qgid.group_uuid() ) @@ -96,10 +96,7 @@ impl StorableDsGroupData { #[cfg(test)] mod test { - use phnxtypes::{ - crypto::ear::Ciphertext, - identifiers::{Fqdn, QualifiedGroupId}, - }; + use phnxtypes::{crypto::ear::Ciphertext, identifiers::QualifiedGroupId}; use sqlx::PgPool; use uuid::Uuid; @@ -113,7 +110,7 @@ mod test { #[sqlx::test] async fn reserve_group_id(pool: PgPool) { - let ds = Ds::new_from_pool(pool, Fqdn::try_from("example.com").unwrap()) + let ds = Ds::new_from_pool(pool, "example.com".parse().unwrap()) .await .expect("Error creating ephemeral Ds instance."); @@ -132,7 +129,7 @@ mod test { #[sqlx::test] async fn group_state_lifecycle(pool: PgPool) { - let ds = Ds::new_from_pool(pool, Fqdn::try_from("example.com").unwrap()) + let ds = Ds::new_from_pool(pool, "example.com".parse().unwrap()) .await .expect("Error creating ephemeral Ds instance."); diff --git a/coreclient/src/clients/mod.rs b/coreclient/src/clients/mod.rs index c1a96687..c88a9165 100644 --- a/coreclient/src/clients/mod.rs +++ b/coreclient/src/clients/mod.rs @@ -38,7 +38,6 @@ use phnxtypes::{ }, identifiers::{ AsClientId, ClientConfig, QsClientId, QsClientReference, QsUserId, QualifiedUserName, - SafeTryInto, }, messages::{ client_as::{ConnectionPackageTbs, UserConnectionPackagesParams}, @@ -118,13 +117,12 @@ impl CoreUser { /// Create a new user with the given `user_name`. If a user with this name /// already exists, this will overwrite that user. pub async fn new( - user_name: impl SafeTryInto, + user_name: QualifiedUserName, password: &str, server_url: impl ToString, db_path: &str, push_token: Option, ) -> Result { - let user_name = user_name.try_into()?; let as_client_id = AsClientId::random(user_name)?; // Open the phnx db to store the client record let phnx_db_connection = open_phnx_db(db_path)?; @@ -619,11 +617,7 @@ impl CoreUser { /// /// Returns the [`ConversationId`] of the newly created connection /// conversation. - pub async fn add_contact( - &self, - user_name: impl SafeTryInto, - ) -> Result { - let user_name = user_name.try_into()?; + pub async fn add_contact(&self, user_name: QualifiedUserName) -> Result { let params = UserConnectionPackagesParams { user_name: user_name.clone(), }; diff --git a/coreclient/src/clients/tests/mod.rs b/coreclient/src/clients/tests/mod.rs index df4ca8ed..baddd97a 100644 --- a/coreclient/src/clients/tests/mod.rs +++ b/coreclient/src/clients/tests/mod.rs @@ -11,10 +11,7 @@ use crate::{ }, }; use phnxserver_test_harness::utils::setup::TestBackend; -use phnxtypes::{ - codec::PhnxCodec, - identifiers::{AsClientId, SafeTryInto}, -}; +use phnxtypes::{codec::PhnxCodec, identifiers::AsClientId}; use rusqlite::Connection; #[actix_rt::test] @@ -23,7 +20,7 @@ async fn user_stages() { let setup = TestBackend::single().await; let user_name = "alice@example.com"; - let as_client_id = AsClientId::random(SafeTryInto::try_into(user_name).unwrap()).unwrap(); + let as_client_id = AsClientId::random(user_name.parse().unwrap()).unwrap(); let phnx_db_connection = Connection::open_in_memory().unwrap(); let mut client_db_connection = Connection::open_in_memory().unwrap(); diff --git a/coreclient/src/conversations/mod.rs b/coreclient/src/conversations/mod.rs index d0d478fc..f2ad8523 100644 --- a/coreclient/src/conversations/mod.rs +++ b/coreclient/src/conversations/mod.rs @@ -7,7 +7,7 @@ use std::fmt::Display; use chrono::{DateTime, Utc}; use openmls::group::GroupId; use phnxtypes::{ - identifiers::{Fqdn, QualifiedGroupId, QualifiedUserName, SafeTryInto}, + identifiers::{Fqdn, QualifiedGroupId, QualifiedUserName}, time::TimeStamp, }; use rusqlite::{ @@ -205,14 +205,12 @@ impl FromSql for ConversationStatus { let Some(user_names) = status.strip_prefix("inactive:") else { return Err(FromSqlError::InvalidType); }; - let user_names = user_names - .split(',') - .map(<&str as SafeTryInto>::try_into) - .collect::, _>>() - .map_err(|error| { - error!(%error, "Failed to parse user names from database"); - FromSqlError::Other(Box::new(error)) - })?; + let user_names: Result, _> = + user_names.split(',').map(|s| s.parse()).collect(); + let user_names = user_names.map_err(|error| { + error!(%error, "Failed to parse user names from database"); + FromSqlError::Other(Box::new(error)) + })?; Ok(Self::Inactive(InactiveConversation::new(user_names))) } } @@ -271,17 +269,15 @@ impl FromSql for ConversationType { }; match conversation_type { "unconfirmed_connection" => Ok(Self::UnconfirmedConnection( - <&str as SafeTryInto>::try_into(user_name).map_err(|error| { - error!(%error, "Failed to parse user name from database"); - FromSqlError::Other(Box::new(error)) - })?, - )), - "connection" => Ok(Self::Connection( - <&str as SafeTryInto>::try_into(user_name).map_err(|error| { + user_name.parse().map_err(|error| { error!(%error, "Failed to parse user name from database"); FromSqlError::Other(Box::new(error)) })?, )), + "connection" => Ok(Self::Connection(user_name.parse().map_err(|error| { + error!(%error, "Failed to parse user name from database"); + FromSqlError::Other(Box::new(error)) + })?)), _ => Err(FromSqlError::InvalidType), } } diff --git a/server/src/main.rs b/server/src/main.rs index 6b64e22b..5d19273a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -17,6 +17,7 @@ use phnxserver::{ telemetry::{get_subscriber, init_subscriber}, }; use phnxtypes::identifiers::Fqdn; +use tracing::info; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -40,24 +41,24 @@ async fn main() -> std::io::Result<()> { let domain: Fqdn = configuration .application .domain - .try_into() - .expect("Invalid domain."); - tracing::info!("Starting server with domain {}.", domain); + .parse() + .expect("Invalid domain"); + info!(%domain, "Starting server"); let network_provider = MockNetworkProvider::new(); let base_db_name = configuration.database.name.clone(); // DS storage provider - configuration.database.name = format!("{}_ds", base_db_name); - tracing::info!( - "Connecting to postgres server at {}.", - configuration.database.host + configuration.database.name = format!("{base_db_name}_ds"); + info!( + host = configuration.database.host, + "Connecting to postgres server", ); let mut counter = 0; let mut ds_result = Ds::new(&configuration.database, domain.clone()).await; // Try again for 10 times each second in case the postgres server is coming up. while let Err(e) = ds_result { - tracing::info!("Failed to connect to postgres server: {}", e); + info!("Failed to connect to postgres server: {}", e); tokio::time::sleep(std::time::Duration::from_secs(1)).await; counter += 1; if counter > 10 { diff --git a/server/tests/mod.rs b/server/tests/mod.rs index 82b372ad..32cc27e8 100644 --- a/server/tests/mod.rs +++ b/server/tests/mod.rs @@ -4,7 +4,7 @@ mod qs; -use std::{fs, io::Cursor}; +use std::{fs, io::Cursor, sync::LazyLock}; use image::{ImageBuffer, Rgba}; use opaque_ke::rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; @@ -16,7 +16,7 @@ use phnxcoreclient::{ }; use phnxserver::network_provider::MockNetworkProvider; use phnxserver_test_harness::utils::{setup::TestBackend, spawn_app}; -use phnxtypes::identifiers::{Fqdn, QualifiedUserName, SafeTryInto}; +use phnxtypes::identifiers::QualifiedUserName; use png::Encoder; #[actix_rt::test] @@ -25,7 +25,7 @@ async fn health_check_works() { tracing::info!("Tracing: Spawning websocket connection task"); let network_provider = MockNetworkProvider::new(); let (address, _ws_dispatch) = - spawn_app(Fqdn::try_from("example.com").unwrap(), network_provider).await; + spawn_app(Some("example.com".parse().unwrap()), network_provider).await; let address = format!("http://{}", address); @@ -36,18 +36,19 @@ async fn health_check_works() { assert!(client.health_check().await); } -const ALICE: &str = "alice@example.com"; -const BOB: &str = "bob@example.com"; -const CHARLIE: &str = "charlie@example.com"; -const DAVE: &str = "dave@example.com"; +static ALICE: LazyLock = LazyLock::new(|| "alice@example.com".parse().unwrap()); +static BOB: LazyLock = LazyLock::new(|| "bob@example.com".parse().unwrap()); +static CHARLIE: LazyLock = + LazyLock::new(|| "charlie@example.com".parse().unwrap()); +static DAVE: LazyLock = LazyLock::new(|| "dave@example.com".parse().unwrap()); #[actix_rt::test] #[tracing::instrument(name = "Connect users test", skip_all)] async fn connect_users() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.connect_users(ALICE, BOB).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.connect_users(&ALICE, &BOB).await; } #[actix_rt::test] @@ -56,34 +57,38 @@ async fn send_message() { tracing::info!("Setting up setup"); let mut setup = TestBackend::single().await; tracing::info!("Creating users"); - setup.add_user(ALICE).await; + setup.add_user(&ALICE).await; tracing::info!("Created alice"); - setup.add_user(BOB).await; - let conversation_id = setup.connect_users(ALICE, BOB).await; - setup.send_message(conversation_id, ALICE, vec![BOB]).await; - setup.send_message(conversation_id, BOB, vec![ALICE]).await; + setup.add_user(&BOB).await; + let conversation_id = setup.connect_users(&ALICE, &BOB).await; + setup + .send_message(conversation_id, &ALICE, vec![&BOB]) + .await; + setup + .send_message(conversation_id, &BOB, vec![&ALICE]) + .await; } #[actix_rt::test] #[tracing::instrument(name = "Create group test", skip_all)] async fn create_group() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.create_group(ALICE).await; + setup.add_user(&ALICE).await; + setup.create_group(&ALICE).await; } #[actix_rt::test] #[tracing::instrument(name = "Invite to group test", skip_all)] async fn invite_to_group() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.add_user(CHARLIE).await; - setup.connect_users(ALICE, BOB).await; - setup.connect_users(ALICE, CHARLIE).await; - let conversation_id = setup.create_group(ALICE).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.add_user(&CHARLIE).await; + setup.connect_users(&ALICE, &BOB).await; + setup.connect_users(&ALICE, &CHARLIE).await; + let conversation_id = setup.create_group(&ALICE).await; setup - .invite_to_group(conversation_id, ALICE, vec![BOB, CHARLIE]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB, &CHARLIE]) .await; } @@ -92,56 +97,50 @@ async fn invite_to_group() { async fn update_group() { let mut setup = TestBackend::single().await; tracing::info!("Adding users"); - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.add_user(CHARLIE).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.add_user(&CHARLIE).await; tracing::info!("Connecting users"); - setup.connect_users(ALICE, BOB).await; - setup.connect_users(ALICE, CHARLIE).await; - let conversation_id = setup.create_group(ALICE).await; + setup.connect_users(&ALICE, &BOB).await; + setup.connect_users(&ALICE, &CHARLIE).await; + let conversation_id = setup.create_group(&ALICE).await; tracing::info!("Inviting to group"); setup - .invite_to_group(conversation_id, ALICE, vec![BOB, CHARLIE]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB, &CHARLIE]) .await; tracing::info!("Updating group"); - setup.update_group(conversation_id, BOB).await + setup.update_group(conversation_id, &BOB).await } #[actix_rt::test] #[tracing::instrument(name = "Remove from group test", skip_all)] async fn remove_from_group() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.add_user(CHARLIE).await; - setup.add_user(DAVE).await; - setup.connect_users(ALICE, BOB).await; - setup.connect_users(ALICE, CHARLIE).await; - setup.connect_users(ALICE, DAVE).await; - let conversation_id = setup.create_group(ALICE).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.add_user(&CHARLIE).await; + setup.add_user(&DAVE).await; + setup.connect_users(&ALICE, &BOB).await; + setup.connect_users(&ALICE, &CHARLIE).await; + setup.connect_users(&ALICE, &DAVE).await; + let conversation_id = setup.create_group(&ALICE).await; setup - .invite_to_group(conversation_id, ALICE, vec![BOB, CHARLIE, DAVE]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB, &CHARLIE, &DAVE]) .await; // Check that Charlie has a user profile stored for BOB, even though // he hasn't connected with them. - let charlie = setup.get_user(CHARLIE); - let bob_user_name = SafeTryInto::try_into(BOB).unwrap(); - let charlie_user_profile_bob = charlie - .user - .user_profile(&bob_user_name) - .await - .unwrap() - .unwrap(); - assert!(charlie_user_profile_bob.user_name() == &bob_user_name); + let charlie = setup.get_user(&CHARLIE); + let charlie_user_profile_bob = charlie.user.user_profile(&BOB).await.unwrap().unwrap(); + assert!(charlie_user_profile_bob.user_name() == &*BOB); setup - .remove_from_group(conversation_id, CHARLIE, vec![ALICE, BOB]) + .remove_from_group(conversation_id, &CHARLIE, vec![&ALICE, &BOB]) .await; // Now that charlie is not in a group with Bob anymore, the user profile // should be removed. - let charlie = setup.get_user(CHARLIE); - let charlie_user_profile_bob = charlie.user.user_profile(&bob_user_name).await.unwrap(); + let charlie = setup.get_user(&CHARLIE); + let charlie_user_profile_bob = charlie.user.user_profile(&BOB).await.unwrap(); assert!(charlie_user_profile_bob.is_none()); } @@ -149,58 +148,64 @@ async fn remove_from_group() { #[tracing::instrument(name = "Re-add to group test", skip_all)] async fn re_add_client() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.connect_users(ALICE, BOB).await; - let conversation_id = setup.create_group(ALICE).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.connect_users(&ALICE, &BOB).await; + let conversation_id = setup.create_group(&ALICE).await; setup - .invite_to_group(conversation_id, ALICE, vec![BOB]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB]) .await; for _ in 0..10 { setup - .remove_from_group(conversation_id, ALICE, vec![BOB]) + .remove_from_group(conversation_id, &ALICE, vec![&BOB]) .await; setup - .invite_to_group(conversation_id, ALICE, vec![BOB]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB]) .await; } - setup.send_message(conversation_id, ALICE, vec![BOB]).await; - setup.send_message(conversation_id, BOB, vec![ALICE]).await; + setup + .send_message(conversation_id, &ALICE, vec![&BOB]) + .await; + setup + .send_message(conversation_id, &BOB, vec![&ALICE]) + .await; } #[actix_rt::test] #[tracing::instrument(name = "Invite to group test", skip_all)] async fn leave_group() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.connect_users(ALICE, BOB).await; - let conversation_id = setup.create_group(ALICE).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.connect_users(&ALICE, &BOB).await; + let conversation_id = setup.create_group(&ALICE).await; setup - .invite_to_group(conversation_id, ALICE, vec![BOB]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB]) .await; - setup.leave_group(conversation_id, ALICE).await; + setup.leave_group(conversation_id, &ALICE).await; } #[actix_rt::test] #[tracing::instrument(name = "Invite to group test", skip_all)] async fn delete_group() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.connect_users(ALICE, BOB).await; - let conversation_id = setup.create_group(ALICE).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.connect_users(&ALICE, &BOB).await; + let conversation_id = setup.create_group(&ALICE).await; setup - .invite_to_group(conversation_id, ALICE, vec![BOB]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB]) .await; - setup.delete_group(conversation_id, BOB).await; + let bob = &BOB; + let delete_group = setup.delete_group(conversation_id, bob); + delete_group.await; } #[actix_rt::test] #[tracing::instrument(name = "Create user", skip_all)] async fn create_user() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; + setup.add_user(&ALICE).await; } #[actix_rt::test] @@ -208,7 +213,7 @@ async fn create_user() { async fn inexistant_endpoint() { let network_provider = MockNetworkProvider::new(); let (address, _ws_dispatch) = - spawn_app(Fqdn::try_from("localhost").unwrap(), network_provider).await; + spawn_app(Some("localhost".parse().unwrap()), network_provider).await; // Initialize the client let address = format!("http://{}", address); @@ -223,54 +228,54 @@ async fn inexistant_endpoint() { async fn full_cycle() { let mut setup = TestBackend::single().await; // Create alice and bob - setup.add_user(ALICE).await; - setup.add_user(BOB).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; // Connect them - let conversation_alice_bob = setup.connect_users(ALICE, BOB).await; + let conversation_alice_bob = setup.connect_users(&ALICE, &BOB).await; // Test the connection conversation by sending messages back and forth. setup - .send_message(conversation_alice_bob, ALICE, vec![BOB]) + .send_message(conversation_alice_bob, &ALICE, vec![&BOB]) .await; setup - .send_message(conversation_alice_bob, BOB, vec![ALICE]) + .send_message(conversation_alice_bob, &BOB, vec![&ALICE]) .await; // Create an independent group and invite bob. - let conversation_id = setup.create_group(ALICE).await; + let conversation_id = setup.create_group(&ALICE).await; setup - .invite_to_group(conversation_id, ALICE, vec![BOB]) + .invite_to_group(conversation_id, &ALICE, vec![&BOB]) .await; // Create chalie, connect him with alice and invite him to the group. - setup.add_user(CHARLIE).await; - setup.connect_users(ALICE, CHARLIE).await; + setup.add_user(&CHARLIE).await; + setup.connect_users(&ALICE, &CHARLIE).await; setup - .invite_to_group(conversation_id, ALICE, vec![CHARLIE]) + .invite_to_group(conversation_id, &ALICE, vec![&CHARLIE]) .await; // Add dave, connect him with charlie and invite him to the group. Then have dave remove alice and bob. - setup.add_user(DAVE).await; - setup.connect_users(CHARLIE, DAVE).await; + setup.add_user(&DAVE).await; + setup.connect_users(&CHARLIE, &DAVE).await; setup - .invite_to_group(conversation_id, CHARLIE, vec![DAVE]) + .invite_to_group(conversation_id, &CHARLIE, vec![&DAVE]) .await; setup - .send_message(conversation_id, ALICE, vec![CHARLIE, BOB, DAVE]) + .send_message(conversation_id, &ALICE, vec![&CHARLIE, &BOB, &DAVE]) .await; setup - .remove_from_group(conversation_id, DAVE, vec![ALICE, BOB]) + .remove_from_group(conversation_id, &DAVE, vec![&ALICE, &BOB]) .await; - setup.leave_group(conversation_id, CHARLIE).await; + setup.leave_group(conversation_id, &CHARLIE).await; - setup.delete_group(conversation_id, DAVE).await + setup.delete_group(conversation_id, &DAVE).await } #[actix_rt::test] @@ -281,20 +286,20 @@ async fn benchmarks() { const NUM_MESSAGES: usize = 10; // Create alice - setup.add_user(ALICE).await; + setup.add_user(&ALICE).await; // Create bob - setup.add_user(BOB).await; + setup.add_user(&BOB).await; // Create many different bobs - let bobs: Vec = (0..NUM_USERS) - .map(|i| format!("bob{}@example.com", i)) - .collect::>(); + let bobs: Vec = (0..NUM_USERS) + .map(|i| format!("bob{i}@example.com").parse().unwrap()) + .collect(); // Measure the time it takes to create all the users let start = std::time::Instant::now(); for bob in bobs.clone() { - setup.add_user(bob).await; + setup.add_user(&bob).await; } let elapsed = start.elapsed(); println!( @@ -306,7 +311,7 @@ async fn benchmarks() { // Measure the time it takes to connect all bobs with alice let start = std::time::Instant::now(); for bob in bobs.clone() { - setup.connect_users(ALICE, bob).await; + setup.connect_users(&ALICE, &bob).await; } let elapsed = start.elapsed(); println!( @@ -316,13 +321,13 @@ async fn benchmarks() { ); // Connect them - let conversation_alice_bob = setup.connect_users(ALICE, BOB).await; + let conversation_alice_bob = setup.connect_users(&ALICE, &BOB).await; // Measure the time it takes to send a message let start = std::time::Instant::now(); for _ in 0..NUM_MESSAGES { setup - .send_message(conversation_alice_bob, ALICE, vec![BOB]) + .send_message(conversation_alice_bob, &ALICE, vec![&BOB]) .await; } let elapsed = start.elapsed(); @@ -333,13 +338,13 @@ async fn benchmarks() { ); // Create an independent group - let conversation_id = setup.create_group(ALICE).await; + let conversation_id = setup.create_group(&ALICE).await; // Measure the time it takes to invite a user let start = std::time::Instant::now(); for bob in bobs.clone() { setup - .invite_to_group(conversation_id, ALICE, vec![bob]) + .invite_to_group(conversation_id, &ALICE, vec![&bob]) .await; } let elapsed = start.elapsed(); @@ -353,7 +358,7 @@ async fn benchmarks() { let start = std::time::Instant::now(); for _ in 0..NUM_MESSAGES { setup - .send_message(conversation_id, ALICE, bobs.clone()) + .send_message(conversation_id, &ALICE, bobs.iter().collect()) .await; } let elapsed = start.elapsed(); @@ -368,10 +373,9 @@ async fn benchmarks() { #[tracing::instrument(name = "User profile exchange test", skip_all)] async fn exchange_user_profiles() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; + setup.add_user(&ALICE).await; // Set a user profile for alice - let alice_user_name: QualifiedUserName = SafeTryInto::try_into(ALICE).unwrap(); let alice_display_name = DisplayName::try_from("4l1c3".to_string()).unwrap(); // Create a new ImgBuf with width: 1px and height: 1px @@ -400,44 +404,43 @@ async fn exchange_user_profiles() { let alice_profile_picture = Asset::Value(png_bytes.clone()); let alice_profile = UserProfile::new( - alice_user_name.clone(), + (*ALICE).clone(), Some(alice_display_name.clone()), Some(alice_profile_picture.clone()), ); setup .users - .get(&alice_user_name) + .get(&ALICE) .unwrap() .user .set_own_user_profile(alice_profile) .await .unwrap(); - setup.add_user(BOB).await; + setup.add_user(&BOB).await; // Set a user profile for - let bob_user_name: QualifiedUserName = SafeTryInto::try_into(BOB).unwrap(); let bob_display_name = DisplayName::try_from("B0b".to_string()).unwrap(); let bob_profile_picture = Asset::Value(png_bytes.clone()); let bob_user_profile = UserProfile::new( - bob_user_name.clone(), + (*BOB).clone(), Some(bob_display_name.clone()), Some(bob_profile_picture.clone()), ); - let user = &setup.users.get(&bob_user_name).unwrap().user; + let user = &setup.users.get(&BOB).unwrap().user; user.set_own_user_profile(bob_user_profile).await.unwrap(); let new_profile = user.own_user_profile().await.unwrap(); let Asset::Value(compressed_profile_picture) = new_profile.profile_picture().unwrap().clone(); - setup.connect_users(ALICE, BOB).await; + setup.connect_users(&ALICE, &BOB).await; let bob_user_profile = setup .users - .get(&alice_user_name) + .get(&ALICE) .unwrap() .user - .user_profile(&bob_user_name) + .user_profile(&BOB) .await .unwrap() .unwrap(); @@ -456,10 +459,10 @@ async fn exchange_user_profiles() { let alice_user_profile = setup .users - .get(&bob_user_name) + .get(&BOB) .unwrap() .user - .user_profile(&alice_user_name) + .user_profile(&ALICE) .await .unwrap() .unwrap(); @@ -474,15 +477,12 @@ async fn exchange_user_profiles() { #[tracing::instrument(name = "Message retrieval test", skip_all)] async fn retrieve_conversation_messages() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; - let conversation_id = setup.connect_users(ALICE, BOB).await; + let conversation_id = setup.connect_users(&ALICE, &BOB).await; - let alice_test_user = setup - .users - .get_mut(&SafeTryInto::try_into(ALICE).unwrap()) - .unwrap(); + let alice_test_user = setup.users.get_mut(&ALICE).unwrap(); let alice = &mut alice_test_user.user; let number_of_messages = 10; @@ -505,7 +505,7 @@ async fn retrieve_conversation_messages() { // Let's see what Alice's messages for this conversation look like. let messages_retrieved = setup .users - .get(&SafeTryInto::try_into(ALICE).unwrap()) + .get(&ALICE) .unwrap() .user .get_messages(conversation_id, number_of_messages) @@ -520,17 +520,14 @@ async fn retrieve_conversation_messages() { #[tracing::instrument(name = "Marking messages as read test", skip_all)] async fn mark_as_read() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - setup.add_user(BOB).await; - setup.add_user(CHARLIE).await; + setup.add_user(&ALICE).await; + setup.add_user(&BOB).await; + setup.add_user(&CHARLIE).await; - let alice_bob_conversation = setup.connect_users(ALICE, BOB).await; - let bob_charlie_conversation = setup.connect_users(BOB, CHARLIE).await; + let alice_bob_conversation = setup.connect_users(&ALICE, &BOB).await; + let bob_charlie_conversation = setup.connect_users(&BOB, &CHARLIE).await; - let charlie_test_user = setup - .users - .get_mut(&SafeTryInto::try_into(ALICE).unwrap()) - .unwrap(); + let charlie_test_user = setup.users.get_mut(&ALICE).unwrap(); let alice = &mut charlie_test_user.user; // Send a few messages @@ -560,10 +557,7 @@ async fn mark_as_read() { let number_of_messages = 10; send_messages(alice, alice_bob_conversation, number_of_messages).await; - let bob_test_user = setup - .users - .get_mut(&SafeTryInto::try_into(BOB).unwrap()) - .unwrap(); + let bob_test_user = setup.users.get_mut(&BOB).unwrap(); let bob = &mut bob_test_user.user; // All messages should be unread @@ -581,17 +575,11 @@ async fn mark_as_read() { // Let's send some messages between bob and charlie s.t. we can test the // global unread messages count. - let charlie_test_user = setup - .users - .get_mut(&SafeTryInto::try_into(CHARLIE).unwrap()) - .unwrap(); + let charlie_test_user = setup.users.get_mut(&CHARLIE).unwrap(); let charlie = &mut charlie_test_user.user; let messages_sent = send_messages(charlie, bob_charlie_conversation, number_of_messages).await; - let bob_test_user = setup - .users - .get_mut(&SafeTryInto::try_into(BOB).unwrap()) - .unwrap(); + let bob_test_user = setup.users.get_mut(&BOB).unwrap(); let bob = &mut bob_test_user.user; let qs_messages = bob.qs_fetch_messages().await.unwrap(); @@ -627,13 +615,8 @@ async fn mark_as_read() { async fn client_persistence() { // Create and persist the user. let mut setup = TestBackend::single().await; - setup.add_persisted_user(ALICE).await; - let client_id = setup - .users - .get(&SafeTryInto::try_into(ALICE).unwrap()) - .unwrap() - .user - .as_client_id(); + setup.add_persisted_user(&ALICE).await; + let client_id = setup.users.get(&ALICE).unwrap().user.as_client_id(); // Try to load the user from the database. let user_result = CoreUser::load(client_id.clone(), "./").await.unwrap(); @@ -649,14 +632,12 @@ async fn client_persistence() { #[tracing::instrument(name = "Test server error if unknown user", skip_all)] async fn error_if_user_doesnt_exist() { let mut setup = TestBackend::single().await; - setup.add_user(ALICE).await; - let alice_test = setup - .users - .get_mut(&SafeTryInto::try_into(ALICE).unwrap()) - .unwrap(); + + setup.add_user(&ALICE).await; + let alice_test = setup.users.get_mut(&ALICE).unwrap(); let alice = &mut alice_test.user; - let res = alice.add_contact(BOB).await; + let res = alice.add_contact((*BOB).clone()).await; assert!(res.is_err()); } diff --git a/server/tests/qs/ws.rs b/server/tests/qs/ws.rs index 16a2e791..779c996d 100644 --- a/server/tests/qs/ws.rs +++ b/server/tests/qs/ws.rs @@ -7,10 +7,7 @@ use phnxapiclient::{qs_api::ws::WsEvent, ApiClient}; use phnxbackend::qs::{WebsocketNotifier, WsNotification}; use phnxserver::network_provider::MockNetworkProvider; use phnxserver_test_harness::utils::spawn_app; -use phnxtypes::{ - identifiers::{Fqdn, QsClientId}, - messages::client_ds::QsWsMessage, -}; +use phnxtypes::{identifiers::QsClientId, messages::client_ds::QsWsMessage}; /// Test the websocket reconnect. #[actix_rt::test] @@ -18,7 +15,7 @@ use phnxtypes::{ async fn ws_reconnect() { let network_provider = MockNetworkProvider::new(); let (address, _ws_dispatch) = - spawn_app(Fqdn::try_from("example.com").unwrap(), network_provider).await; + spawn_app(Some("example.com".parse().unwrap()), network_provider).await; let client_id = QsClientId::random(&mut OsRng); @@ -56,7 +53,7 @@ async fn ws_reconnect() { async fn ws_sending() { let network_provider = MockNetworkProvider::new(); let (address, ws_dispatch) = - spawn_app(Fqdn::try_from("example.com").unwrap(), network_provider).await; + spawn_app(Some("example.com".parse().unwrap()), network_provider).await; let client_id = QsClientId::random(&mut OsRng); diff --git a/test_harness/src/docker/mod.rs b/test_harness/src/docker/mod.rs index 9fe41332..215955f5 100644 --- a/test_harness/src/docker/mod.rs +++ b/test_harness/src/docker/mod.rs @@ -51,16 +51,16 @@ impl DockerTestBed { let network_name = format!("{scenario}_network"); // Create docker network create_network(&network_name); - let servers = (0..scenario.number_of_servers()) + let servers: HashMap<_, _> = (0..scenario.number_of_servers()) .map(|index| { - let domain = format!("{}{}.com", scenario, index) - .try_into() + let domain = format!("{scenario}{index}.com") + .parse() .expect("Invalid domain"); tracing::info!("Starting server {domain}"); let server = create_and_start_server_container(&domain, Some(&network_name)); (domain.clone(), server) }) - .collect::>(); + .collect(); Self { servers, diff --git a/test_harness/src/main.rs b/test_harness/src/main.rs index 85ca9a02..e92113f2 100644 --- a/test_harness/src/main.rs +++ b/test_harness/src/main.rs @@ -42,7 +42,7 @@ async fn main() -> ExitCode { let mut counter = 0; let mut domains = HashSet::new(); while let Ok(domain_name) = std::env::var(format!("PHNX_SERVER_{}", counter)) { - domains.insert(domain_name.try_into().expect("Invalid domain name")); + domains.insert(domain_name.parse().expect("Invalid domain name")); counter += 1; } if !wait_until_servers_are_up(domains.clone()).await { diff --git a/test_harness/src/test_scenarios/basic_group_operations.rs b/test_harness/src/test_scenarios/basic_group_operations.rs index d41eecef..c4b0b120 100644 --- a/test_harness/src/test_scenarios/basic_group_operations.rs +++ b/test_harness/src/test_scenarios/basic_group_operations.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use phnxtypes::identifiers::Fqdn; +use phnxtypes::identifiers::{Fqdn, QualifiedUserName}; use crate::utils::setup::TestBackend; @@ -13,9 +13,9 @@ pub(super) const NUMBER_OF_SERVERS: usize = 2; impl TestBed { async fn create_alice_and_bob(&mut self, domains: &[Fqdn]) -> (String, String) { let alice_name = format!("alice@{}", domains[0]); - self.add_user(alice_name.clone()).await; + self.add_user(&alice_name.parse().unwrap()).await; let bob_name = format!("bob@{}", domains[1]); - self.add_user(bob_name.clone()).await; + self.add_user(&bob_name.parse().unwrap()).await; (alice_name, bob_name) } @@ -24,7 +24,8 @@ impl TestBed { domains: &[Fqdn], ) -> (String, String) { let (alice_name, bob_name) = self.create_alice_and_bob(domains).await; - self.connect_users(&alice_name, &bob_name).await; + self.connect_users(&alice_name.parse().unwrap(), &bob_name.parse().unwrap()) + .await; (alice_name, bob_name) } } @@ -41,30 +42,36 @@ pub async fn connect_users_runner(domains: &[Fqdn]) { pub async fn invite_to_group_runner(domains: &[Fqdn]) { let mut test_bed = TestBed::federated(); let (alice_name, bob_name) = test_bed.create_and_connect_alice_and_bob(domains).await; - let conversation_id = test_bed.create_group(&alice_name).await; + let alice = alice_name.parse().unwrap(); + let bob = bob_name.parse().unwrap(); + let conversation_id = test_bed.create_group(&alice).await; test_bed - .invite_to_group(conversation_id, &alice_name, vec![&bob_name]) + .invite_to_group(conversation_id, &alice, vec![&bob]) .await; } pub async fn remove_from_group_runner(domains: &[Fqdn]) { let mut test_bed = TestBed::federated(); let (alice_name, bob_name) = test_bed.create_and_connect_alice_and_bob(domains).await; - let conversation_id = test_bed.create_group(&alice_name).await; + let alice: QualifiedUserName = alice_name.parse().unwrap(); + let bob: QualifiedUserName = bob_name.parse().unwrap(); + let conversation_id = test_bed.create_group(&alice).await; test_bed - .invite_to_group(conversation_id, &alice_name, vec![&bob_name]) + .invite_to_group(conversation_id, &alice, vec![&bob]) .await; test_bed - .remove_from_group(conversation_id, &alice_name, vec![&bob_name]) + .remove_from_group(conversation_id, &alice, vec![&bob]) .await; } pub async fn leave_group_runner(domains: &[Fqdn]) { let mut test_bed = TestBed::federated(); let (alice_name, bob_name) = test_bed.create_and_connect_alice_and_bob(domains).await; - let conversation_id = test_bed.create_group(&alice_name).await; + let alice: QualifiedUserName = alice_name.parse().unwrap(); + let bob: QualifiedUserName = bob_name.parse().unwrap(); + let conversation_id = test_bed.create_group(&alice).await; test_bed - .invite_to_group(conversation_id, &alice_name, vec![&bob_name]) + .invite_to_group(conversation_id, &alice, vec![&bob]) .await; - test_bed.leave_group(conversation_id, &bob_name).await + test_bed.leave_group(conversation_id, &bob).await } diff --git a/test_harness/src/test_scenarios/federated_group_operations.rs b/test_harness/src/test_scenarios/federated_group_operations.rs index c5979c37..6185b863 100644 --- a/test_harness/src/test_scenarios/federated_group_operations.rs +++ b/test_harness/src/test_scenarios/federated_group_operations.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use phnxtypes::identifiers::Fqdn; +use phnxtypes::identifiers::{Fqdn, QualifiedUserName}; use crate::utils::setup::TestBackend; @@ -16,29 +16,34 @@ pub async fn group_operations_runner(domains: &[Fqdn]) { // Create and connect alice and bob. let (alice_name, bob_name) = test_bed.create_and_connect_alice_and_bob(domains).await; let charlie_name = format!("charlie@{}", domains[2]); - test_bed.add_user(charlie_name.clone()).await; + + let alice: QualifiedUserName = alice_name.parse().unwrap(); + let bob: QualifiedUserName = bob_name.parse().unwrap(); + let charlie: QualifiedUserName = charlie_name.parse().unwrap(); + + test_bed.add_user(&charlie).await; // Connect bob and charlie. - test_bed.connect_users(&bob_name, &charlie_name).await; + test_bed.connect_users(&bob, &charlie).await; // Have alice create a group. - let conversation_id = test_bed.create_group(&alice_name).await; + let conversation_id = test_bed.create_group(&alice).await; // Have alice invite bob test_bed - .invite_to_group(conversation_id, &alice_name, vec![&bob_name]) + .invite_to_group(conversation_id, &alice, vec![&bob]) .await; // Have bob invite charlie test_bed - .invite_to_group(conversation_id, &bob_name, vec![&charlie_name]) + .invite_to_group(conversation_id, &bob, vec![&charlie]) .await; // Have charlie remove alice test_bed - .remove_from_group(conversation_id, &charlie_name, vec![&alice_name]) + .remove_from_group(conversation_id, &charlie, vec![&alice]) .await; // Have bob leave the group - test_bed.leave_group(conversation_id, &bob_name).await; + test_bed.leave_group(conversation_id, &bob).await; } diff --git a/test_harness/src/test_scenarios/randomized_operations.rs b/test_harness/src/test_scenarios/randomized_operations.rs index 80fbd1c5..b490ec20 100644 --- a/test_harness/src/test_scenarios/randomized_operations.rs +++ b/test_harness/src/test_scenarios/randomized_operations.rs @@ -2,8 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use phnxtypes::identifiers::Fqdn; +use phnxtypes::identifiers::{Fqdn, QualifiedUserName}; use rand::{seq::SliceRandom, SeedableRng}; +use tracing::info; use crate::utils::setup::TestBackend; @@ -14,15 +15,14 @@ pub(super) const NUMBER_OF_SERVERS: usize = 3; pub async fn randomized_operations_runner(domains: &[Fqdn]) { // Check if a specific seed was set manually. let randomness_seed: u64 = if let Ok(seed) = std::env::var("PHNX_TEST_RANDOM_SEED") { - tracing::info!("setting seed manually from environment"); + info!("setting seed manually from environment"); seed.parse().unwrap() } else { rand::random() }; - tracing::info!( + info!( random_operation = true, - "randomness_seed: {}", - randomness_seed + "randomness_seed: {}", randomness_seed ); let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(randomness_seed as u64); let mut test_bed = TestBed::federated(); @@ -30,16 +30,16 @@ pub async fn randomized_operations_runner(domains: &[Fqdn]) { // Pick a random domain let domain = domains.choose(&mut rng).unwrap(); // Just count the users to avoid collisions - let user_name = format!("{}@{}", index, domain); - tracing::info!( + let user_name: QualifiedUserName = format!("{index}@{domain}").parse().unwrap(); + info!( random_operation = true, - "Random operation: Creating user {}", - user_name + %user_name, + "Random operation: Creating user", ); - test_bed.add_user(user_name).await; + test_bed.add_user(&user_name).await; } for _index in 0..100 { test_bed.perform_random_operation(&mut rng).await; } - tracing::info!("Done testing with randomness_seed: {}", randomness_seed); + info!("Done testing with randomness_seed: {}", randomness_seed); } diff --git a/test_harness/src/utils/mod.rs b/test_harness/src/utils/mod.rs index 6faed748..7f3e8ce2 100644 --- a/test_harness/src/utils/mod.rs +++ b/test_harness/src/utils/mod.rs @@ -58,7 +58,7 @@ pub async fn spawn_app( let host = configuration.application.host; let listener = TcpListener::bind(format!("{host}:{port}")).expect("Failed to bind to random port."); - let domain = domain.into().unwrap_or_else(|| host.try_into().unwrap()); + let domain = domain.into().unwrap_or_else(|| host.parse().unwrap()); let address = listener.local_addr().unwrap(); let ws_dispatch_notifier = DispatchWebsocketNotifier::default_addr(); diff --git a/test_harness/src/utils/setup.rs b/test_harness/src/utils/setup.rs index 33c8827c..a2cfed6d 100644 --- a/test_harness/src/utils/setup.rs +++ b/test_harness/src/utils/setup.rs @@ -7,11 +7,12 @@ use std::collections::{HashMap, HashSet}; use phnxcoreclient::{clients::CoreUser, ConversationId, ConversationStatus, ConversationType, *}; use phnxserver::network_provider::MockNetworkProvider; use phnxtypes::{ - identifiers::{Fqdn, QualifiedUserName, SafeTryInto}, + identifiers::{Fqdn, QualifiedUserName}, DEFAULT_PORT_HTTP, }; use rand::{distributions::Alphanumeric, seq::IteratorRandom, Rng, RngCore}; use rand_chacha::rand_core::OsRng; +use tracing::info; use super::spawn_app; @@ -94,7 +95,7 @@ impl TestBackend { } pub async fn single() -> Self { let network_provider = MockNetworkProvider::new(); - let domain = Fqdn::try_from("example.com").unwrap(); + let domain: Fqdn = "example.com".parse().unwrap(); let (address, _ws_dispatch) = spawn_app(domain.clone(), network_provider).await; Self { users: HashMap::new(), @@ -111,23 +112,20 @@ impl TestBackend { } } - pub async fn add_persisted_user(&mut self, user_name: impl SafeTryInto) { - let user_name = user_name.try_into().unwrap(); - tracing::info!("Creating {user_name}"); - let user = TestUser::new_persisted(&user_name, self.url(), "./").await; - self.users.insert(user_name, user); + pub async fn add_persisted_user(&mut self, user_name: &QualifiedUserName) { + info!("Creating {user_name}"); + let user = TestUser::new_persisted(user_name, self.url(), "./").await; + self.users.insert(user_name.clone(), user); } - pub async fn add_user(&mut self, user_name: impl SafeTryInto) { - let user_name = user_name.try_into().unwrap(); - tracing::info!("Creating {user_name}"); - let user = TestUser::new(&user_name, self.url()).await; - self.users.insert(user_name, user); + pub async fn add_user(&mut self, user_name: &QualifiedUserName) { + info!("Creating {user_name}"); + let user = TestUser::new(user_name, self.url()).await; + self.users.insert(user_name.clone(), user); } - pub fn get_user(&self, user_name: impl SafeTryInto) -> &TestUser { - let user_name = user_name.try_into().unwrap(); - self.users.get(&user_name).unwrap() + pub fn get_user(&self, user_name: &QualifiedUserName) -> &TestUser { + self.users.get(user_name).unwrap() } /// This has the updater commit an update, but without the checks ensuring @@ -135,16 +133,15 @@ impl TestBackend { pub async fn commit_to_proposals( &mut self, conversation_id: ConversationId, - updater_name: impl SafeTryInto, + updater_name: QualifiedUserName, ) { - let updater_name = &updater_name.try_into().unwrap(); - tracing::info!( + info!( "{} performs an update in group {}", updater_name, conversation_id.as_uuid() ); - let test_updater = self.users.get_mut(updater_name).unwrap(); + let test_updater = self.users.get_mut(&updater_name).unwrap(); let updater = &mut test_updater.user; let pending_removes = HashSet::::from_iter( @@ -171,7 +168,7 @@ impl TestBackend { // Have all group members fetch and process messages. for group_member_name in group_members.iter() { // skip the sender - if group_member_name == updater_name { + if group_member_name == &updater_name { continue; } let test_group_member = self.users.get_mut(group_member_name).unwrap(); @@ -218,10 +215,9 @@ impl TestBackend { pub async fn update_group( &mut self, conversation_id: ConversationId, - updater_name: impl SafeTryInto, + updater_name: &QualifiedUserName, ) { - let updater_name = &updater_name.try_into().unwrap(); - tracing::info!( + info!( "{} performs an update in group {}", updater_name, conversation_id.as_uuid() @@ -263,13 +259,11 @@ impl TestBackend { pub async fn connect_users( &mut self, - user1_name: impl SafeTryInto, - user2_name: impl SafeTryInto, + user1_name: &QualifiedUserName, + user2_name: &QualifiedUserName, ) -> ConversationId { - let user1_name = user1_name.try_into().unwrap(); - let user2_name = user2_name.try_into().unwrap(); - tracing::info!("Connecting users {} and {}", user1_name, user2_name); - let test_user1 = self.users.get_mut(&user1_name).unwrap(); + info!("Connecting users {} and {}", user1_name, user2_name); + let test_user1 = self.users.get_mut(user1_name).unwrap(); let user1 = &mut test_user1.user; let user1_partial_contacts_before = user1.partial_contacts().await.unwrap(); let user1_conversations_before = user1.conversations().await.unwrap(); @@ -281,7 +275,7 @@ impl TestBackend { ); let new_user_position = user1_partial_contacts_after .iter() - .position(|c| c.user_name == user2_name) + .position(|c| &c.user_name == user2_name) .expect(&error_msg); // If we remove the new user, the partial contact lists should be the same. user1_partial_contacts_after.remove(new_user_position); @@ -311,25 +305,25 @@ impl TestBackend { }); let user1_conversation_id = conversation.id(); - let test_user2 = self.users.get_mut(&user2_name).unwrap(); + let test_user2 = self.users.get_mut(user2_name).unwrap(); let user2 = &mut test_user2.user; let user2_contacts_before = user2.contacts().await.unwrap(); let user2_conversations_before = user2.conversations().await.unwrap(); - tracing::info!("{} fetches AS messages", user2_name); + info!("{} fetches AS messages", user2_name); let as_messages = user2.as_fetch_messages().await.unwrap(); - tracing::info!("{} processes AS messages", user2_name); + info!("{} processes AS messages", user2_name); user2.fully_process_as_messages(as_messages).await.unwrap(); // User 2 should have auto-accepted (for now at least) the connection request. let mut user2_contacts_after = user2.contacts().await.unwrap(); - tracing::info!("User 2 contacts after: {:?}", user2_contacts_after); + info!("User 2 contacts after: {:?}", user2_contacts_after); let user2_partial_contacts_before = user2.partial_contacts().await.unwrap(); - tracing::info!( + info!( "User 2 partial contacts after: {:?}", user2_partial_contacts_before ); let new_contact_position = user2_contacts_after .iter() - .position(|c| c.user_name == user1_name) + .position(|c| &c.user_name == user1_name) .expect("User 1 should be in the partial contacts list of user 2"); // If we remove the new user, the partial contact lists should be the same. user2_contacts_after.remove(new_contact_position); @@ -341,7 +335,7 @@ impl TestBackend { }); // User 2 should have created a connection group. let mut user2_conversations_after = user2.conversations().await.unwrap(); - tracing::info!( + info!( "User 2 conversations after: {:?}", user2_conversations_after ); @@ -363,7 +357,7 @@ impl TestBackend { let user2_conversation_id = conversation.id(); let user2_user_name = user2.user_name().clone(); - let test_user1 = self.users.get_mut(&user1_name).unwrap(); + let test_user1 = self.users.get_mut(user1_name).unwrap(); let user1 = &mut test_user1.user; let user1_contacts_before: HashSet<_> = user1 .contacts() @@ -373,9 +367,9 @@ impl TestBackend { .map(|contact| contact.user_name.clone()) .collect(); let user1_conversations_before = user1.conversations().await.unwrap(); - tracing::info!("{} fetches QS messages", user1_name); + info!("{} fetches QS messages", user1_name); let qs_messages = user1.qs_fetch_messages().await.unwrap(); - tracing::info!("{} processes QS messages", user1_name); + info!("{} processes QS messages", user1_name); user1.fully_process_qs_messages(qs_messages).await.unwrap(); // User 1 should have added user 2 to its contacts now and a connection @@ -409,7 +403,7 @@ impl TestBackend { let user1_unread_messages = self .users - .get_mut(&user1_name) + .get_mut(user1_name) .unwrap() .user .unread_messages_count(user1_conversation_id) @@ -417,32 +411,24 @@ impl TestBackend { assert_eq!(user1_unread_messages, 0); // Send messages both ways to ensure it works. - self.send_message( - user1_conversation_id, - user1_name.clone(), - vec![user2_name.clone()], - ) - .await; + self.send_message(user1_conversation_id, user1_name, vec![user2_name]) + .await; let user1_unread_messages = self .users - .get_mut(&user1_name) + .get_mut(user1_name) .unwrap() .user .unread_messages_count(user1_conversation_id) .await; assert_eq!(user1_unread_messages, 0); - self.send_message( - user1_conversation_id, - user2_name.clone(), - vec![user1_name.clone()], - ) - .await; + self.send_message(user1_conversation_id, user2_name, vec![user1_name]) + .await; let user1_unread_messages = self .users - .get_mut(&user1_name) + .get_mut(user1_name) .unwrap() .user .unread_messages_count(user1_conversation_id) @@ -450,7 +436,7 @@ impl TestBackend { assert_eq!(user1_unread_messages, 1); // Fetch the last message and mark it as read. - let test_user1 = self.users.get_mut(&user1_name).unwrap(); + let test_user1 = self.users.get_mut(user1_name).unwrap(); let user1 = &mut test_user1.user; let user1_messages = user1.get_messages(user1_conversation_id, 1).await.unwrap(); @@ -468,7 +454,7 @@ impl TestBackend { let user1_unread_messages = user1.unread_messages_count(user1_conversation_id).await; assert_eq!(user1_unread_messages, 0); - let test_user2 = self.users.get_mut(&user2_name).unwrap(); + let test_user2 = self.users.get_mut(user2_name).unwrap(); let user2 = &mut test_user2.user; let user2_messages = user2.get_messages(user2_conversation_id, 1).await.unwrap(); @@ -482,7 +468,8 @@ impl TestBackend { let user2_unread_messages = user2.unread_messages_count(user2_conversation_id).await; assert_eq!(user2_unread_messages, 0); - let member_set: HashSet = [user1_name, user2_name].into(); + let member_set: HashSet = + [user1_name.clone(), user2_name.clone()].into(); assert_eq!(member_set.len(), 2); self.groups.insert(user1_conversation_id, member_set); user1_conversation_id @@ -494,19 +481,14 @@ impl TestBackend { pub async fn send_message( &mut self, conversation_id: ConversationId, - sender_name: impl SafeTryInto, - recipient_names: Vec>, + sender_name: &QualifiedUserName, + recipient_names: Vec<&QualifiedUserName>, ) -> ConversationMessageId { - let sender_name = sender_name.try_into().unwrap(); - let recipient_names: Vec = recipient_names - .into_iter() - .map(|name| name.try_into().unwrap()) - .collect::>(); let recipient_strings = recipient_names .iter() .map(|n| n.to_string()) .collect::>(); - tracing::info!( + info!( "{} sends a message to {}", sender_name, recipient_strings.join(", ") @@ -517,7 +499,7 @@ impl TestBackend { .map(char::from) .collect(); let orig_message = MimiContent::simple_markdown_message(sender_name.domain(), message); - let test_sender = self.users.get_mut(&sender_name).unwrap(); + let test_sender = self.users.get_mut(sender_name).unwrap(); let sender = &mut test_sender.user; // Before sending a message, the sender must first fetch and process its QS messages. @@ -568,12 +550,8 @@ impl TestBackend { message.id() } - pub async fn create_group( - &mut self, - user_name: impl SafeTryInto, - ) -> ConversationId { - let user_name = user_name.try_into().unwrap(); - let test_user = self.users.get_mut(&user_name).unwrap(); + pub async fn create_group(&mut self, user_name: &QualifiedUserName) -> ConversationId { + let test_user = self.users.get_mut(user_name).unwrap(); let user = &mut test_user.user; let user_conversations_before = user.conversations().await.unwrap(); @@ -603,7 +581,7 @@ impl TestBackend { .for_each(|(before, after)| { assert_eq!(before.id(), after.id()); }); - let member_set: HashSet = [user_name].into(); + let member_set: HashSet = [user_name.clone()].into(); assert_eq!(member_set.len(), 1); self.groups.insert(conversation_id, member_set); @@ -615,19 +593,14 @@ impl TestBackend { pub async fn invite_to_group( &mut self, conversation_id: ConversationId, - inviter_name: impl SafeTryInto, - invitee_names: Vec>, + inviter_name: &QualifiedUserName, + invitee_names: Vec<&QualifiedUserName>, ) { - let inviter_name = inviter_name.try_into().unwrap(); - let invitee_names: Vec = invitee_names - .into_iter() - .map(|name| name.try_into().unwrap()) - .collect::>(); let invitee_strings = invitee_names .iter() .map(|n| n.to_string()) .collect::>(); - let test_inviter = self.users.get_mut(&inviter_name).unwrap(); + let test_inviter = self.users.get_mut(inviter_name).unwrap(); let inviter = &mut test_inviter.user; // Before inviting anyone to a group, the inviter must first fetch and @@ -640,7 +613,7 @@ impl TestBackend { .expect("Error processing qs messages."); let inviter_conversation = inviter.conversation(&conversation_id).await.unwrap(); - tracing::info!( + info!( "{} invites {} to the group with id {}", inviter_name, invitee_strings.join(", "), @@ -654,7 +627,10 @@ impl TestBackend { .expect("Error getting group members."); let invite_messages = inviter - .invite_users(conversation_id, &invitee_names) + .invite_users( + conversation_id, + &invitee_names.iter().cloned().cloned().collect::>(), + ) .await .expect("Error inviting users."); @@ -678,7 +654,7 @@ impl TestBackend { let new_members = inviter_group_members_after .difference(&inviter_group_members_before) .collect::>(); - let invitee_set = invitee_names.iter().collect::>(); + let invitee_set = invitee_names.iter().copied().collect::>(); assert_eq!(new_members, invitee_set); // Now that the invitation is out, have the invitees and all other group @@ -723,8 +699,8 @@ impl TestBackend { invitee_conversations_before.remove(inactive_conversation_position); } // Now that we've removed the new conversation, it should be the same set of conversations - tracing::info!("Conversations_before: {:?}", invitee_conversations_before); - tracing::info!("Conversations_after: {:?}", invitee_conversations_after); + info!("Conversations_before: {:?}", invitee_conversations_before); + info!("Conversations_after: {:?}", invitee_conversations_after); let different_conversations = invitee_conversations_before .into_iter() .collect::>() @@ -739,7 +715,7 @@ impl TestBackend { let group_members = self.groups.get_mut(&conversation_id).unwrap(); for group_member_name in group_members.iter() { // Skip the sender - if group_member_name == &inviter_name { + if group_member_name == inviter_name { continue; } let test_group_member = self.users.get_mut(group_member_name).unwrap(); @@ -766,19 +742,19 @@ impl TestBackend { let new_members = group_members_after .difference(&group_members_before) .collect::>(); - let invitee_set = invitee_names.iter().collect::>(); + let invitee_set = invitee_names.iter().copied().collect::>(); assert_eq!(new_members, invitee_set) } for invitee_name in &invitee_names { - let unique_member = group_members.insert(invitee_name.clone()); + let unique_member = group_members.insert((*invitee_name).clone()); assert!(unique_member); } // Now send messages to check that the group works properly. This also // ensures that everyone involved has picked up their messages from the // QS and that notifications are flushed. - self.send_message(conversation_id, inviter_name.clone(), invitee_names.clone()) + self.send_message(conversation_id, inviter_name, invitee_names.clone()) .await; for invitee_name in &invitee_names { let recipients: Vec<_> = invitee_names @@ -787,7 +763,7 @@ impl TestBackend { .chain([&inviter_name].into_iter()) .map(|name| name.to_owned()) .collect(); - self.send_message(conversation_id, invitee_name.clone(), recipients) + self.send_message(conversation_id, invitee_name, recipients) .await; } } @@ -797,19 +773,14 @@ impl TestBackend { pub async fn remove_from_group( &mut self, conversation_id: ConversationId, - remover_name: impl SafeTryInto, - removed_names: Vec>, + remover_name: &QualifiedUserName, + removed_names: Vec<&QualifiedUserName>, ) { - let remover_name = remover_name.try_into().unwrap(); - let removed_names: Vec = removed_names - .into_iter() - .map(|name| name.try_into().unwrap()) - .collect::>(); let removed_strings = removed_names .iter() .map(|n| n.to_string()) .collect::>(); - let test_remover = self.users.get_mut(&remover_name).unwrap(); + let test_remover = self.users.get_mut(remover_name).unwrap(); let remover = &mut test_remover.user; // Before removing anyone from a group, the remover must first fetch and @@ -821,7 +792,7 @@ impl TestBackend { .await .expect("Error processing qs messages."); - tracing::info!( + info!( "{} removes {} from the group with id {}", remover_name, removed_strings.join(", "), @@ -836,7 +807,10 @@ impl TestBackend { .expect("Error getting group members."); let remove_messages = remover - .remove_users(conversation_id, &removed_names) + .remove_users( + conversation_id, + &removed_names.iter().copied().cloned().collect::>(), + ) .await .expect("Error removing users."); @@ -863,7 +837,7 @@ impl TestBackend { .collect::>(); let removed_set = removed_names .iter() - .map(|name| name.to_owned()) + .map(|name| (*name).clone()) .collect::>(); assert_eq!(removed_members, removed_set); @@ -927,7 +901,7 @@ impl TestBackend { // Now have the rest of the group pick up and process their messages. for group_member_name in group_members.iter() { // Skip the remover - if group_member_name == &remover_name { + if group_member_name == remover_name { continue; } let test_group_member = self.users.get_mut(group_member_name).unwrap(); @@ -956,7 +930,7 @@ impl TestBackend { .collect::>(); let removed_set = removed_names .iter() - .map(|name| name.to_owned()) + .map(|name| (*name).clone()) .collect::>(); assert_eq!(removed_members, removed_set) } @@ -966,15 +940,14 @@ impl TestBackend { pub async fn leave_group( &mut self, conversation_id: ConversationId, - leaver_name: impl SafeTryInto, + leaver_name: &QualifiedUserName, ) { - let leaver_name = leaver_name.try_into().unwrap(); - tracing::info!( + info!( "{} leaves the group with id {}", leaver_name, conversation_id.as_uuid() ); - let test_leaver = self.users.get_mut(&leaver_name).unwrap(); + let test_leaver = self.users.get_mut(leaver_name).unwrap(); let leaver = &mut test_leaver.user; // Perform the leave operation. @@ -988,7 +961,7 @@ impl TestBackend { let mut random_member_iter = group_members.iter(); let mut random_member_name = random_member_iter.next().unwrap(); // Ensure that the random member isn't the leaver. - if random_member_name == &leaver_name { + if random_member_name == leaver_name { random_member_name = random_member_iter.next().unwrap() } let test_random_member = self.users.get_mut(random_member_name).unwrap(); @@ -1010,21 +983,20 @@ impl TestBackend { .await; let group_members = self.groups.get_mut(&conversation_id).unwrap(); - group_members.remove(&leaver_name); + group_members.remove(leaver_name); } pub async fn delete_group( &mut self, conversation_id: ConversationId, - deleter_name: impl SafeTryInto, + deleter_name: &QualifiedUserName, ) { - let deleter_name = deleter_name.try_into().unwrap(); - tracing::info!( + info!( "{} deletes the group with id {}", deleter_name, conversation_id.as_uuid() ); - let test_deleter = self.users.get_mut(&deleter_name).unwrap(); + let test_deleter = self.users.get_mut(deleter_name).unwrap(); let deleter = &mut test_deleter.user; // Before removing anyone from a group, the remover must first fetch and @@ -1066,7 +1038,7 @@ impl TestBackend { for group_member_name in self.groups.get(&conversation_id).unwrap().iter() { // Skip the deleter - if group_member_name == &deleter_name { + if group_member_name == deleter_name { continue; } let test_group_member = self.users.get_mut(group_member_name).unwrap(); @@ -1143,22 +1115,20 @@ impl TestBackend { .into_iter() .any(|contact| contact.user_name == random_user); if user != &random_user && !is_contact { - other_users.push(user); + other_users.push(user.clone()); } } if let Some(other_user) = other_users.into_iter().choose(rng) { - tracing::info!( + info!( random_operation = true, - "Random operation: Connecting {} and {}", - random_user, - other_user + "Random operation: Connecting {} and {}", random_user, other_user ); - self.connect_users(random_user, other_user.clone()).await; + self.connect_users(&random_user, &other_user).await; } } 1 => { - let conversation_id = self.create_group(random_user).await; - tracing::info!( + let conversation_id = self.create_group(&random_user).await; + info!( random_operation = true, "Random operation: Created group {}", conversation_id.as_uuid() @@ -1210,15 +1180,19 @@ impl TestBackend { .iter() .map(|invitee| invitee.to_string()) .collect::>(); - tracing::info!( + info!( random_operation = true, "Random operation: {} invites {} to group {}", random_user, invitee_strings.join(", "), conversation.id().as_uuid() ); - self.invite_to_group(conversation.id(), random_user, invitee_names) - .await; + self.invite_to_group( + conversation.id(), + &random_user, + invitee_names.iter().collect(), + ) + .await; } } } @@ -1250,14 +1224,15 @@ impl TestBackend { .iter() .map(|removed| removed.to_string()) .collect::>(); - tracing::info!( + info!( random_operation = true, "Random operation: {} removes {} from group {}", random_user, removed_strings.join(", "), conversation.id().as_uuid() ); - self.remove_from_group(conversation.id(), random_user, members_to_remove) + let members_to_remove = members_to_remove.iter().collect(); + self.remove_from_group(conversation.id(), &random_user, members_to_remove) .await; } } @@ -1276,13 +1251,13 @@ impl TestBackend { }) .choose(rng) { - tracing::info!( + info!( random_operation = true, "Random operation: {} leaves group {}", random_user, conversation.id().as_uuid() ); - self.leave_group(conversation.id(), random_user).await; + self.leave_group(conversation.id(), &random_user).await; } } _ => panic!("Invalid action"), diff --git a/types/src/identifiers/mod.rs b/types/src/identifiers/mod.rs index c234cc7b..1f1f4d12 100644 --- a/types/src/identifiers/mod.rs +++ b/types/src/identifiers/mod.rs @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use std::{ - fmt::{Display, Formatter}, - hash::Hash, - str::FromStr, -}; +use std::{fmt, hash::Hash, str::FromStr}; use mls_assist::{openmls::group::GroupId, openmls_traits::types::HpkeCiphertext}; use rand::{CryptoRng, Rng, RngCore}; @@ -16,6 +12,7 @@ use rusqlite::{ ToSql, }; use tls_codec_impls::{TlsString, TlsUuid}; +use tracing::{debug, error}; use url::Host; use uuid::Uuid; @@ -58,10 +55,9 @@ impl<'r> sqlx::Decode<'r, sqlx::Postgres> for Fqdn { fn decode( value: sqlx::postgres::PgValueRef<'r>, ) -> Result> { - let string = String::decode(value)?; - let fqdn = Fqdn::try_from(string).map_err(|e| { - tracing::error!("Error parsing Fqdn from DB: {}", e); - sqlx::Error::Decode(Box::new(e)) + let s: &str = sqlx::Decode::decode(value)?; + let fqdn = s.parse().inspect_err(|error| { + error!(%error, "Error parsing Fqdn from DB"); })?; Ok(fqdn) } @@ -78,17 +74,17 @@ impl ToSql for Fqdn { #[cfg(feature = "sqlite")] impl FromSql for Fqdn { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let string = value.as_str()?.to_owned(); - let fqdn = Fqdn::try_from(string).map_err(|e| { - tracing::error!("Error parsing Fqdn from DB: {}", e); + let s = value.as_str()?; + let fqdn = s.parse().map_err(|error| { + error!(%error, "Error parsing Fqdn from DB"); FromSqlError::InvalidType })?; Ok(fqdn) } } -impl Display for Fqdn { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Fqdn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", &self.domain) } } @@ -101,28 +97,21 @@ pub enum FqdnError { UrlError(#[from] url::ParseError), } -impl TryFrom for Fqdn { - type Error = FqdnError; - - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} - -impl TryFrom<&str> for Fqdn { - type Error = FqdnError; +impl FromStr for Fqdn { + type Err = FqdnError; - fn try_from(value: &str) -> Result { + fn from_str(s: &str) -> Result { // Arbitrary upper limit of 100 characters so we know it will cleanly tls-serialize. - if value.len() > 100 { + if s.len() > 100 { return Err(FqdnError::NotADomainName); } - let domain = Host::::parse(value)?; - // Fqdns can't be IP addresses. - if !matches!(domain, Host::Domain(_)) { - return Err(FqdnError::NotADomainName); + match Host::parse(s)? { + Host::Domain(_) => Ok(Self { + domain: Host::::parse(s)?, + }), + // Fqdns can't be IP addresses. + Host::Ipv4(_) | Host::Ipv6(_) => Err(FqdnError::NotADomainName), } - Ok(Self { domain }) } } @@ -167,8 +156,8 @@ impl From for GroupId { } } -impl std::fmt::Display for QualifiedGroupId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for QualifiedGroupId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let uuid = Uuid::from_bytes(self.group_uuid); write!(f, "{}@{}", uuid, self.owning_domain) } @@ -182,37 +171,29 @@ pub enum QualifiedGroupIdError { InvalidQualifiedGroupId, } -impl TryFrom for QualifiedGroupId { - type Error = QualifiedGroupIdError; +impl FromStr for QualifiedGroupId { + type Err = QualifiedGroupIdError; - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} - -impl TryFrom<&str> for QualifiedGroupId { - type Error = QualifiedGroupIdError; - - fn try_from(value: &str) -> Result { - let mut split_string = value.split('@'); + fn from_str(s: &str) -> Result { + let mut split_string = s.split('@'); let group_id = split_string.next().ok_or_else(|| { - tracing::debug!("The given string is empty."); + debug!("The given string is empty"); QualifiedGroupIdError::InvalidQualifiedGroupId })?; - let group_id_uuid = Uuid::from_str(group_id).map_err(|_| { - tracing::debug!("The given group id is not a valid UUID."); + let group_id_uuid: Uuid = group_id.parse().map_err(|_| { + debug!("The given group id is not a valid UUID"); QualifiedGroupIdError::InvalidQualifiedGroupId })?; let group_id = group_id_uuid.into_bytes(); // GroupIds MUST be qualified let domain = split_string.next().ok_or_else(|| { - tracing::debug!("The given group id is not qualified."); + debug!("The given group id is not qualified"); QualifiedGroupIdError::InvalidQualifiedGroupId })?; - let owning_domain = >::try_from(domain)?; + let owning_domain = domain.parse()?; if split_string.next().is_some() { - tracing::debug!("The domain name may not contain a '@'."); + debug!("The domain name may not contain a '@'"); return Err(QualifiedGroupIdError::InvalidQualifiedGroupId); } @@ -239,15 +220,15 @@ impl TryFrom<&str> for QualifiedGroupId { #[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent))] pub struct UserName(TlsString); -impl std::fmt::Display for UserName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for UserName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } #[derive(Debug, Clone, Error)] pub enum UserNameError { - #[error("The given string does not represent a valid user name.")] + #[error("The given string does not represent a valid user name")] InvalidUserName, } @@ -255,13 +236,10 @@ impl TryFrom for UserName { type Error = UserNameError; fn try_from(value: String) -> Result { - fn contains_any_of(value: &str, chars: &[char]) -> bool { - value.chars().any(|c| chars.contains(&c)) - } - if contains_any_of(&value, &['@', '.']) { + if value.contains(['@', '.']) { Err(UserNameError::InvalidUserName) } else { - Ok(Self(TlsString(value.to_string()))) + Ok(Self(TlsString(value))) } } } @@ -301,11 +279,11 @@ impl ToSql for QualifiedUserName { #[cfg(feature = "sqlite")] impl FromSql for QualifiedUserName { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let user_name = <&str as SafeTryInto>::try_into(value.as_str()?) - .map_err(|e| { - tracing::error!("Error parsing UserName: {}", e); - FromSqlError::InvalidType - })?; + let s = value.as_str()?; + let user_name: QualifiedUserName = s.parse().map_err(|error| { + error!(%error, "Error parsing UserName"); + FromSqlError::InvalidType + })?; Ok(user_name) } } @@ -320,26 +298,11 @@ pub enum QualifiedUserNameError { InvalidFqdn(#[from] FqdnError), } -impl SafeTryInto for T { - type Error = std::convert::Infallible; - - fn try_into(self) -> Result { - Ok(self) - } -} +impl FromStr for QualifiedUserName { + type Err = QualifiedUserNameError; -// Convenience trait to allow `impl TryInto` as function input. -pub trait SafeTryInto: Sized { - type Error: std::error::Error + Send + Sync + 'static; - fn try_into(self) -> Result; -} - -// TODO: This string processing is way too simplistic, but it should do for now. -impl SafeTryInto for &str { - type Error = QualifiedUserNameError; - - fn try_into(self) -> Result { - let mut split_name = self.split('@'); + fn from_str(s: &str) -> Result { + let mut split_name = s.split('@'); let user_name_str = split_name .next() .ok_or(QualifiedUserNameError::InvalidString)?; @@ -351,27 +314,11 @@ impl SafeTryInto for &str { if split_name.next().is_some() { return Err(QualifiedUserNameError::InvalidString); } - let domain = >::try_from(domain)?; + let domain = domain.parse()?; Ok(QualifiedUserName { user_name, domain }) } } -impl SafeTryInto for String { - type Error = QualifiedUserNameError; - - fn try_into(self) -> Result { - <&str as SafeTryInto>::try_into(self.as_str()) - } -} - -impl SafeTryInto for &String { - type Error = QualifiedUserNameError; - - fn try_into(self) -> Result { - <&str as SafeTryInto>::try_into(self.as_str()) - } -} - impl QualifiedUserName { pub fn user_name(&self) -> &UserName { &self.user_name @@ -382,8 +329,8 @@ impl QualifiedUserName { } } -impl std::fmt::Display for QualifiedUserName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for QualifiedUserName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}@{}", self.user_name, self.domain) } } @@ -406,8 +353,8 @@ pub struct AsClientId { client_id: TlsUuid, } -impl std::fmt::Display for AsClientId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for AsClientId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let client_id_str = self.client_id.to_string(); write!(f, "{}.{}", client_id_str, self.user_name) } @@ -452,7 +399,7 @@ impl TryFrom for AsClientId { return Err(AsClientIdError::InvalidClientId); }; let client_id = TlsUuid(Uuid::parse_str(client_id_str)?); - let user_name = <&str as SafeTryInto>::try_into(user_name_str)?; + let user_name: QualifiedUserName = user_name_str.parse()?; Ok(Self { user_name, client_id, @@ -473,7 +420,7 @@ impl FromSql for AsClientId { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { let string = value.as_str()?.to_owned(); let as_client_id = AsClientId::try_from(string).map_err(|e| { - tracing::error!("Error parsing AsClientId: {}", e); + error!("Error parsing AsClientId: {}", e); FromSqlError::InvalidType })?; Ok(as_client_id) @@ -632,26 +579,41 @@ mod tests { #[test] fn valid_fqdn() { let fqdn_str = "example.com"; - let fqdn = Fqdn::try_from(fqdn_str).unwrap(); + let fqdn = Fqdn::from_str(fqdn_str).unwrap(); assert_eq!(fqdn.domain, Host::Domain(fqdn_str.to_string())); let fqdn_subdomain_str = "sub.example.com"; - let fqdn = Fqdn::try_from(fqdn_subdomain_str).unwrap(); + let fqdn = Fqdn::from_str(fqdn_subdomain_str).unwrap(); assert_eq!(fqdn.domain, Host::Domain(fqdn_subdomain_str.to_string())); } #[test] fn invalid_fqdn() { let fqdn_str = "invalid#domain#character"; - let result = Fqdn::try_from(fqdn_str); + let result = Fqdn::from_str(fqdn_str); assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), FqdnError::UrlError(_))); + assert!(matches!(result, Err(FqdnError::UrlError(_)))); } #[test] fn ip_address_fqdn() { let fqdn_str = "192.168.0.1"; - let result = Fqdn::try_from(fqdn_str); - assert!(matches!(result.unwrap_err(), FqdnError::NotADomainName)); + let result = Fqdn::from_str(fqdn_str); + assert!(matches!(result, Err(FqdnError::NotADomainName))); + } + + #[test] + fn valid_user_name() { + let user_name = UserName::try_from("alice".to_string()); + assert_eq!(user_name.unwrap().0 .0, "alice"); + } + + #[test] + fn invalid_user_name() { + let user_name = UserName::try_from("alice@host".to_string()); + assert!(matches!(user_name, Err(UserNameError::InvalidUserName))); + + let user_name = UserName::try_from("alice.bob".to_string()); + assert!(matches!(user_name, Err(UserNameError::InvalidUserName))); } } diff --git a/types/src/identifiers/tls_codec_impls.rs b/types/src/identifiers/tls_codec_impls.rs index 3487caa7..b521fbaa 100644 --- a/types/src/identifiers/tls_codec_impls.rs +++ b/types/src/identifiers/tls_codec_impls.rs @@ -36,10 +36,7 @@ impl Serialize for TlsUuid { } impl DeserializeBytes for TlsUuid { - fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> - where - Self: Sized, - { + fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { let (uuid_bytes, rest) = <[u8; 16]>::tls_deserialize_bytes(bytes)?; let uuid = Uuid::from_bytes(uuid_bytes); Ok((Self(uuid), rest)) @@ -82,10 +79,7 @@ impl Size for TlsString { } impl DeserializeBytes for TlsString { - fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> - where - Self: Sized, - { + fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { let (string, rest) = >::tls_deserialize_bytes(bytes)?; let string = String::from_utf8(string) .map_err(|_| Error::DecodingError("Couldn't decode string.".to_owned()))?; @@ -110,14 +104,10 @@ impl Size for Fqdn { } impl DeserializeBytes for Fqdn { - fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> - where - Self: Sized, - { + fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { let (TlsString(domain_string), rest) = TlsString::tls_deserialize_bytes(bytes)?; - let domain = Fqdn::try_from(domain_string).map_err(|e| { - let e = format!("Couldn't decode domain string: {}.", e); - Error::DecodingError(e) + let domain = domain_string.parse().map_err(|error| { + Error::DecodingError(format!("Couldn't decode domain string: {error}")) })?; Ok((domain, rest)) } @@ -134,10 +124,7 @@ impl Serialize for Fqdn { } impl DeserializeBytes for UserName { - fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> - where - Self: Sized, - { + fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { let (TlsString(user_name_string), rest) = TlsString::tls_deserialize_bytes(bytes)?; let user_name = UserName::try_from(user_name_string).map_err(|e| { let e = format!("Couldn't decode user name string: {}.", e);