From 9d1e39155c7348aacd5fb29a9b236d1ae427aa6d Mon Sep 17 00:00:00 2001 From: map Date: Tue, 1 Oct 2024 09:54:44 +0200 Subject: [PATCH] wip next test --- .../rust/sdk/src/services/service_executor.rs | 727 ++++++++++-------- 1 file changed, 409 insertions(+), 318 deletions(-) diff --git a/tuta-sdk/rust/sdk/src/services/service_executor.rs b/tuta-sdk/rust/sdk/src/services/service_executor.rs index 06394b1f8df9..e5f79bd846a2 100644 --- a/tuta-sdk/rust/sdk/src/services/service_executor.rs +++ b/tuta-sdk/rust/sdk/src/services/service_executor.rs @@ -11,7 +11,7 @@ use crate::rest_client::{HttpMethod, RestClient, RestClientOptions}; use crate::rest_error::HttpError; use crate::services::hidden::Executor; use crate::services::{ - DeleteService, ExtraServiceParams, GetService, PostService, PutService, Service, + DeleteService, ExtraServiceParams, GetService, PostService, PutService, Service, }; use crate::type_model_provider::TypeModelProvider; use crate::{ApiCallError, HeadersProvider}; @@ -20,348 +20,439 @@ use serde::Serialize; use std::sync::Arc; pub struct ServiceExecutor { - auth_headers_provider: Arc, - crypto_facade: Arc, - entity_facade: Arc, - instance_mapper: Arc, - json_serializer: Arc, - rest_client: Arc, - type_model_provider: Arc, - base_url: String, + auth_headers_provider: Arc, + crypto_facade: Arc, + entity_facade: Arc, + instance_mapper: Arc, + json_serializer: Arc, + rest_client: Arc, + type_model_provider: Arc, + base_url: String, } impl ServiceExecutor { - pub fn new( - auth_headers_provider: Arc, - crypto_facade: Arc, - entity_facade: Arc, - instance_mapper: Arc, - json_serializer: Arc, - rest_client: Arc, - type_model_provider: Arc, - ) -> Self { - Self { - auth_headers_provider, - crypto_facade, - entity_facade, - instance_mapper, - json_serializer, - rest_client, - type_model_provider, - base_url: "http://api.tuta.com".to_string(), - } - } + pub fn new( + auth_headers_provider: Arc, + crypto_facade: Arc, + entity_facade: Arc, + instance_mapper: Arc, + json_serializer: Arc, + rest_client: Arc, + type_model_provider: Arc, + ) -> Self { + Self { + auth_headers_provider, + crypto_facade, + entity_facade, + instance_mapper, + json_serializer, + rest_client, + type_model_provider, + base_url: "http://api.tuta.com".to_string(), + } + } - pub async fn get( - &self, - data: S::Input, - params: ExtraServiceParams, - ) -> Result - where - S: GetService, - { - S::get(self, data, params).await - } + pub async fn get( + &self, + data: S::Input, + params: ExtraServiceParams, + ) -> Result + where + S: GetService, + { + S::get(self, data, params).await + } - pub async fn post( - &self, - data: S::Input, - params: ExtraServiceParams, - ) -> Result - where - S: PostService, - { - S::post(self, data, params).await - } + pub async fn post( + &self, + data: S::Input, + params: ExtraServiceParams, + ) -> Result + where + S: PostService, + { + S::post(self, data, params).await + } - pub async fn put( - &self, - data: S::Input, - params: ExtraServiceParams, - ) -> Result - where - S: PutService, - { - S::put(self, data, params).await - } + pub async fn put( + &self, + data: S::Input, + params: ExtraServiceParams, + ) -> Result + where + S: PutService, + { + S::put(self, data, params).await + } - pub async fn delete( - &self, - data: S::Input, - params: ExtraServiceParams, - ) -> Result - where - S: DeleteService, - { - S::delete(self, data, params).await - } + pub async fn delete( + &self, + data: S::Input, + params: ExtraServiceParams, + ) -> Result + where + S: DeleteService, + { + S::delete(self, data, params).await + } } impl Executor for ServiceExecutor { - async fn do_request( - &self, - data: Option, - method: HttpMethod, - extra_service_params: ExtraServiceParams, - ) -> Result>, ApiCallError> - where - S: Service, - I: Entity + Serialize, - { - let url = format!( - "{}/rest/{}", - if let Some(url) = extra_service_params.base_url { - url.clone() - } else { - self.base_url.clone() - }, - S::PATH, - ); - let model_version: u32 = S::VERSION; + async fn do_request( + &self, + data: Option, + method: HttpMethod, + extra_service_params: ExtraServiceParams, + ) -> Result>, ApiCallError> + where + S: Service, + I: Entity + Serialize, + { + let url = format!( + "{}/rest/{}", + if let Some(url) = extra_service_params.base_url { + url.clone() + } else { + self.base_url.clone() + }, + S::PATH, + ); + let model_version: u32 = S::VERSION; - let body: Option> = if let Some(input_entity) = data { - let parsed_entity = self - .instance_mapper - .serialize_entity(input_entity) - .map_err(|e| { - ApiCallError::internal_with_err(e, "failed to convert to ParsedEntity") - })?; - let input_type_ref = I::type_ref(); - let type_model = self - .type_model_provider - .get_type_model(input_type_ref.app, input_type_ref.type_) - .ok_or(ApiCallError::internal(format!( - "type {:?} does not exist", - input_type_ref - )))?; - let encrypted_parsed_entity = self.entity_facade.encrypt_and_map_to_literal( - type_model, - &parsed_entity, - extra_service_params.session_key, - )?; + let body: Option> = if let Some(input_entity) = data { + let parsed_entity = self + .instance_mapper + .serialize_entity(input_entity) + .map_err(|e| { + ApiCallError::internal_with_err(e, "failed to convert to ParsedEntity") + })?; + let input_type_ref = I::type_ref(); + let type_model = self + .type_model_provider + .get_type_model(input_type_ref.app, input_type_ref.type_) + .ok_or(ApiCallError::internal(format!( + "type {:?} does not exist", + input_type_ref + )))?; + let encrypted_parsed_entity = self.entity_facade.encrypt_and_map_to_literal( + type_model, + &parsed_entity, + extra_service_params.session_key, + )?; - let raw_entity = self - .json_serializer - .serialize(&I::type_ref(), encrypted_parsed_entity)?; - let bytes = serde_json::to_vec::(&raw_entity).map_err(|e| { - ApiCallError::internal_with_err(e, "failed to serialize input to string") - })?; - Some(bytes) - } else { - None - }; + let raw_entity = self + .json_serializer + .serialize(&I::type_ref(), encrypted_parsed_entity)?; + let bytes = serde_json::to_vec::(&raw_entity).map_err(|e| { + ApiCallError::internal_with_err(e, "failed to serialize input to string") + })?; + Some(bytes) + } else { + None + }; - let mut headers = self.auth_headers_provider.provide_headers(model_version); - if let Some(extra_headers) = extra_service_params.extra_headers { - headers.extend(extra_headers); - } + let mut headers = self.auth_headers_provider.provide_headers(model_version); + if let Some(extra_headers) = extra_service_params.extra_headers { + headers.extend(extra_headers); + } - let response = self - .rest_client - .request_binary(url, method, RestClientOptions { body, headers }) - .await?; - let precondition = response.headers.get("precondition"); - match response.status { - 200 | 201 => Ok(response.body), - _ => Err(ApiCallError::ServerResponseError { - source: HttpError::from_http_response(response.status, precondition)?, - }), - } - } + let response = self + .rest_client + .request_binary(url, method, RestClientOptions { body, headers }) + .await?; + let precondition = response.headers.get("precondition"); + match response.status { + 200 | 201 => Ok(response.body), + _ => Err(ApiCallError::ServerResponseError { + source: HttpError::from_http_response(response.status, precondition)?, + }), + } + } - async fn handle_response( - &self, - body: Option>, - ) -> Result - where - OutputType: Entity + Deserialize<'static>, - { - let response_bytes = body.expect("no body"); - let response_entity = serde_json::from_slice::(response_bytes.as_slice()) - .map_err(|e| ApiCallError::internal_with_err(e, "Failed to serialize instance"))?; - let output_type_ref = &OutputType::type_ref(); - let mut parsed_entity = self - .json_serializer - .parse(output_type_ref, response_entity)?; - let type_model: &TypeModel = self - .type_model_provider - .get_type_model(output_type_ref.app, output_type_ref.type_) - .expect("invalid type ref!"); + async fn handle_response( + &self, + body: Option>, + ) -> Result + where + OutputType: Entity + Deserialize<'static>, + { + let response_bytes = body.expect("no body"); + let response_entity = serde_json::from_slice::(response_bytes.as_slice()) + .map_err(|e| ApiCallError::internal_with_err(e, "Failed to serialize instance"))?; + let output_type_ref = &OutputType::type_ref(); + let mut parsed_entity = self + .json_serializer + .parse(output_type_ref, response_entity)?; + let type_model: &TypeModel = self + .type_model_provider + .get_type_model(output_type_ref.app, output_type_ref.type_) + .expect("invalid type ref!"); - if type_model.marked_encrypted() { - let possible_session_key = self - .crypto_facade - .resolve_session_key(&mut parsed_entity, type_model) - .await - .map_err(|error| { - ApiCallError::internal(format!( - "Failed to resolve session key for service response '{}'; {}", - type_model.name, error - )) - })?; - match possible_session_key { - Some(session_key) => { - let decrypted_entity = self.entity_facade.decrypt_and_map( - type_model, - parsed_entity, - session_key, - )?; - let typed_entity = self - .instance_mapper - .parse_entity::(decrypted_entity) - .map_err(|e| { - ApiCallError::internal_with_err( - e, - "Failed to parse encrypted entity into proper types", - ) - })?; - Ok(typed_entity) - }, - // `resolve_session_key()` only returns none if the entity is unencrypted, so - // no need to handle it - None => { - unreachable!() - }, - } - } else { - let typed_entity = self - .instance_mapper - .parse_entity::(parsed_entity) - .map_err(|error| { - ApiCallError::internal_with_err( - error, - "Failed to parse unencrypted entity into proper types", - ) - })?; - Ok(typed_entity) - } - } + if type_model.marked_encrypted() { + let possible_session_key = self + .crypto_facade + .resolve_session_key(&mut parsed_entity, type_model) + .await + .map_err(|error| { + ApiCallError::internal(format!( + "Failed to resolve session key for service response '{}'; {}", + type_model.name, error + )) + })?; + match possible_session_key { + Some(session_key) => { + let decrypted_entity = self.entity_facade.decrypt_and_map( + type_model, + parsed_entity, + session_key, + )?; + let typed_entity = self + .instance_mapper + .parse_entity::(decrypted_entity) + .map_err(|e| { + ApiCallError::internal_with_err( + e, + "Failed to parse encrypted entity into proper types", + ) + })?; + Ok(typed_entity) + } + // `resolve_session_key()` only returns none if the entity is unencrypted, so + // no need to handle it + None => { + unreachable!() + } + } + } else { + let typed_entity = self + .instance_mapper + .parse_entity::(parsed_entity) + .map_err(|error| { + ApiCallError::internal_with_err( + error, + "Failed to parse unencrypted entity into proper types", + ) + })?; + Ok(typed_entity) + } + } } #[cfg(test)] mod tests { - #[mockall_double::double] - use crate::crypto::crypto_facade::CryptoFacade; - use crate::element_value::{ElementValue, ParsedEntity}; - use crate::entities::entity_facade::MockEntityFacade; - use crate::entities::sys::{SaltData, SaltReturn}; - use crate::entities::Entity; - use crate::instance_mapper::InstanceMapper; - use crate::json_element::RawEntity; - use crate::json_serializer::JsonSerializer; - use crate::rest_client::{HttpMethod, MockRestClient, RestClientOptions, RestResponse}; - use crate::services::service_executor::ServiceExecutor; - use crate::services::sys::SaltService; - use crate::services::{ExtraServiceParams, GetService}; - use crate::type_model_provider::{init_type_model_provider, TypeModelProvider}; - use crate::HeadersProvider; - use mockall::predicate::{eq, function}; - use std::collections::HashMap; - use std::sync::Arc; + #[mockall_double::double] + use crate::crypto::crypto_facade::CryptoFacade; + use crate::element_value::{ElementValue, ParsedEntity}; + use crate::entities::entity_facade::MockEntityFacade; + use crate::entities::sys::{AlarmServicePost, GiftCardRedeemData, GiftCardRedeemGetReturn, SaltData, SaltReturn}; + use crate::entities::Entity; + use crate::instance_mapper::InstanceMapper; + use crate::json_element::RawEntity; + use crate::json_serializer::JsonSerializer; + use crate::rest_client::{HttpMethod, MockRestClient, RestClientOptions, RestResponse}; + use crate::services::service_executor::ServiceExecutor; + use crate::services::sys::{AlarmService, GiftCardRedeemService, SaltService}; + use crate::services::{ExtraServiceParams, GetService}; + use crate::type_model_provider::{init_type_model_provider, TypeModelProvider}; + use crate::{HeadersProvider, IdTuple}; + use mockall::predicate::{eq, function}; + use std::collections::HashMap; + use std::sync::Arc; + use crate::generated_id::GeneratedId; - fn setup() -> ServiceExecutor { - let type_model_provider: Arc = Arc::new(init_type_model_provider()); + fn setup() -> ServiceExecutor { + let type_model_provider: Arc = Arc::new(init_type_model_provider()); - let crypto_facade = Arc::new(CryptoFacade::default()); - let entity_facade = Arc::new(MockEntityFacade::default()); - let auth_headers_provider = Arc::new(HeadersProvider::new( - "1.2.3".to_string(), - "access_token".to_string(), - )); - let instance_mapper = Arc::new(InstanceMapper::new()); - let json_serializer = Arc::new(JsonSerializer::new(type_model_provider.clone())); - let rest_client = Arc::new(MockRestClient::new()); + let crypto_facade = Arc::new(CryptoFacade::default()); + let entity_facade = Arc::new(MockEntityFacade::default()); + let auth_headers_provider = Arc::new(HeadersProvider::new( + "1.2.3".to_string(), + "access_token".to_string(), + )); + let instance_mapper = Arc::new(InstanceMapper::new()); + let json_serializer = Arc::new(JsonSerializer::new(type_model_provider.clone())); + let rest_client = Arc::new(MockRestClient::new()); - ServiceExecutor::new( - auth_headers_provider, - crypto_facade, - entity_facade, - instance_mapper, - json_serializer, - rest_client, - type_model_provider.clone(), - ) - } - #[tokio::test] - pub async fn get_encrypts_data() { - let executor = setup(); + ServiceExecutor::new( + auth_headers_provider, + crypto_facade, + entity_facade, + instance_mapper, + json_serializer, + rest_client, + type_model_provider.clone(), + ) + } - let version = executor - .type_model_provider - .get_type_model(SaltData::type_ref().app, SaltData::type_ref().type_) - .unwrap() - .version; + #[tokio::test] + pub async fn get_encrypts_data_and_maps_unencrypted_response_data_to_instance() { + let executor = setup(); - unsafe { - Arc::as_ptr(&executor.rest_client) - .cast::() - .cast_mut() - .as_mut() - .unwrap() - .expect_request_binary() - .return_const(Ok(RestResponse { - status: 200, - headers: HashMap::new(), - body: Some(br#"{"_format": "1", "kdfVersion": "1", "salt": ""}"#.to_vec()), - })) - .with( - eq(String::from("http://api.tuta.com/rest/sys/saltservice")), - eq(HttpMethod::GET), - function(move |rest_client_options: &RestClientOptions| { - let expected_headers = [ - ("v", version), - ("accessToken", "access_token"), - ("cv", "1.2.3"), - ] - .into_iter() - .map(|(a, b)| (a.to_string(), b.to_string())) - .collect::>(); - assert_eq!(expected_headers, rest_client_options.headers); - let expected_response = serde_json::from_slice::( - br#"{"_format":"1", "mailAddress":"test@test.de"}"#, - ) - .unwrap(); - let raw_response = serde_json::from_slice::( - rest_client_options.body.as_ref().unwrap(), - ) - .unwrap(); - assert_eq!(expected_response, raw_response); - true - }), - ) - .times(1); + let version = executor + .type_model_provider + .get_type_model(SaltData::type_ref().app, SaltData::type_ref().type_) + .unwrap() + .version; - Arc::as_ptr(&executor.entity_facade) - .cast::() - .cast_mut() - .as_mut() - .unwrap() - .expect_encrypt_and_map_to_literal() - .return_const(Ok([ - ("_format".to_string(), ElementValue::Number(1)), - ( - "mailAddress".to_string(), - ElementValue::String("test@test.de".to_string()), - ), - ] - .into_iter() - .collect::())); - } + unsafe { + Arc::as_ptr(&executor.rest_client) + .cast::() + .cast_mut() + .as_mut() + .unwrap() + .expect_request_binary() + .return_const(Ok(RestResponse { + status: 200, + headers: HashMap::new(), + body: Some(br#"{"_format": "1", "kdfVersion": "1", "salt": ""}"#.to_vec()), + })) + .with( + eq(String::from("http://api.tuta.com/rest/sys/saltservice")), + eq(HttpMethod::GET), + function(move |rest_client_options: &RestClientOptions| { + let expected_headers = [ + ("v", version), + ("accessToken", "access_token"), + ("cv", "1.2.3"), + ] + .into_iter() + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect::>(); + assert_eq!(expected_headers, rest_client_options.headers); + let expected_response = serde_json::from_slice::( + br#"{"_format":"1", "mailAddress":"test@test.de"}"#, + ) + .unwrap(); + let raw_response = serde_json::from_slice::( + rest_client_options.body.as_ref().unwrap(), + ) + .unwrap(); + assert_eq!(expected_response, raw_response); + true + }), + ) + .times(1); - let data = SaltData { - mailAddress: "test".to_string(), - _format: 1, - }; - let result = executor - .get::(data, ExtraServiceParams::default()) - .await; + Arc::as_ptr(&executor.entity_facade) + .cast::() + .cast_mut() + .as_mut() + .unwrap() + .expect_encrypt_and_map_to_literal() + .return_const(Ok([ + ("_format".to_string(), ElementValue::Number(1)), + ( + "mailAddress".to_string(), + ElementValue::String("test@test.de".to_string()), + ), + ] + .into_iter() + .collect::())); + } - assert_eq!( - Ok(SaltReturn { - _format: 1, - kdfVersion: 1, - salt: Vec::new() - }), - result - ); - } + let data = SaltData { + mailAddress: "test".to_string(), + _format: 1, + }; + let result = executor + .get::(data, ExtraServiceParams::default()) + .await; + + assert_eq!( + Ok(SaltReturn { + _format: 1, + kdfVersion: 1, + salt: Vec::new() + }), + result + ); + } + + #[tokio::test] + pub async fn get_maps_encrypted_response_data_to_instance() { + let executor = setup(); + + let version = executor + .type_model_provider + .get_type_model(AlarmServicePost::type_ref().app, AlarmServicePost::type_ref().type_) + .unwrap() + .version; + + unsafe { + Arc::as_ptr(&executor.rest_client) + .cast::() + .cast_mut() + .as_mut() + .unwrap() + .expect_request_binary() + .return_const(Ok(RestResponse { + status: 200, + headers: HashMap::new(), + body: Some(br#"{"_format": "1", "countryCode": "DE", "keyHash": "", giftCardInfo: "------------"}"#.to_vec()), + })) + .with( + eq(String::from("http://api.tuta.com/rest/sys/alarmservice")), + eq(HttpMethod::GET), + function(move |rest_client_options: &RestClientOptions| { + let expected_headers = [ + ("v", version), + ("accessToken", "access_token"), + ("cv", "1.2.3"), + ] + .into_iter() + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect::>(); + assert_eq!(expected_headers, rest_client_options.headers); + let expected_response = serde_json::from_slice::( + br#"{"_format":"1", "mailAddress":"test@test.de"}"#, + ) + .unwrap(); + let raw_response = serde_json::from_slice::( + rest_client_options.body.as_ref().unwrap(), + ) + .unwrap(); + assert_eq!(expected_response, raw_response); + true + }), + ) + .times(1); + + Arc::as_ptr(&executor.entity_facade) + .cast::() + .cast_mut() + .as_mut() + .unwrap() + .expect_encrypt_and_map_to_literal() + .return_const(Ok([ + ("_format".to_string(), ElementValue::Number(1)), + ( + "mailAddress".to_string(), + ElementValue::String("test@test.de".to_string()), + ), + ] + .into_iter() + .collect::())); + } + + let data = GiftCardRedeemData { + countryCode: "de".to_string(), + keyHash: Vec::from([1, 2, 3]), + giftCardInfo: GeneratedId("------------".to_string()), + _format: 1, + }; + let result = executor + .get::(data, ExtraServiceParams::default()) + .await; + + assert_eq!( + Ok(GiftCardRedeemGetReturn { + _format: 1, + message: "test".to_string(), + value: 0, + giftCard: IdTuple::new(GeneratedId::test_random(), GeneratedId::test_random()), + _errors: None, + _finalIvs: Default::default(), + }), + result + ); + } }