etopay_sdk/core/viviswap/
swap.rs

1use crate::backend::viviswap::{
2    delete_viviswap_detail, get_viviswap_details, get_viviswap_order, get_viviswap_orders, get_viviswap_payment_method,
3    set_viviswap_contract, set_viviswap_detail,
4};
5use crate::core::Sdk;
6use crate::core::viviswap::ViviswapError;
7use crate::error::Result;
8use crate::types::currencies::{CryptoAmount, Currency};
9use crate::types::newtypes::EncryptionPin;
10use crate::types::viviswap::{
11    ViviswapAddressDetail, ViviswapDeposit, ViviswapDepositDetails, ViviswapDetailUpdateStrategy, ViviswapWithdrawal,
12    ViviswapWithdrawalDetails,
13};
14use api_types::api::viviswap::contract::ViviswapApiContractDetails;
15use api_types::api::viviswap::detail::SwapPaymentDetailKey;
16use api_types::api::viviswap::order::{Order, OrderList};
17use log::{debug, info};
18use rust_decimal_macros::dec;
19
20impl Sdk {
21    /// Get current iban of viviswap user
22    ///
23    /// # Arguments
24    ///
25    /// None
26    ///
27    /// # Returns
28    ///
29    /// - `Result<ViviswapAddressDetail>` - The current IBAN of the viviswap user.
30    ///
31    /// # Errors
32    ///
33    /// - [`crate::Error::ViviswapInvalidState`] - If the viviswap state is invalid.
34    /// - [`crate::Error::UserRepoNotInitialized`] - If the repository initialization fails.
35    /// - [`crate::Error::ViviswapApi`] - If there is an error in the viviswap API.
36    /// - [`crate::Error::UserStatusUpdateError`] - If there is an error updating the user status.
37    // MARK1:get_iban_for_viviswap
38    pub async fn get_iban_for_viviswap(&mut self) -> Result<ViviswapAddressDetail> {
39        info!("Getting IBAN for viviswap");
40        // load user entity
41        let mut user = self.get_user().await?;
42
43        // check if user has already a viviswap state available
44        let Some(mut viviswap_state) = user.viviswap_state else {
45            return Err(crate::Error::Viviswap(ViviswapError::UserStateExisting));
46        };
47
48        let Some(repo) = &mut self.repo else {
49            return Err(crate::Error::UserRepoNotInitialized);
50        };
51
52        // get iban from store
53        if let Some(iban) = viviswap_state.current_iban {
54            // try to get it from backend maybe
55            Ok(iban)
56        } else {
57            let access_token = self
58                .access_token
59                .as_ref()
60                .ok_or(crate::error::Error::MissingAccessToken)?;
61            let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
62            let details = get_viviswap_details(config, access_token, SwapPaymentDetailKey::Sepa).await?;
63            // update it internally
64            // get the first detail
65            if let Some(detail) = details.payment_detail.first() {
66                let new_detail = ViviswapAddressDetail {
67                    id: detail.id.clone(),
68                    address: detail.address.clone(),
69                    is_verified: detail.is_verified.unwrap_or(false),
70                };
71                viviswap_state.current_iban = Option::Some(new_detail.clone());
72                user.viviswap_state = Some(viviswap_state.clone());
73                repo.update(&user)?;
74                Ok(new_detail)
75            } else {
76                Err(crate::Error::Viviswap(ViviswapError::Api(
77                    "Unable to find IBAN at viviswap".to_string(),
78                )))
79            }
80        }
81    }
82
83    /// Ensure details
84    ///
85    /// # Arguments
86    ///
87    /// - `address` - The address to ensure.
88    /// - `payment_method_key` - The payment method key.
89    /// - `some_update_strategy` - The update strategy for the detail.
90    ///
91    /// # Returns
92    ///
93    /// - `Result<ViviswapAddressDetail>` - The ensured Viviswap address detail.
94    ///
95    /// # Errors
96    ///
97    /// - [`crate::Error::ViviswapApi`] - If there is an error loading payment details from the Viviswap API.
98    // MARK2:ensure_detail
99    async fn ensure_detail(
100        &self,
101        address: String,
102        payment_method_key: SwapPaymentDetailKey,
103        some_update_strategy: ViviswapDetailUpdateStrategy,
104    ) -> Result<ViviswapAddressDetail> {
105        debug!("Ensuring payment detail for viviswap");
106        // load user entity
107        let _user = self.get_user().await?;
108        let access_token = self
109            .access_token
110            .as_ref()
111            .ok_or(crate::error::Error::MissingAccessToken)?;
112        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
113
114        // get all viviswap details for specific payment-method
115        let details = get_viviswap_details(config, access_token, payment_method_key).await?;
116        debug!("{details:#?}");
117        // search for address in existing details
118        for detail in &details.payment_detail {
119            debug!("{detail:#?}");
120            let add = detail.address.clone();
121            debug!("{add:#?}");
122            debug!("{address:#?}");
123            if detail.address == address {
124                return Ok(ViviswapAddressDetail {
125                    id: detail.id.to_string(),
126                    address: detail.address.to_string(),
127                    is_verified: detail.is_verified.unwrap_or(false),
128                });
129            }
130        }
131
132        // 1. creates a new detail
133        let new_detail = set_viviswap_detail(config, access_token, payment_method_key, &address).await?;
134
135        // set_viviswap_detail should fill this out, but if not, we need to return an error
136        let Some(detail) = new_detail.payment_detail else {
137            return Err(crate::Error::Viviswap(ViviswapError::Api(
138                "no payment details returned".into(),
139            )));
140        };
141
142        // 2. handle the update strategy
143        if let (ViviswapDetailUpdateStrategy::Replace, Some(old_detail)) =
144            (some_update_strategy, details.payment_detail.first())
145        {
146            delete_viviswap_detail(config, access_token, payment_method_key, old_detail.id.as_str()).await?
147        }
148
149        // 3. returns the new detail
150        Ok(ViviswapAddressDetail {
151            id: detail.id,
152            address: detail.address,
153            is_verified: detail.is_verified.unwrap_or(false),
154        })
155    }
156
157    /// Update IBAN of viviswap user.
158    ///
159    /// # Arguments
160    ///
161    /// - `pin` - The user's PIN.
162    /// - `address` - The new IBAN address.
163    ///
164    /// # Returns
165    ///
166    /// - `Result<ViviswapAddressDetail>` - The updated Viviswap address detail.
167    ///
168    /// # Errors
169    ///
170    /// - [`crate::Error::UserRepoNotInitialized`] - If the repository initialization fails.
171    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
172    /// - [`crate::Error::UserStatusUpdateError`] - If there is an error updating the user status.
173    // MARK3:update_iban_for_viviswap
174    pub async fn update_iban_for_viviswap(
175        &mut self,
176        pin: &EncryptionPin,
177        address: String,
178    ) -> Result<ViviswapAddressDetail> {
179        info!("Updating user IBAN");
180        // verify pin
181        self.verify_pin(pin).await?;
182
183        // ensure that the repository exist (cannot borrow as mutable here since we also borrow self as mutable in between)
184        if self.repo.is_none() {
185            return Err(crate::Error::UserRepoNotInitialized);
186        }
187
188        // load user entity
189        let mut user = self.get_user().await?;
190
191        // check if user has already a viviswap state available
192        let Some(mut viviswap_state) = user.viviswap_state else {
193            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
194        };
195
196        // 1. check if iban is already saved to service
197        if let Some(current_iban) = viviswap_state.current_iban {
198            if current_iban.address == address {
199                return Ok(current_iban.clone());
200            }
201        }
202
203        // 2. check if iban does already exist
204        let new_detail_response = self
205            .ensure_detail(
206                address,
207                SwapPaymentDetailKey::Sepa,
208                ViviswapDetailUpdateStrategy::Replace,
209            )
210            .await?;
211        let new_detail = new_detail_response;
212
213        // 3. update storage
214
215        // load repository
216        if let Some(repo) = &mut self.repo {
217            viviswap_state.current_iban = Option::Some(new_detail.clone());
218            user.viviswap_state = Some(viviswap_state.clone());
219            repo.update(&user)?;
220        } else {
221            return Err(crate::Error::UserRepoNotInitialized);
222        };
223
224        Ok(new_detail)
225    }
226
227    /// create deposit for viviswap user
228    ///
229    /// # Returns
230    ///
231    /// - `Result<ViviswapDeposit>` - The created Viviswap deposit.
232    ///
233    /// # Errors
234    ///
235    /// - [`crate::Error::UserRepoNotInitialized`] - If the repository initialization fails.
236    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
237    /// - [`crate::Error::ViviswapInvalidState`] - If the viviswap state is invalid.
238    /// - [`crate::Error::ViviswapApi`] - If there is an error with the Viviswap API.
239    // MARK4:create_deposit_with_viviswap
240    pub async fn create_deposit_with_viviswap(&mut self, pin: &EncryptionPin) -> Result<ViviswapDeposit> {
241        info!("Creating deposit for viviswap");
242        // load user entity
243        let user = self.get_user().await?;
244        let address = self.generate_new_address(pin).await?;
245
246        // check if user has already a viviswap state available
247        let Some(viviswap_state) = user.viviswap_state else {
248            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
249        };
250
251        // check if iban exists, otherwise error
252        let Some(iban_detail) = viviswap_state.current_iban else {
253            return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
254        };
255
256        let iban_method_id = self.get_payment_method_id_viviswap(SwapPaymentDetailKey::Sepa).await?;
257        let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
258        let currency = Currency::try_from(network.display_symbol)?;
259
260        let payment_method_key = currency.to_vivi_payment_method_key();
261
262        let coin_method_id = self.get_payment_method_id_viviswap(payment_method_key).await?;
263
264        let coin_detail = self
265            .ensure_detail(address, payment_method_key, ViviswapDetailUpdateStrategy::Add)
266            .await?;
267        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
268
269        let access_token = self
270            .access_token
271            .as_ref()
272            .ok_or(crate::error::Error::MissingAccessToken)?;
273        let contract_response = set_viviswap_contract(
274            config,
275            access_token,
276            CryptoAmount::try_from(dec!(25.0))?, // any random amount works here!
277            iban_method_id,
278            Option::Some(iban_detail.id),
279            coin_method_id,
280            coin_detail.id,
281        )
282        .await?;
283
284        let new_contract = contract_response
285            .contract
286            .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
287                "Error creating the new contract for user.",
288            ))))?;
289        let bank_details = new_contract
290            .details
291            .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
292                "The new contract has invalid state. Deposit details are missing!",
293            ))))?;
294
295        match bank_details {
296            ViviswapApiContractDetails::BankAccount(account_details) => Ok(ViviswapDeposit {
297                contract_id: new_contract.id,
298                deposit_address: coin_detail.address,
299                details: ViviswapDepositDetails {
300                    reference: new_contract.reference,
301                    beneficiary: account_details.beneficiary,
302                    name_of_bank: account_details.name_of_bank,
303                    address_of_bank: account_details.address_of_bank,
304                    iban: account_details.address,
305                    bic: account_details.bic,
306                },
307            }),
308            _ => Err(crate::Error::Viviswap(ViviswapError::Api(String::from(
309                "The new contract has invalid state. Bank deposit details are missing!",
310            )))),
311        }
312    }
313
314    /// create detail for viviswap user
315    ///
316    /// # Returns
317    ///
318    /// - `Result<ViviswapAddressDetail>` - The created Viviswap address detail.
319    ///
320    /// # Errors
321    ///
322    /// - [`crate::Error::ConfigInitError`] - If there is an error initializing the configuration.
323    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
324    // MARK5:create_detail_for_viviswap
325    pub async fn create_detail_for_viviswap(&mut self, pin: &EncryptionPin) -> Result<ViviswapAddressDetail> {
326        let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
327        let currency = Currency::try_from(network.display_symbol)?;
328        let payment_method_key = currency.to_vivi_payment_method_key();
329
330        info!("Creating a payment detail for viviswap for {payment_method_key:?}");
331        // load user entity
332        let user = self.get_user().await?;
333
334        // check if user has already a viviswap state available
335        if user.viviswap_state.is_none() {
336            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
337        }
338        let address = self.generate_new_address(pin).await?;
339        // 1. check if address does already exist
340        let new_detail = self
341            .ensure_detail(address, payment_method_key, ViviswapDetailUpdateStrategy::Add)
342            .await?;
343
344        Ok(new_detail)
345    }
346
347    /// get payment methods and get the id for the given method key
348    ///
349    /// # Arguments
350    ///
351    /// * `payment_method_key` - The key of the payment method.
352    ///
353    /// # Returns
354    ///
355    /// - `Result<String>` - The ID of the payment method.
356    ///
357    /// # Errors
358    ///
359    /// - [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
360    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
361    /// - [`crate::Error::ViviswapApi`] - If the payment method is not found.
362    // MARK6:get_payment_method_id_viviswap
363    async fn get_payment_method_id_viviswap(&mut self, payment_method_key: SwapPaymentDetailKey) -> Result<String> {
364        let mut user = self.get_user().await?;
365
366        // load repository
367        let Some(repo) = &mut self.repo else {
368            return Err(crate::Error::UserRepoNotInitialized);
369        };
370
371        // check if user has already a viviswap state available
372        let Some(mut viviswap_state) = user.viviswap_state else {
373            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
374        };
375
376        let payment_methods = match viviswap_state.payment_methods {
377            Some(details) => details,
378            None => {
379                let access_token = self
380                    .access_token
381                    .as_ref()
382                    .ok_or(crate::error::Error::MissingAccessToken)?;
383                let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
384                let payment_methods = get_viviswap_payment_method(config, access_token).await?;
385                viviswap_state.payment_methods = Some(payment_methods.clone());
386                user.viviswap_state = Some(viviswap_state.clone());
387                repo.update(&user)?;
388                payment_methods
389            }
390        };
391
392        let method_id = payment_methods
393            .methods
394            .iter()
395            .find(|&method| method.key == payment_method_key)
396            .map(|method| method.id.clone())
397            .ok_or_else(|| {
398                crate::Error::Viviswap(ViviswapError::Api(format!(
399                    "Payment method not found for key: {payment_method_key:?}"
400                )))
401            })?;
402
403        Ok(method_id)
404    }
405
406    /// create withdrawal for viviswap user
407    ///
408    /// # Arguments
409    ///
410    /// * `amount` - The amount of the withdrawal.
411    /// * `pin` - The optional PIN for verification.
412    /// * `tag` - The transactions tag. Optional.
413    /// * `data` - The associated data with the tag. Optional.
414    /// * `message` - The transactions message. Optional.
415    ///
416    /// # Returns
417    ///
418    /// - `Result<ViviswapWithdrawal>` - The created Viviswap withdrawal.
419    ///
420    /// # Errors
421    ///
422    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
423    /// - [`crate::Error::ViviswapInvalidState`] - If the viviswap state is invalid.
424    /// - [`crate::Error::ViviswapApi`] - If there is an error with the Viviswap API.
425    // MARK7:create_withdrawal_with_viviswap
426    pub async fn create_withdrawal_with_viviswap(
427        &mut self,
428        amount: CryptoAmount,
429        pin: Option<&EncryptionPin>,
430        data: Option<Vec<u8>>,
431    ) -> Result<ViviswapWithdrawal> {
432        info!("Creating withdrawal with viviswap");
433        // load user entity
434        if let Some(pin) = pin {
435            self.verify_pin(pin).await?;
436        }
437        let user = self.get_user().await?;
438
439        // check if user has already a viviswap state available
440        let Some(viviswap_state) = user.viviswap_state else {
441            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
442        };
443
444        let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
445        let currency = Currency::try_from(network.display_symbol)?;
446
447        // check if iban exists, otherwise error
448        let Some(iban_detail) = viviswap_state.current_iban else {
449            return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
450        };
451
452        let iban_method_id = self.get_payment_method_id_viviswap(SwapPaymentDetailKey::Sepa).await?;
453
454        let coin_method_id = self
455            .get_payment_method_id_viviswap(currency.to_vivi_payment_method_key())
456            .await?;
457
458        let access_token = self
459            .access_token
460            .as_ref()
461            .ok_or(crate::error::Error::MissingAccessToken)?;
462        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
463
464        let contract_response = set_viviswap_contract(
465            config,
466            access_token,
467            amount,
468            coin_method_id,
469            Option::None,
470            iban_method_id,
471            iban_detail.id.clone(),
472        )
473        .await?;
474
475        let new_contract = contract_response
476            .contract
477            .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
478                "Error creating the new contract for user.",
479            ))))?;
480        let withdrawal_details =
481            new_contract
482                .details
483                .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
484                    "The new contract has invalid state. Withdrawal details are missing!",
485                ))))?;
486
487        match withdrawal_details {
488            ViviswapApiContractDetails::Crypto(crypto_details) => {
489                if let Some(pin) = pin {
490                    self.send_amount(pin, &crypto_details.deposit_address, amount, data)
491                        .await?;
492                }
493                Ok(ViviswapWithdrawal {
494                    contract_id: new_contract.id,
495                    deposit_address: iban_detail.address.clone(),
496                    details: ViviswapWithdrawalDetails {
497                        reference: new_contract.reference,
498                        wallet_id: crypto_details.wallet_id,
499                        crypto_address: crypto_details.deposit_address,
500                    },
501                })
502            }
503            _ => Err(crate::Error::Viviswap(ViviswapError::Api(String::from(
504                "The new contract has invalid state. Crypto deposit details are missing!",
505            )))),
506        }
507    }
508
509    /// Get the list of swaps for the viviswap user.
510    ///
511    /// # Returns
512    ///
513    /// Returns a `Result` containing a vector of `Swap` if successful, or a [`crate::Error`] if an error occurs.
514    ///
515    /// # Errors
516    ///
517    /// Returns an `Err` variant of [`crate::Error`] if any of the following conditions are met:
518    ///
519    /// * Repository initialization error.
520    /// * Viviswap API error.
521    // MARK8:get_swap_list
522    pub async fn get_swap_list(&self, start: u32, limit: u32) -> Result<OrderList> {
523        let Some(_user) = &self.active_user else {
524            return Err(crate::Error::UserRepoNotInitialized);
525        };
526        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
527        info!("get_swap_list request");
528
529        let access_token = self
530            .access_token
531            .as_ref()
532            .ok_or(crate::error::Error::MissingAccessToken)?;
533        let orders = get_viviswap_orders(config, access_token, start, limit).await?;
534        Ok(orders)
535    }
536
537    /// Get swap details
538    ///
539    /// # Arguments
540    ///
541    /// * `order_id` - The ID of the swap order.
542    ///
543    /// # Returns
544    ///
545    /// Returns a `Result` containing the swap order details or an error.
546    // MARK9:get_swap_details
547    pub async fn get_swap_details(&self, order_id: String) -> Result<Order> {
548        let Some(_user) = &self.active_user else {
549            return Err(crate::Error::UserRepoNotInitialized);
550        };
551        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
552        info!("get_swap_details request for order_id: {order_id}");
553        let access_token = self
554            .access_token
555            .as_ref()
556            .ok_or(crate::error::Error::MissingAccessToken)?;
557        match get_viviswap_order(config, access_token, &order_id).await {
558            Ok(order_detail) => Ok(order_detail),
559            Err(_) => Err(crate::Error::Viviswap(ViviswapError::Api(format!(
560                "Swap id:{order_id} not found"
561            )))),
562        }
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569    use crate::testing_utils::{
570        ADDRESS, AUTH_PROVIDER, ETH_NETWORK_KEY, HEADER_X_APP_NAME, IOTA_NETWORK_KEY, ORDER_ID, PIN, TOKEN, USERNAME,
571        example_api_network, example_api_networks, example_bank_details, example_contract_response,
572        example_crypto_details, example_get_payment_details_response, example_get_user, example_viviswap_oder_response,
573        set_config,
574    };
575    use crate::types::users::KycType;
576    use crate::{
577        core::Sdk,
578        types::users::ActiveUser,
579        user::MockUserRepo,
580        wallet::wallet::MockWalletUser,
581        wallet_manager::{MockWalletManager, WalletBorrow},
582    };
583    use api_types::api::networks::ApiNetwork;
584    use api_types::api::{dlt::SetUserAddressRequest, viviswap::order::GetOrdersResponse};
585    use mockito::Matcher;
586    use rand::Rng;
587    use rstest::rstest;
588
589    /// Create an active user
590    fn get_active_user() -> ActiveUser {
591        ActiveUser {
592            username: USERNAME.into(),
593            wallet_manager: Box::new(MockWalletManager::new()),
594        }
595    }
596
597    #[tokio::test]
598    async fn test_err_get_swap_list_should_consume_only_authenticated_requests() {
599        // Arrange
600        let (_srv, config, _cleanup) = set_config().await;
601
602        let mut sdk = Sdk::new(config).unwrap();
603        sdk.repo = Some(Box::new(MockUserRepo::new()));
604        sdk.access_token = Some(TOKEN.clone());
605        sdk.active_user = None;
606
607        // Call the function you want to test
608        let result = sdk.get_swap_list(1, 2).await;
609
610        assert!(result.is_err());
611    }
612
613    #[tokio::test]
614    async fn test_err_get_swap_details_should_consume_only_authenticated_requests() {
615        // Arrange
616        let (_srv, config, _cleanup) = set_config().await;
617
618        let mut sdk = Sdk::new(config).unwrap();
619        sdk.repo = Some(Box::new(MockUserRepo::new()));
620        sdk.access_token = Some(TOKEN.clone());
621        sdk.active_user = None;
622
623        // Call the function you want to test
624        let result = sdk.get_swap_details(String::from(ORDER_ID)).await;
625
626        assert!(result.is_err());
627    }
628
629    #[tokio::test]
630    async fn test_ok_get_swap_details() {
631        // Arrange
632        let (mut srv, config, _cleanup) = set_config().await;
633        let mut sdk = Sdk::new(config).unwrap();
634        sdk.repo = Some(Box::new(MockUserRepo::new()));
635        sdk.access_token = Some(TOKEN.clone());
636        sdk.active_user = Some(get_active_user());
637
638        let mock_response = example_viviswap_oder_response();
639        let body = serde_json::to_string(&mock_response).unwrap();
640
641        let mock_server = srv
642            .mock("GET", format!("/api/viviswap/orders?id={}", ORDER_ID).as_str())
643            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
644            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
645            .with_status(200)
646            .with_body(&body)
647            .with_header("content-type", "application/json")
648            .with_body(&body)
649            .create();
650
651        // Call the function you want to test
652        let result = sdk.get_swap_details(String::from(ORDER_ID)).await;
653
654        // Assert
655        assert_eq!(result.unwrap(), example_viviswap_oder_response());
656        mock_server.assert();
657    }
658
659    #[tokio::test]
660    async fn test_ok_get_swap_list() {
661        // Arrange
662        let (mut srv, config, _cleanup) = set_config().await;
663        let mut sdk = Sdk::new(config).unwrap();
664        sdk.repo = Some(Box::new(MockUserRepo::new()));
665        sdk.access_token = Some(TOKEN.clone());
666        sdk.active_user = Some(get_active_user());
667
668        let mock_order = example_viviswap_oder_response();
669        let mock_response = GetOrdersResponse {
670            count: 1,
671            start: 2,
672            limit: 3,
673            orders: vec![mock_order],
674        };
675        let body = serde_json::to_string(&mock_response).unwrap();
676
677        let mock_server = srv
678            .mock("GET", "/api/viviswap/orders?start=2&limit=1")
679            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
680            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
681            .with_status(200)
682            .with_body(&body)
683            .with_header("content-type", "application/json")
684            .with_body(&body)
685            .create();
686
687        // Call the function you want to test
688        let result = sdk.get_swap_list(2, 1).await;
689
690        // Assert
691        let result = result.unwrap();
692        assert_eq!(result.orders[0].contract_id, mock_response.orders[0].contract_id);
693        assert_eq!(result.orders[0].crypto_fees, mock_response.orders[0].crypto_fees);
694        assert_eq!(
695            result.orders[0].fees_amount_eur,
696            mock_response.orders[0].fees_amount_eur
697        );
698        mock_server.assert();
699    }
700
701    #[tokio::test]
702    async fn test_err_get_swap_list_server_error() {
703        // Arrange
704        let (mut srv, config, _cleanup) = set_config().await;
705        let mut sdk = Sdk::new(config).unwrap();
706        sdk.repo = Some(Box::new(MockUserRepo::new()));
707        sdk.access_token = Some(TOKEN.clone());
708        sdk.active_user = Some(get_active_user());
709
710        let mock_response = GetOrdersResponse {
711            count: 1,
712            start: 2,
713            limit: 3,
714            orders: vec![],
715        };
716        let body = serde_json::to_string(&mock_response).unwrap();
717
718        let server_error_status = rand::rng().random_range(400..410);
719        let mock_server = srv
720            .mock("GET", "/api/viviswap/orders?start=2&limit=1")
721            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
722            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
723            .with_status(server_error_status)
724            .with_body(&body)
725            .with_header("content-type", "application/json")
726            .with_body(&body)
727            .create();
728
729        // Call the function you want to test
730        let result = sdk.get_swap_list(2, 1).await;
731
732        assert!(result.is_err());
733        mock_server.assert();
734    }
735
736    #[tokio::test]
737    async fn test_err_get_swap_details_server_error() {
738        // Arrange
739        let (mut srv, config, _cleanup) = set_config().await;
740        let mut sdk = Sdk::new(config).unwrap();
741        sdk.repo = Some(Box::new(MockUserRepo::new()));
742        sdk.access_token = Some(TOKEN.clone());
743        sdk.active_user = Some(get_active_user());
744
745        let server_error_status = rand::rng().random_range(400..410);
746        let mock_server = srv
747            .mock("GET", format!("/api/viviswap/orders?id={}", ORDER_ID).as_str())
748            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
749            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
750            .with_status(server_error_status)
751            .with_header("content-type", "application/json")
752            .create();
753
754        // Call the function you want to test
755        let result = sdk.get_swap_details(String::from(ORDER_ID)).await;
756
757        // Assert
758        assert!(result.is_err());
759        mock_server.assert();
760    }
761
762    #[rstest]
763    #[case(
764        example_api_network(IOTA_NETWORK_KEY.to_string()),
765        SwapPaymentDetailKey::Iota,
766        "/api/viviswap/details?payment_method_key=IOTA"
767    )]
768    #[case(
769        example_api_network(ETH_NETWORK_KEY.to_string()),
770        SwapPaymentDetailKey::Eth,
771        "/api/viviswap/details?payment_method_key=ETH"
772    )]
773    #[tokio::test]
774    async fn it_should_create_viviswap_deposit(
775        #[case] network: ApiNetwork,                      // Parametrized network
776        #[case] payment_detail_key: SwapPaymentDetailKey, // Payment detail key (Iota, Eth, etc.)
777        #[case] payment_method_path: &str,                // The payment method query path
778    ) {
779        // Arrange
780        let (mut srv, config, _cleanup) = set_config().await;
781        let mut sdk = Sdk::new(config).unwrap();
782        sdk.access_token = Some(TOKEN.clone());
783
784        sdk.set_networks(example_api_networks());
785        sdk.set_network(network.key.clone()).await.unwrap(); // Set parametrized network
786        sdk.refresh_access_token(Some(TOKEN.clone())).await.unwrap();
787
788        let mock_user_repo = example_get_user(payment_detail_key, false, 5, KycType::Viviswap);
789        sdk.repo = Some(Box::new(mock_user_repo));
790
791        let mut mock_wallet_manager = MockWalletManager::new();
792        mock_wallet_manager.expect_try_get().returning({
793            move |_, _, _, _, _| {
794                let mut mock_wallet = MockWalletUser::new();
795                mock_wallet
796                    .expect_get_address()
797                    .once()
798                    .returning(move || Ok(ADDRESS.to_string()));
799                Ok(WalletBorrow::from(mock_wallet))
800            }
801        });
802        sdk.active_user = Some(ActiveUser {
803            username: USERNAME.into(),
804            wallet_manager: Box::new(mock_wallet_manager),
805        });
806
807        // create viviswap contract
808        let contract_mock_response = example_contract_response(example_bank_details());
809        let body = serde_json::to_string(&contract_mock_response).unwrap();
810        let create_viviswap_contract = srv
811            .mock("POST", "/api/viviswap/contracts")
812            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
813            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
814            .with_status(200)
815            .with_body(&body)
816            .with_header("content-type", "application/json")
817            .with_body(&body)
818            .create();
819
820        // Get user payment details
821        let payment_details_mock_response = example_get_payment_details_response();
822        let body = serde_json::to_string(&payment_details_mock_response).unwrap();
823        let get_payment_details = srv
824            .mock("GET", payment_method_path) // Use the parametrized payment method path
825            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
826            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
827            .with_status(200)
828            .with_body(&body)
829            .with_header("content-type", "application/json")
830            .with_body(&body)
831            .create();
832
833        let mock_request = SetUserAddressRequest {
834            address: ADDRESS.to_string(),
835        };
836        let body = serde_json::to_string(&mock_request).unwrap();
837
838        let put_user_address = srv
839            .mock("PUT", "/api/user/address")
840            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
841            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
842            .match_query(Matcher::Exact(format!("network_key={}", network.key)))
843            .match_body(Matcher::Exact(body))
844            .with_status(201)
845            .expect(1)
846            .with_header("content-type", "application/json")
847            .create();
848
849        // Call the function you want to test
850        let _ = sdk.create_deposit_with_viviswap(&PIN).await.unwrap();
851
852        // Assert
853        put_user_address.assert();
854        get_payment_details.assert();
855        create_viviswap_contract.assert();
856    }
857
858    #[tokio::test]
859    async fn it_should_create_withdrawal_deposit_for_iota_and_smr() {
860        // Arrange
861        let (mut srv, config, _cleanup) = set_config().await;
862
863        let mut sdk = Sdk::new(config).unwrap();
864        sdk.set_networks(example_api_networks());
865        sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
866
867        let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 3, KycType::Viviswap);
868        sdk.repo = Some(Box::new(mock_user_repo));
869
870        sdk.access_token = Some(TOKEN.clone());
871        sdk.active_user = Some(get_active_user());
872
873        // create viviswap contract
874        let contract_mock_response = example_contract_response(example_crypto_details());
875        let body = serde_json::to_string(&contract_mock_response).unwrap();
876        let create_viviswap_contract = srv
877            .mock("POST", "/api/viviswap/contracts")
878            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
879            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
880            .with_status(200)
881            .with_body(&body)
882            .with_header("content-type", "application/json")
883            .with_body(&body)
884            .create();
885
886        // Call the function you want to test
887        let result = sdk
888            .create_withdrawal_with_viviswap(dec!(50.0).try_into().unwrap(), None, Some(Vec::from([8, 16])))
889            .await;
890
891        // Assert
892        result.unwrap();
893        create_viviswap_contract.assert();
894    }
895}