1use super::Sdk;
5use crate::backend;
6use crate::backend::kyc::check_kyc_status;
7use crate::error::Result;
8use crate::types::newtypes::AccessToken;
9use crate::types::newtypes::EncryptionPin;
10use crate::types::newtypes::EncryptionSalt;
11use crate::types::users::{ActiveUser, KycType, UserEntity};
12use log::{debug, info, warn};
13
14impl Sdk {
15 pub async fn get_user(&self) -> Result<UserEntity> {
25 debug!("Getting the user");
26 let Some(repo) = &self.repo else {
27 return Err(crate::Error::UserRepoNotInitialized);
28 };
29
30 let Some(active_user) = &self.active_user else {
32 return Err(crate::Error::UserNotInitialized);
33 };
34
35 Ok(repo.get(active_user.username.as_str())?)
37 }
38
39 pub async fn create_new_user(&mut self, username: &str) -> Result<()> {
53 info!("Creating a new user");
54 let Some(repo) = &mut self.repo else {
55 return Err(crate::Error::UserRepoNotInitialized);
56 };
57
58 let salt = EncryptionSalt::generate();
59 let user = UserEntity {
60 user_id: None,
61 username: username.into(),
62 encrypted_password: None,
63 salt,
64 is_kyc_verified: false,
65 kyc_type: KycType::Undefined,
66 viviswap_state: Option::None,
67 local_share: None,
68 wallet_transactions: Vec::new(),
69 };
70
71 repo.create(&user)?;
72
73 Ok(())
74 }
75
76 pub async fn delete_user(&mut self, pin: Option<&EncryptionPin>) -> Result<()> {
90 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
91
92 let user_entity = self.get_user().await?;
93
94 if user_entity.encrypted_password.is_some() {
96 let pin = pin.ok_or(crate::Error::Wallet(crate::WalletError::WrongPinOrPassword))?;
97 self.verify_pin(pin).await?;
98 info!("Pin verified");
99 }
100
101 let Some(active_user) = &mut self.active_user else {
102 return Err(crate::Error::UserNotInitialized);
103 };
104
105 warn!("Deleting an existing user");
106
107 let Some(repo) = &mut self.repo else {
108 return Err(crate::Error::UserRepoNotInitialized);
109 };
110
111 let username = &active_user.username;
112
113 let access_token = self
115 .access_token
116 .as_ref()
117 .ok_or(crate::error::Error::MissingAccessToken)?;
118 crate::backend::user::delete_user_account(config, access_token).await?;
119
120 repo.delete(username)?;
122
123 if let Err(e) = active_user
125 .wallet_manager
126 .delete_wallet(config, &self.access_token, repo)
127 .await
128 {
129 log::error!("Error deleting the wallet: {e:?}");
130 }
131
132 Ok(())
133 }
134
135 pub async fn init_user(&mut self, username: &str) -> Result<()> {
149 info!("Initializing user {username}");
150 let Some(repo) = &mut self.repo else {
151 return Err(crate::Error::UserRepoNotInitialized);
152 };
153 let user = repo.get(username)?;
154 let active_user = ActiveUser::from(user);
155
156 if let Some(access_token) = &self.access_token {
157 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
158 let status = check_kyc_status(config, access_token, username).await?;
159 repo.set_kyc_state(username, status.is_verified)?;
160 }
161
162 self.active_user = Some(active_user);
163
164 Ok(())
165 }
166
167 pub async fn refresh_access_token(&mut self, access_token: Option<AccessToken>) -> Result<()> {
181 self.access_token = access_token;
182
183 Ok(())
184 }
185
186 pub async fn is_kyc_status_verified(&mut self, username: &str) -> Result<bool> {
200 info!("Checking KYC status of user {username}");
201 let Some(repo) = &mut self.repo else {
202 return Err(crate::Error::UserRepoNotInitialized);
203 };
204 let access_token = self
205 .access_token
206 .as_ref()
207 .ok_or(crate::error::Error::MissingAccessToken)?;
208
209 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
210 let status = check_kyc_status(config, access_token, username).await?;
211 let is_verified = status.is_verified;
212
213 if is_verified {
215 repo.set_kyc_state(username, is_verified)?;
216 }
217
218 Ok(is_verified)
219 }
220
221 pub async fn set_preferred_network(&mut self, network_key: Option<String>) -> Result<()> {
223 let Some(_user) = &self.active_user else {
224 return Err(crate::Error::UserNotInitialized);
225 };
226 let access_token = self
227 .access_token
228 .as_ref()
229 .ok_or(crate::error::Error::MissingAccessToken)?;
230 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
231 backend::user::set_preferred_network(config, access_token, network_key).await?;
232 Ok(())
233 }
234
235 pub async fn get_preferred_network(&self) -> Result<Option<String>> {
237 let Some(_user) = &self.active_user else {
238 return Err(crate::Error::UserNotInitialized);
239 };
240 let access_token = self
241 .access_token
242 .as_ref()
243 .ok_or(crate::error::Error::MissingAccessToken)?;
244 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
245 let preferred_network = backend::user::get_preferred_network(config, access_token).await?;
246 Ok(preferred_network)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use crate::core::core_testing_utils::handle_error_test_cases;
254 use crate::testing_utils::{
255 AUTH_PROVIDER, HEADER_X_APP_NAME, IOTA_NETWORK_KEY, TOKEN, USERNAME, example_get_user, set_config,
256 };
257 use crate::{core::Sdk, user::MockUserRepo, wallet_manager::MockWalletManager};
258 use api_types::api::kyc::KycStatusResponse;
259 use api_types::api::viviswap::detail::SwapPaymentDetailKey;
260 use rstest::rstest;
261
262 #[rstest]
263 #[case::success(Ok(example_get_user(SwapPaymentDetailKey::Iota, false, 0, KycType::Undefined)))]
264 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
265 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
266 #[case::user_not_found(Err(crate::Error::UserRepository(crate::user::error::UserKvStorageError::UserNotFound { username: USERNAME.to_string() })))]
267 #[tokio::test]
268 async fn test_get_user(#[case] expected: Result<MockUserRepo>) {
269 let (_srv, config, _cleanup) = set_config().await;
271 let mut sdk = Sdk::new(config).unwrap();
272
273 match &expected {
274 Ok(_) => {
275 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
276 sdk.repo = Some(Box::new(mock_user_repo));
277
278 sdk.active_user = Some(crate::types::users::ActiveUser {
279 username: USERNAME.into(),
280 wallet_manager: Box::new(MockWalletManager::new()),
281 });
282 }
283 Err(error) => {
284 handle_error_test_cases(error, &mut sdk, 0, 0).await;
285 }
286 }
287
288 let response = sdk.get_user().await;
290
291 match expected {
293 Ok(_resp) => {
294 assert!(response.is_ok());
295 }
296 Err(ref expected_err) => {
297 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
298 }
299 }
300 }
301
302 #[rstest]
303 #[case::success(Ok(()))]
304 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
305 #[tokio::test]
306 async fn test_create_new_user(#[case] expected: Result<()>) {
307 let (_srv, config, _cleanup) = set_config().await;
309 let mut sdk = Sdk::new(config).unwrap();
310
311 match &expected {
312 Ok(_) => {
313 let mut mock_user_repo = MockUserRepo::new();
314 mock_user_repo.expect_create().times(1).returning(|_| Ok(()));
315
316 sdk.repo = Some(Box::new(mock_user_repo));
317
318 sdk.active_user = Some(crate::types::users::ActiveUser {
319 username: USERNAME.into(),
320 wallet_manager: Box::new(MockWalletManager::new()),
321 });
322 }
323 Err(error) => {
324 handle_error_test_cases(error, &mut sdk, 0, 0).await;
325 }
326 }
327
328 let response = sdk.create_new_user("new_user").await;
330
331 match expected {
333 Ok(_) => response.unwrap(),
334 Err(ref expected_err) => {
335 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
336 }
337 }
338 }
339
340 #[rstest]
341 #[case::success(Ok(()))]
342 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
343 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
344 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
345 #[case::missing_config(Err(crate::Error::MissingConfig))]
346 #[tokio::test]
347 async fn test_delete_user(#[case] expected: Result<()>) {
348 let (mut srv, config, _cleanup) = set_config().await;
350 let mut sdk = Sdk::new(config).unwrap();
351
352 let pin = EncryptionPin::try_from_string("123456").unwrap();
353
354 let mut mock_server = None;
355 match &expected {
356 Ok(_) => {
357 let mut mock_wallet_user = MockWalletManager::new();
358 mock_wallet_user
359 .expect_delete_wallet()
360 .once()
361 .returning(|_, _, _| Ok(()));
362
363 sdk.active_user = Some(crate::types::users::ActiveUser {
364 username: USERNAME.into(),
365 wallet_manager: Box::new(mock_wallet_user),
366 });
367
368 sdk.access_token = Some(TOKEN.clone());
369
370 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 3, KycType::Undefined);
371 mock_user_repo.expect_update().once().returning(|_| Ok(()));
372 mock_user_repo.expect_delete().once().returning(|uname| {
373 assert_eq!(uname, USERNAME);
374 Ok(())
375 });
376 sdk.repo = Some(Box::new(mock_user_repo));
377
378 let new_pin = EncryptionPin::try_from_string("123456").unwrap();
379 sdk.change_pin(&pin, &new_pin).await.unwrap();
380
381 mock_server = Some(
382 srv.mock("DELETE", "/api/user")
383 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
384 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
385 .with_status(202) .expect(1)
387 .create(),
388 );
389 }
390 Err(error) => {
391 handle_error_test_cases(error, &mut sdk, 0, 2).await;
392 }
393 }
394
395 let response = sdk.delete_user(Some(&pin)).await;
397
398 match expected {
400 Ok(_) => response.unwrap(),
401 Err(ref expected_err) => {
402 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
403 }
404 }
405 if let Some(m) = mock_server {
406 m.assert();
407 }
408 }
409
410 #[rstest]
411 #[case::success(true, Ok(()))]
412 #[case::repo_init_error(false, Err(crate::Error::UserRepoNotInitialized))]
413 #[case::missing_config(false, Err(crate::Error::MissingConfig))]
414 #[tokio::test]
415 async fn test_init_user(#[case] _access_token: bool, #[case] expected: Result<()>) {
416 let (mut srv, config, _cleanup) = set_config().await;
418 let mut sdk = Sdk::new(config).unwrap();
419
420 let mut mock_server = None;
421 match &expected {
422 Ok(_) => {
423 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
424 mock_user_repo.expect_set_kyc_state().times(1).returning(|_, _| Ok(()));
425 sdk.repo = Some(Box::new(mock_user_repo));
426
427 sdk.active_user = Some(crate::types::users::ActiveUser {
428 username: USERNAME.into(),
429 wallet_manager: Box::new(MockWalletManager::new()),
430 });
431
432 sdk.access_token = Some(TOKEN.clone());
433
434 let mock_response = KycStatusResponse {
435 username: USERNAME.into(),
436 is_verified: true,
437 };
438 let body = serde_json::to_string(&mock_response).unwrap();
439
440 mock_server = Some(
441 srv.mock("GET", "/api/kyc/check-status")
442 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
443 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
444 .with_status(200)
445 .with_body(body)
446 .create(),
447 );
448 }
449 Err(error) => {
450 handle_error_test_cases(error, &mut sdk, 1, 0).await;
451 }
452 }
453
454 let response = sdk.init_user(USERNAME).await;
456
457 match expected {
459 Ok(_) => response.unwrap(),
460 Err(ref expected_err) => {
461 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
462 }
463 }
464 if let Some(m) = mock_server {
465 m.assert();
466 }
467 }
468
469 #[rstest]
470 #[case::success(Ok(true))]
471 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
472 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
473 #[case::missing_config(Err(crate::Error::MissingConfig))]
474 #[tokio::test]
475 async fn test_is_kyc_verified(#[case] expected: Result<bool>) {
476 let (mut srv, config, _cleanup) = set_config().await;
478 let mut sdk = Sdk::new(config).unwrap();
479 let mut mock_server = None;
480
481 match &expected {
482 Ok(_) => {
483 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 0, KycType::Undefined);
484 mock_user_repo.expect_set_kyc_state().times(1).returning(|_, _| Ok(()));
485 sdk.repo = Some(Box::new(mock_user_repo));
486
487 sdk.active_user = Some(crate::types::users::ActiveUser {
488 username: USERNAME.into(),
489 wallet_manager: Box::new(MockWalletManager::new()),
490 });
491
492 sdk.access_token = Some(TOKEN.clone());
493
494 let mock_response = KycStatusResponse {
495 username: USERNAME.into(),
496 is_verified: true,
497 };
498 let body = serde_json::to_string(&mock_response).unwrap();
499
500 mock_server = Some(
501 srv.mock("GET", "/api/kyc/check-status")
502 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
503 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
504 .with_status(200)
505 .with_body(body)
506 .create(),
507 );
508 }
509 Err(error) => {
510 handle_error_test_cases(error, &mut sdk, 0, 0).await;
511 }
512 }
513
514 let response = sdk.is_kyc_status_verified(USERNAME).await;
516
517 match expected {
519 Ok(verified) => assert!(verified),
520 Err(ref expected_err) => {
521 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
522 }
523 }
524 if let Some(m) = mock_server {
525 m.assert();
526 }
527 }
528
529 #[rstest]
530 #[case::success(Ok(()))]
531 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
532 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
533 #[case::missing_config(Err(crate::Error::MissingConfig))]
534 #[tokio::test]
535 async fn test_set_preferred_network(#[case] expected: Result<()>) {
536 let (mut srv, config, _cleanup) = set_config().await;
538 let mut sdk = Sdk::new(config).unwrap();
539 let mut mock_server = None;
540
541 match &expected {
542 Ok(_) => {
543 sdk.active_user = Some(crate::types::users::ActiveUser {
544 username: USERNAME.into(),
545 wallet_manager: Box::new(MockWalletManager::new()),
546 });
547 sdk.access_token = Some(TOKEN.clone());
548
549 mock_server = Some(
550 srv.mock("PUT", "/api/user/network")
551 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
552 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
553 .with_status(202)
554 .expect(1)
555 .create(),
556 );
557 }
558 Err(error) => {
559 handle_error_test_cases(error, &mut sdk, 0, 0).await;
560 }
561 }
562
563 let response = sdk.set_preferred_network(Some(IOTA_NETWORK_KEY.to_string())).await;
565
566 match expected {
568 Ok(()) => response.unwrap(),
569 Err(ref expected_err) => {
570 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
571 }
572 }
573 if let Some(m) = mock_server {
574 m.assert();
575 }
576 }
577
578 #[rstest]
579 #[case::success(Ok(Some(IOTA_NETWORK_KEY.to_string())))]
580 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
581 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
582 #[case::missing_config(Err(crate::Error::MissingConfig))]
583 #[tokio::test]
584 async fn test_get_preferred_network(#[case] expected: Result<Option<String>>) {
585 let (mut srv, config, _cleanup) = set_config().await;
587 let mut sdk = Sdk::new(config).unwrap();
588 let mut mock_server = None;
589
590 match &expected {
591 Ok(_) => {
592 sdk.active_user = Some(crate::types::users::ActiveUser {
593 username: USERNAME.into(),
594 wallet_manager: Box::new(MockWalletManager::new()),
595 });
596 sdk.access_token = Some(TOKEN.clone());
597
598 mock_server = Some(
599 srv.mock("GET", "/api/user/network")
600 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
601 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
602 .with_status(200)
603 .with_header("content-type", "application/json")
604 .with_body("{\"network_key\":\"IOTA\"}")
605 .expect(1)
606 .create(),
607 );
608 }
609 Err(error) => {
610 handle_error_test_cases(error, &mut sdk, 0, 0).await;
611 }
612 }
613
614 let response = sdk.get_preferred_network().await;
616
617 match expected {
619 Ok(network_key) => {
620 assert_eq!(response.unwrap(), network_key)
621 }
622 Err(ref expected_err) => {
623 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
624 }
625 }
626 if let Some(m) = mock_server {
627 m.assert();
628 }
629 }
630}