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