Bug: Implement `MandateInterface` for `MockDb`



Spin out from https://github.com/juspay/hyperswitch/issues/172. Please refer to that issue for more information.
diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs
index 0bec1c7c081..3cd510b33e5 100644
--- a/crates/router/src/db.rs
+++ b/crates/router/src/db.rs
@@ -112,6 +112,7 @@ pub struct MockDb {
     events: Arc<Mutex<Vec<storage::Event>>>,
     disputes: Arc<Mutex<Vec<storage::Dispute>>>,
     lockers: Arc<Mutex<Vec<storage::LockerMockUp>>>,
+    mandates: Arc<Mutex<Vec<storage::Mandate>>>,
 }
 
 impl MockDb {
@@ -132,6 +133,7 @@ impl MockDb {
             events: Default::default(),
             disputes: Default::default(),
             lockers: Default::default(),
+            mandates: Default::default(),
         }
     }
 }
diff --git a/crates/router/src/db/mandate.rs b/crates/router/src/db/mandate.rs
index 2ac9595b776..a53409067ce 100644
--- a/crates/router/src/db/mandate.rs
+++ b/crates/router/src/db/mandate.rs
@@ -3,8 +3,11 @@ use error_stack::IntoReport;
 use super::{MockDb, Store};
 use crate::{
     connection,
-    core::errors::{self, CustomResult},
-    types::storage::{self, MandateDbExt},
+    core::{errors, errors::CustomResult},
+    types::{
+        storage::{self, MandateDbExt},
+        transformers::ForeignInto,
+    },
 };
 
 #[async_trait::async_trait]
@@ -108,46 +111,148 @@ impl MandateInterface for Store {
 impl MandateInterface for MockDb {
     async fn find_mandate_by_merchant_id_mandate_id(
         &self,
-        _merchant_id: &str,
-        _mandate_id: &str,
+        merchant_id: &str,
+        mandate_id: &str,
     ) -> CustomResult<storage::Mandate, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        self.mandates
+            .lock()
+            .await
+            .iter()
+            .find(|mandate| mandate.merchant_id == merchant_id && mandate.mandate_id == mandate_id)
+            .cloned()
+            .ok_or_else(|| errors::StorageError::ValueNotFound("mandate not found".to_string()))
+            .map_err(|err| err.into())
     }
 
     async fn find_mandate_by_merchant_id_customer_id(
         &self,
-        _merchant_id: &str,
-        _customer_id: &str,
+        merchant_id: &str,
+        customer_id: &str,
     ) -> CustomResult<Vec<storage::Mandate>, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        return Ok(self
+            .mandates
+            .lock()
+            .await
+            .iter()
+            .filter(|mandate| {
+                mandate.merchant_id == merchant_id && mandate.customer_id == customer_id
+            })
+            .cloned()
+            .collect());
     }
 
     async fn update_mandate_by_merchant_id_mandate_id(
         &self,
-        _merchant_id: &str,
-        _mandate_id: &str,
-        _mandate: storage::MandateUpdate,
+        merchant_id: &str,
+        mandate_id: &str,
+        mandate_update: storage::MandateUpdate,
     ) -> CustomResult<storage::Mandate, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let mut mandates = self.mandates.lock().await;
+        match mandates
+            .iter_mut()
+            .find(|mandate| mandate.merchant_id == merchant_id && mandate.mandate_id == mandate_id)
+        {
+            Some(mandate) => {
+                match mandate_update {
+                    storage::MandateUpdate::StatusUpdate { mandate_status } => {
+                        mandate.mandate_status = mandate_status;
+                    }
+                    storage::MandateUpdate::CaptureAmountUpdate { amount_captured } => {
+                        mandate.amount_captured = amount_captured;
+                    }
+                    storage::MandateUpdate::ConnectorReferenceUpdate {
+                        connector_mandate_ids,
+                    } => {
+                        mandate.connector_mandate_ids = connector_mandate_ids;
+                    }
+                }
+                Ok(mandate.clone())
+            }
+            None => {
+                Err(errors::StorageError::ValueNotFound("mandate not found".to_string()).into())
+            }
+        }
     }
 
     async fn find_mandates_by_merchant_id(
         &self,
-        _merchant_id: &str,
-        _mandate_constraints: api_models::mandates::MandateListConstraints,
+        merchant_id: &str,
+        mandate_constraints: api_models::mandates::MandateListConstraints,
     ) -> CustomResult<Vec<storage::Mandate>, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let mandates = self.mandates.lock().await;
+        let mandates_iter = mandates.iter().filter(|mandate| {
+            let mut checker = mandate.merchant_id == merchant_id;
+            if let Some(created_time) = mandate_constraints.created_time {
+                checker &= mandate.created_at == created_time;
+            }
+            if let Some(created_time_lt) = mandate_constraints.created_time_lt {
+                checker &= mandate.created_at < created_time_lt;
+            }
+            if let Some(created_time_gt) = mandate_constraints.created_time_gt {
+                checker &= mandate.created_at > created_time_gt;
+            }
+            if let Some(created_time_lte) = mandate_constraints.created_time_lte {
+                checker &= mandate.created_at <= created_time_lte;
+            }
+            if let Some(created_time_gte) = mandate_constraints.created_time_gte {
+                checker &= mandate.created_at >= created_time_gte;
+            }
+            if let Some(connector) = &mandate_constraints.connector {
+                checker &= mandate.connector == *connector;
+            }
+            if let Some(mandate_status) = mandate_constraints.mandate_status {
+                let storage_mandate_status: storage_models::enums::MandateStatus =
+                    mandate_status.foreign_into();
+                checker &= mandate.mandate_status == storage_mandate_status;
+            }
+            checker
+        });
+
+        let mandates: Vec<storage::Mandate> = if let Some(limit) = mandate_constraints.limit {
+            #[allow(clippy::as_conversions)]
+            mandates_iter
+                .take((if limit < 0 { 0 } else { limit }) as usize)
+                .cloned()
+                .collect()
+        } else {
+            mandates_iter.cloned().collect()
+        };
+        Ok(mandates)
     }
 
     async fn insert_mandate(
         &self,
-        _mandate: storage::MandateNew,
+        mandate_new: storage::MandateNew,
     ) -> CustomResult<storage::Mandate, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let mut mandates = self.mandates.lock().await;
+        let mandate = storage::Mandate {
+            #[allow(clippy::as_conversions)]
+            id: mandates.len() as i32,
+            mandate_id: mandate_new.mandate_id.clone(),
+            customer_id: mandate_new.customer_id,
+            merchant_id: mandate_new.merchant_id,
+            payment_method_id: mandate_new.payment_method_id,
+            mandate_status: mandate_new.mandate_status,
+            mandate_type: mandate_new.mandate_type,
+            customer_accepted_at: mandate_new.customer_accepted_at,
+            customer_ip_address: mandate_new.customer_ip_address,
+            customer_user_agent: mandate_new.customer_user_agent,
+            network_transaction_id: mandate_new.network_transaction_id,
+            previous_attempt_id: mandate_new.previous_attempt_id,
+            created_at: mandate_new
+                .created_at
+                .unwrap_or_else(common_utils::date_time::now),
+            mandate_amount: mandate_new.mandate_amount,
+            mandate_currency: mandate_new.mandate_currency,
+            amount_captured: mandate_new.amount_captured,
+            connector: mandate_new.connector,
+            connector_mandate_id: mandate_new.connector_mandate_id,
+            start_date: mandate_new.start_date,
+            end_date: mandate_new.end_date,
+            metadata: mandate_new.metadata,
+            connector_mandate_ids: mandate_new.connector_mandate_ids,
+        };
+        mandates.push(mandate.clone());
+        Ok(mandate)
     }
 }

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [x] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
- fixes #1256 

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless its an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed submitted code
- [x] I added unit tests for my changes where possible
- [x] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
