diff --git a/services/commtest/src/backup/backup_utils.rs b/services/commtest/src/backup/backup_utils.rs index 4dcef9e5a..29b343445 100644 --- a/services/commtest/src/backup/backup_utils.rs +++ b/services/commtest/src/backup/backup_utils.rs @@ -1,97 +1,100 @@ use crate::identity::device::DeviceInfo; use crate::tools::generate_stable_nbytes; use backup_client::{BackupData, Error as BackupClientError}; use bytesize::ByteSize; use comm_lib::auth::UserIdentity; use comm_lib::backup::UploadLogRequest; +use rand::Rng; use reqwest::StatusCode; use uuid::Uuid; -pub fn generate_backup_data(predefined_byte_value: u8) -> BackupData { +pub fn generate_backup_data(predefined_byte_value: Option) -> BackupData { + let predefined_byte_value = + predefined_byte_value.unwrap_or(rand::thread_rng().gen::()); BackupData { backup_id: Uuid::new_v4().to_string(), user_keys: Some(generate_stable_nbytes( ByteSize::kib(4).as_u64() as usize, Some(predefined_byte_value), )), user_data: Some(generate_stable_nbytes( ByteSize::mib(4).as_u64() as usize, Some(predefined_byte_value), )), attachments: vec![], siwe_backup_msg: None, } } fn generate_log_data(backup_id: &str, value: u8) -> Vec { const IN_DB_SIZE: usize = ByteSize::kib(4).as_u64() as usize; const IN_BLOB_SIZE: usize = ByteSize::kib(400).as_u64() as usize; (1..30) .map(|log_id| { let size = if log_id % 2 == 0 { IN_DB_SIZE } else { IN_BLOB_SIZE }; let attachments = if log_id % 10 == 0 { Some(vec![Uuid::new_v4().to_string()]) } else { None }; let mut content = generate_stable_nbytes(size, Some(value)); let unique_suffix = log_id.to_string(); content.extend(unique_suffix.as_bytes()); UploadLogRequest { backup_id: backup_id.to_string(), log_id, content, attachments, } }) .collect() } pub fn generate_backup_data_with_logs( predefined_byte_values: Vec, ) -> Vec<(BackupData, Vec)> { predefined_byte_values .into_iter() .map(|byte_value| { - let backup_data = generate_backup_data(byte_value); + let backup_data = generate_backup_data(Some(byte_value)); let log_data = generate_log_data(&backup_data.backup_id, byte_value); (backup_data, log_data) }) .collect() } pub fn assert_reqwest_error( response: Result, expected_status: StatusCode, ) { match response { Err(BackupClientError::ReqwestError(error)) => { assert_eq!( error.status(), Some(expected_status), "Expected status {}", expected_status ); } Err(err) => panic!( "Backup should return ReqwestError, instead got response: {:?}", err ), Ok(_) => panic!("Backup should return BackupClientError"), } } pub fn create_user_identity(device_info: DeviceInfo) -> UserIdentity { UserIdentity { user_id: device_info.user_id.clone(), access_token: device_info.access_token.clone(), device_id: device_info.device_id.clone(), } } diff --git a/services/commtest/tests/backup_performance_test.rs b/services/commtest/tests/backup_performance_test.rs index bdd5daa54..7ecdc80d3 100644 --- a/services/commtest/tests/backup_performance_test.rs +++ b/services/commtest/tests/backup_performance_test.rs @@ -1,169 +1,169 @@ use backup_client::{BackupClient, BackupDescriptor, RequestedData}; use comm_lib::backup::LatestBackupInfoResponse; use commtest::backup::backup_utils::{ create_user_identity, generate_backup_data, }; use commtest::identity::device::register_user_device; use commtest::{ service_addr, tools::{obtain_number_of_threads, Error}, }; use grpc_clients::identity::DeviceType; use tokio::{runtime::Runtime, task::JoinSet}; #[tokio::test] async fn backup_performance_test() -> Result<(), Error> { let backup_client = BackupClient::new(service_addr::BACKUP_SERVICE_HTTP)?; let number_of_threads = obtain_number_of_threads(); let rt = Runtime::new().unwrap(); println!( "Running performance tests for backup, number of threads: {}", number_of_threads ); let backup_data: Vec<_> = (0..number_of_threads) - .map(|i| generate_backup_data(i as u8)) + .map(|i| generate_backup_data(Some(i as u8))) .collect(); let device_info_1 = register_user_device(None, Some(DeviceType::Ios)).await; let device_info_2 = register_user_device(None, Some(DeviceType::Ios)).await; let user_identities = [ create_user_identity(device_info_1.clone()), create_user_identity(device_info_2.clone()), ]; tokio::task::spawn_blocking(move || { println!("Creating new backups"); rt.block_on(async { let mut set = JoinSet::new(); for (i, item) in backup_data.iter().cloned().enumerate() { let backup_client = backup_client.clone(); let user = user_identities[i % user_identities.len()].clone(); set.spawn(async move { backup_client.upload_backup(&user, item).await.unwrap(); }); } while let Some(result) = set.join_next().await { result.unwrap(); } }); let mut latest_ids_for_user = vec![]; println!("Reading latest ids"); rt.block_on(async { let mut handlers = vec![]; for user in &user_identities { let backup_client = backup_client.clone(); let user_identifier = if user.user_id == device_info_1.user_id { device_info_1.username.clone() } else { device_info_2.username.clone() }; let descriptor = BackupDescriptor::Latest { user_identifier }; handlers.push(tokio::spawn(async move { let response = backup_client .download_backup_data(&descriptor, RequestedData::BackupInfo) .await .unwrap(); serde_json::from_slice::(&response).unwrap() })); } for handler in handlers { latest_ids_for_user.push(handler.await.unwrap().backup_id); } }); assert_eq!(latest_ids_for_user.len(), user_identities.len()); let mut latest_user_keys_for_user = vec![]; println!("Reading latest user keys"); rt.block_on(async { let mut handlers = vec![]; for user in &user_identities { let backup_client = backup_client.clone(); let user_identifier = if user.user_id == device_info_1.user_id { device_info_1.username.clone() } else { device_info_2.username.clone() }; let descriptor = BackupDescriptor::Latest { user_identifier }; handlers.push(tokio::spawn(async move { backup_client .download_backup_data(&descriptor, RequestedData::UserKeys) .await .unwrap() })); } for handler in handlers { latest_user_keys_for_user.push(handler.await.unwrap()); } }); assert_eq!(latest_user_keys_for_user.len(), user_identities.len()); for (backup_id, user_keys) in latest_ids_for_user.iter().zip(latest_user_keys_for_user) { let backup = backup_data .iter() .find(|data| data.backup_id == *backup_id) .expect("Request should return existing backup data"); assert_eq!(backup.user_keys, Some(user_keys)); } let mut latest_user_data_for_user = vec![]; println!("Reading latest user data"); rt.block_on(async { let mut handlers = vec![]; for (i, backup_id) in latest_ids_for_user.iter().enumerate() { let backup_client = backup_client.clone(); let descriptor = BackupDescriptor::BackupID { backup_id: backup_id.clone(), user_identity: user_identities[i % user_identities.len()].clone(), }; handlers.push(tokio::spawn(async move { backup_client .download_backup_data(&descriptor, RequestedData::UserData) .await .unwrap() })); } for handler in handlers { latest_user_data_for_user.push(handler.await.unwrap()); } }); assert_eq!(latest_user_data_for_user.len(), user_identities.len()); for (backup_id, user_data) in latest_ids_for_user.iter().zip(latest_user_data_for_user) { let backup = backup_data .iter() .find(|data| data.backup_id == *backup_id) .expect("Request should return existing backup data"); assert_eq!(backup.user_data, Some(user_data)); } }) .await .expect("Task panicked"); Ok(()) } diff --git a/services/commtest/tests/backup_upload_test.rs b/services/commtest/tests/backup_upload_test.rs new file mode 100644 index 000000000..ad0fb0252 --- /dev/null +++ b/services/commtest/tests/backup_upload_test.rs @@ -0,0 +1,278 @@ +use backup_client::{ + BackupClient, BackupData, BackupDescriptor, RequestedData, +}; +use comm_lib::backup::LatestBackupInfoResponse; +use commtest::backup::backup_utils::{ + assert_reqwest_error, create_user_identity, generate_backup_data, +}; +use commtest::identity::device::register_user_device; +use commtest::{service_addr, tools::Error}; +use grpc_clients::identity::DeviceType; +use reqwest::StatusCode; + +#[tokio::test] +async fn backup_upload_user_keys() -> Result<(), Error> { + let backup_client = BackupClient::new(service_addr::BACKUP_SERVICE_HTTP)?; + + let device_info = register_user_device(None, Some(DeviceType::Ios)).await; + let user_identity = create_user_identity(device_info.clone()); + + let backup_data = BackupData { + user_data: None, + ..generate_backup_data(None) + }; + + // Upload backup (User Keys) + backup_client + .upload_backup(&user_identity, backup_data.clone()) + .await?; + + // Test User Keys download + let backup_descriptor = BackupDescriptor::BackupID { + backup_id: backup_data.backup_id.clone(), + user_identity: user_identity.clone(), + }; + let user_keys = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserKeys) + .await?; + + assert_eq!(Some(user_keys), backup_data.user_keys); + + // Test latest backup lookup without User Data + let latest_backup_descriptor = BackupDescriptor::Latest { + user_identifier: device_info.username, + }; + let backup_info_response = backup_client + .download_backup_data(&latest_backup_descriptor, RequestedData::BackupInfo) + .await?; + let response: LatestBackupInfoResponse = + serde_json::from_slice(&backup_info_response)?; + + assert_eq!(response.backup_id, backup_data.backup_id); + assert_eq!(response.user_id, device_info.user_id); + + let user_keys = backup_client + .download_backup_data(&latest_backup_descriptor, RequestedData::UserKeys) + .await?; + assert_eq!(Some(user_keys), backup_data.user_keys); + + // Test backup cleanup for User Keys only + let new_backup_data = BackupData { + user_data: None, + ..generate_backup_data(None) + }; + + backup_client + .upload_backup(&user_identity, new_backup_data.clone()) + .await?; + + // Test Data download for old `backup_id` -> should be not found + let user_data_response = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserData) + .await; + let user_keys_response = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserKeys) + .await; + + assert_reqwest_error(user_data_response, StatusCode::NOT_FOUND); + assert_reqwest_error(user_keys_response, StatusCode::NOT_FOUND); + + Ok(()) +} + +#[tokio::test] +async fn backup_upload_the_same_user_keys() -> Result<(), Error> { + let backup_client = BackupClient::new(service_addr::BACKUP_SERVICE_HTTP)?; + + let device_info = register_user_device(None, Some(DeviceType::Ios)).await; + let user_identity = create_user_identity(device_info); + + let backup_data = BackupData { + user_data: None, + ..generate_backup_data(None) + }; + + // Upload backup twice (User Keys only) + backup_client + .upload_backup(&user_identity, backup_data.clone()) + .await?; + + backup_client + .upload_backup(&user_identity, backup_data.clone()) + .await?; + + // Test User Keys download + let backup_descriptor = BackupDescriptor::BackupID { + backup_id: backup_data.backup_id.clone(), + user_identity: user_identity.clone(), + }; + let user_keys = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserKeys) + .await?; + + assert_eq!(Some(user_keys), backup_data.user_keys); + + Ok(()) +} + +#[tokio::test] +async fn backup_upload_user_data_without_user_keys() -> Result<(), Error> { + let backup_client = BackupClient::new(service_addr::BACKUP_SERVICE_HTTP)?; + + let device_info = register_user_device(None, Some(DeviceType::Ios)).await; + let user_identity = create_user_identity(device_info); + + let backup_data = BackupData { + user_keys: None, + ..generate_backup_data(None) + }; + + // Upload backup (User Data) -> should fail, + // there is no corresponding User Keys + let response = backup_client + .upload_backup(&user_identity, backup_data.clone()) + .await; + + assert_reqwest_error(response, StatusCode::NOT_FOUND); + + Ok(()) +} + +#[tokio::test] +async fn backup_upload_user_keys_and_user_data() -> Result<(), Error> { + let backup_client = BackupClient::new(service_addr::BACKUP_SERVICE_HTTP)?; + + let device_info = register_user_device(None, Some(DeviceType::Ios)).await; + let user_identity = create_user_identity(device_info.clone()); + + let backup_data = generate_backup_data(None); + let user_keys = BackupData { + user_data: None, + ..backup_data.clone() + }; + let user_data = BackupData { + user_keys: None, + ..backup_data.clone() + }; + + // Upload backups (User Keys and User Data) + backup_client + .upload_backup(&user_identity, user_keys.clone()) + .await?; + + backup_client + .upload_backup(&user_identity, user_data.clone()) + .await?; + + // Test User Keys download + let backup_descriptor = BackupDescriptor::BackupID { + backup_id: backup_data.backup_id.clone(), + user_identity: user_identity.clone(), + }; + let user_keys = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserKeys) + .await?; + + assert_eq!(Some(user_keys), backup_data.user_keys); + + // Test User Data download + let user_data = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserData) + .await?; + assert_eq!(Some(user_data), backup_data.user_data); + + // Upload new User Data + let new_backup_data = generate_backup_data(None); + let new_user_data = BackupData { + // Important we using the same `backup_id` + backup_id: backup_data.backup_id.clone(), + user_keys: None, + user_data: new_backup_data.user_data.clone(), + attachments: new_backup_data.attachments, + siwe_backup_msg: None, + }; + + backup_client + .upload_backup(&user_identity, new_user_data.clone()) + .await?; + + // Test User Keys download again -> should remain unchanged + let backup_descriptor = BackupDescriptor::BackupID { + backup_id: new_user_data.backup_id.clone(), + user_identity: user_identity.clone(), + }; + let user_keys = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserKeys) + .await?; + + assert_eq!(Some(user_keys), backup_data.user_keys); + + // Test User Data download, should be updated + let user_data = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserData) + .await?; + + assert_eq!(Some(user_data), new_backup_data.user_data); + + // Upload new User Keys -> should override User Keys and keep User Data unchanged + let new_user_keys = BackupData { + user_data: None, + ..generate_backup_data(None) + }; + + backup_client + .upload_backup(&user_identity, new_user_keys.clone()) + .await?; + + // Test latest backup -> should return newest `backup_id` + let latest_backup_descriptor = BackupDescriptor::Latest { + user_identifier: device_info.username, + }; + let backup_info_response = backup_client + .download_backup_data(&latest_backup_descriptor, RequestedData::BackupInfo) + .await?; + let response: LatestBackupInfoResponse = + serde_json::from_slice(&backup_info_response)?; + + assert_eq!(response.backup_id, new_user_keys.backup_id); + assert_eq!(response.user_id, device_info.user_id); + + // Test User Keys download -> should be updated + let backup_descriptor = BackupDescriptor::BackupID { + backup_id: new_user_keys.backup_id.clone(), + user_identity: user_identity.clone(), + }; + let user_keys = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserKeys) + .await?; + + assert_eq!(Some(user_keys), new_user_keys.user_keys); + + // Test User Data download -> should be the old one + let backup_descriptor = BackupDescriptor::BackupID { + backup_id: new_user_keys.backup_id.clone(), + user_identity: user_identity.clone(), + }; + let user_data = backup_client + .download_backup_data(&backup_descriptor, RequestedData::UserData) + .await?; + + assert_eq!(Some(user_data), new_backup_data.user_data); + + // Test Data download for old `backup_id` -> should be not found + let removed_backup_descriptor = BackupDescriptor::BackupID { + backup_id: backup_data.backup_id.clone(), + user_identity: user_identity.clone(), + }; + let user_data_response = backup_client + .download_backup_data(&removed_backup_descriptor, RequestedData::UserData) + .await; + let user_keys_response = backup_client + .download_backup_data(&removed_backup_descriptor, RequestedData::UserKeys) + .await; + + assert_reqwest_error(user_data_response, StatusCode::NOT_FOUND); + assert_reqwest_error(user_keys_response, StatusCode::NOT_FOUND); + + Ok(()) +}