Bug: [FEATURE] Implement `MerchantAccountInterface` for `MockDb` 



Spin off from #172. Refer to the parent issue for more information
diff --git a/crates/diesel_models/src/merchant_account.rs b/crates/diesel_models/src/merchant_account.rs
index b1dcc1d3476..12d51311e87 100644
--- a/crates/diesel_models/src/merchant_account.rs
+++ b/crates/diesel_models/src/merchant_account.rs
@@ -260,6 +260,36 @@ pub struct MerchantAccountUpdateInternal {
     pub recon_status: Option<storage_enums::ReconStatus>,
 }
 
+#[cfg(feature = "v2")]
+impl MerchantAccountUpdateInternal {
+    pub fn apply_changeset(self, source: MerchantAccount) -> MerchantAccount {
+        let Self {
+            merchant_name,
+            merchant_details,
+            publishable_key,
+            storage_scheme,
+            metadata,
+            modified_at,
+            organization_id,
+            recon_status,
+        } = self;
+
+        MerchantAccount {
+            merchant_name: merchant_name.or(source.merchant_name),
+            merchant_details: merchant_details.or(source.merchant_details),
+            publishable_key: publishable_key.or(source.publishable_key),
+            storage_scheme: storage_scheme.unwrap_or(source.storage_scheme),
+            metadata: metadata.or(source.metadata),
+            created_at: source.created_at,
+            modified_at,
+            organization_id: organization_id.unwrap_or(source.organization_id),
+            recon_status: recon_status.unwrap_or(source.recon_status),
+            version: source.version,
+            id: source.id,
+        }
+    }
+}
+
 #[cfg(feature = "v1")]
 #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
 #[diesel(table_name = merchant_account)]
@@ -290,3 +320,71 @@ pub struct MerchantAccountUpdateInternal {
     pub payment_link_config: Option<serde_json::Value>,
     pub pm_collect_link_config: Option<serde_json::Value>,
 }
+
+#[cfg(feature = "v1")]
+impl MerchantAccountUpdateInternal {
+    pub fn apply_changeset(self, source: MerchantAccount) -> MerchantAccount {
+        let Self {
+            merchant_name,
+            merchant_details,
+            return_url,
+            webhook_details,
+            sub_merchants_enabled,
+            parent_merchant_id,
+            enable_payment_response_hash,
+            payment_response_hash_key,
+            redirect_to_merchant_with_http_post,
+            publishable_key,
+            storage_scheme,
+            locker_id,
+            metadata,
+            routing_algorithm,
+            primary_business_details,
+            modified_at,
+            intent_fulfillment_time,
+            frm_routing_algorithm,
+            payout_routing_algorithm,
+            organization_id,
+            is_recon_enabled,
+            default_profile,
+            recon_status,
+            payment_link_config,
+            pm_collect_link_config,
+        } = self;
+
+        MerchantAccount {
+            merchant_id: source.merchant_id,
+            return_url: return_url.or(source.return_url),
+            enable_payment_response_hash: enable_payment_response_hash
+                .unwrap_or(source.enable_payment_response_hash),
+            payment_response_hash_key: payment_response_hash_key
+                .or(source.payment_response_hash_key),
+            redirect_to_merchant_with_http_post: redirect_to_merchant_with_http_post
+                .unwrap_or(source.redirect_to_merchant_with_http_post),
+            merchant_name: merchant_name.or(source.merchant_name),
+            merchant_details: merchant_details.or(source.merchant_details),
+            webhook_details: webhook_details.or(source.webhook_details),
+            sub_merchants_enabled: sub_merchants_enabled.or(source.sub_merchants_enabled),
+            parent_merchant_id: parent_merchant_id.or(source.parent_merchant_id),
+            publishable_key: publishable_key.or(source.publishable_key),
+            storage_scheme: storage_scheme.unwrap_or(source.storage_scheme),
+            locker_id: locker_id.or(source.locker_id),
+            metadata: metadata.or(source.metadata),
+            routing_algorithm: routing_algorithm.or(source.routing_algorithm),
+            primary_business_details: primary_business_details
+                .unwrap_or(source.primary_business_details),
+            intent_fulfillment_time: intent_fulfillment_time.or(source.intent_fulfillment_time),
+            created_at: source.created_at,
+            modified_at,
+            frm_routing_algorithm: frm_routing_algorithm.or(source.frm_routing_algorithm),
+            payout_routing_algorithm: payout_routing_algorithm.or(source.payout_routing_algorithm),
+            organization_id: organization_id.unwrap_or(source.organization_id),
+            is_recon_enabled: is_recon_enabled.unwrap_or(source.is_recon_enabled),
+            default_profile: default_profile.unwrap_or(source.default_profile),
+            recon_status: recon_status.unwrap_or(source.recon_status),
+            payment_link_config: payment_link_config.or(source.payment_link_config),
+            pm_collect_link_config: pm_collect_link_config.or(source.pm_collect_link_config),
+            version: source.version,
+        }
+    }
+}
diff --git a/crates/hyperswitch_domain_models/src/merchant_account.rs b/crates/hyperswitch_domain_models/src/merchant_account.rs
index 3e860d36284..1e3302dab4a 100644
--- a/crates/hyperswitch_domain_models/src/merchant_account.rs
+++ b/crates/hyperswitch_domain_models/src/merchant_account.rs
@@ -201,7 +201,7 @@ impl MerchantAccount {
 
 #[cfg(feature = "v1")]
 #[allow(clippy::large_enum_variant)]
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum MerchantAccountUpdate {
     Update {
         merchant_name: OptionalEncryptableName,
@@ -237,7 +237,7 @@ pub enum MerchantAccountUpdate {
 
 #[cfg(feature = "v2")]
 #[allow(clippy::large_enum_variant)]
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum MerchantAccountUpdate {
     Update {
         merchant_name: OptionalEncryptableName,
diff --git a/crates/router/src/db/merchant_account.rs b/crates/router/src/db/merchant_account.rs
index 13a778001ae..39844503892 100644
--- a/crates/router/src/db/merchant_account.rs
+++ b/crates/router/src/db/merchant_account.rs
@@ -482,91 +482,225 @@ impl MerchantAccountInterface for MockDb {
         merchant_key_store: &domain::MerchantKeyStore,
     ) -> CustomResult<domain::MerchantAccount, errors::StorageError> {
         let accounts = self.merchant_accounts.lock().await;
-        let account: Option<domain::MerchantAccount> = accounts
+        accounts
             .iter()
             .find(|account| account.get_id() == merchant_id)
             .cloned()
-            .async_map(|a| async {
-                a.convert(
-                    state,
-                    merchant_key_store.key.get_inner(),
-                    merchant_key_store.merchant_id.clone().into(),
-                )
-                .await
-                .change_context(errors::StorageError::DecryptionError)
-            })
+            .ok_or(errors::StorageError::ValueNotFound(format!(
+                "Merchant ID: {:?} not found",
+                merchant_id
+            )))?
+            .convert(
+                state,
+                merchant_key_store.key.get_inner(),
+                merchant_key_store.merchant_id.clone().into(),
+            )
             .await
-            .transpose()?;
-
-        match account {
-            Some(account) => Ok(account),
-            // [#172]: Implement function for `MockDb`
-            None => Err(errors::StorageError::MockDbError)?,
-        }
+            .change_context(errors::StorageError::DecryptionError)
     }
 
     async fn update_merchant(
         &self,
-        _state: &KeyManagerState,
-        _this: domain::MerchantAccount,
-        _merchant_account: storage::MerchantAccountUpdate,
-        _merchant_key_store: &domain::MerchantKeyStore,
+        state: &KeyManagerState,
+        merchant_account: domain::MerchantAccount,
+        merchant_account_update: storage::MerchantAccountUpdate,
+        merchant_key_store: &domain::MerchantKeyStore,
     ) -> CustomResult<domain::MerchantAccount, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let merchant_id = merchant_account.get_id().to_owned();
+        let mut accounts = self.merchant_accounts.lock().await;
+        accounts
+            .iter_mut()
+            .find(|account| account.get_id() == merchant_account.get_id())
+            .async_map(|account| async {
+                let update = MerchantAccountUpdateInternal::from(merchant_account_update)
+                    .apply_changeset(
+                        Conversion::convert(merchant_account)
+                            .await
+                            .change_context(errors::StorageError::EncryptionError)?,
+                    );
+                *account = update.clone();
+                update
+                    .convert(
+                        state,
+                        merchant_key_store.key.get_inner(),
+                        merchant_key_store.merchant_id.clone().into(),
+                    )
+                    .await
+                    .change_context(errors::StorageError::DecryptionError)
+            })
+            .await
+            .transpose()?
+            .ok_or(
+                errors::StorageError::ValueNotFound(format!(
+                    "Merchant ID: {:?} not found",
+                    merchant_id
+                ))
+                .into(),
+            )
     }
 
     async fn update_specific_fields_in_merchant(
         &self,
-        _state: &KeyManagerState,
-        _merchant_id: &common_utils::id_type::MerchantId,
-        _merchant_account: storage::MerchantAccountUpdate,
-        _merchant_key_store: &domain::MerchantKeyStore,
+        state: &KeyManagerState,
+        merchant_id: &common_utils::id_type::MerchantId,
+        merchant_account_update: storage::MerchantAccountUpdate,
+        merchant_key_store: &domain::MerchantKeyStore,
     ) -> CustomResult<domain::MerchantAccount, errors::StorageError> {
-        // [#TODO]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let mut accounts = self.merchant_accounts.lock().await;
+        accounts
+            .iter_mut()
+            .find(|account| account.get_id() == merchant_id)
+            .async_map(|account| async {
+                let update = MerchantAccountUpdateInternal::from(merchant_account_update)
+                    .apply_changeset(account.clone());
+                *account = update.clone();
+                update
+                    .convert(
+                        state,
+                        merchant_key_store.key.get_inner(),
+                        merchant_key_store.merchant_id.clone().into(),
+                    )
+                    .await
+                    .change_context(errors::StorageError::DecryptionError)
+            })
+            .await
+            .transpose()?
+            .ok_or(
+                errors::StorageError::ValueNotFound(format!(
+                    "Merchant ID: {:?} not found",
+                    merchant_id
+                ))
+                .into(),
+            )
     }
 
     async fn find_merchant_account_by_publishable_key(
         &self,
-        _state: &KeyManagerState,
-        _publishable_key: &str,
+        state: &KeyManagerState,
+        publishable_key: &str,
     ) -> CustomResult<authentication::AuthenticationData, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let accounts = self.merchant_accounts.lock().await;
+        let account = accounts
+            .iter()
+            .find(|account| {
+                account
+                    .publishable_key
+                    .as_ref()
+                    .is_some_and(|key| key == publishable_key)
+            })
+            .ok_or(errors::StorageError::ValueNotFound(format!(
+                "Publishable Key: {} not found",
+                publishable_key
+            )))?;
+        let key_store = self
+            .get_merchant_key_store_by_merchant_id(
+                state,
+                account.get_id(),
+                &self.get_master_key().to_vec().into(),
+            )
+            .await?;
+        Ok(authentication::AuthenticationData {
+            merchant_account: account
+                .clone()
+                .convert(
+                    state,
+                    key_store.key.get_inner(),
+                    key_store.merchant_id.clone().into(),
+                )
+                .await
+                .change_context(errors::StorageError::DecryptionError)?,
+
+            key_store,
+            profile_id: None,
+        })
     }
 
     async fn update_all_merchant_account(
         &self,
-        _merchant_account_update: storage::MerchantAccountUpdate,
+        merchant_account_update: storage::MerchantAccountUpdate,
     ) -> CustomResult<usize, errors::StorageError> {
-        Err(errors::StorageError::MockDbError)?
+        let mut accounts = self.merchant_accounts.lock().await;
+        Ok(accounts.iter_mut().fold(0, |acc, account| {
+            let update = MerchantAccountUpdateInternal::from(merchant_account_update.clone())
+                .apply_changeset(account.clone());
+            *account = update;
+            acc + 1
+        }))
     }
 
     async fn delete_merchant_account_by_merchant_id(
         &self,
-        _merchant_id: &common_utils::id_type::MerchantId,
+        merchant_id: &common_utils::id_type::MerchantId,
     ) -> CustomResult<bool, errors::StorageError> {
-        // [#172]: Implement function for `MockDb`
-        Err(errors::StorageError::MockDbError)?
+        let mut accounts = self.merchant_accounts.lock().await;
+        accounts.retain(|x| x.get_id() != merchant_id);
+        Ok(true)
     }
 
     #[cfg(feature = "olap")]
     async fn list_merchant_accounts_by_organization_id(
         &self,
-        _state: &KeyManagerState,
-        _organization_id: &common_utils::id_type::OrganizationId,
+        state: &KeyManagerState,
+        organization_id: &common_utils::id_type::OrganizationId,
     ) -> CustomResult<Vec<domain::MerchantAccount>, errors::StorageError> {
-        Err(errors::StorageError::MockDbError)?
+        let accounts = self.merchant_accounts.lock().await;
+        let futures = accounts
+            .iter()
+            .filter(|account| account.organization_id == *organization_id)
+            .map(|account| async {
+                let key_store = self
+                    .get_merchant_key_store_by_merchant_id(
+                        state,
+                        account.get_id(),
+                        &self.get_master_key().to_vec().into(),
+                    )
+                    .await;
+                match key_store {
+                    Ok(key) => account
+                        .clone()
+                        .convert(state, key.key.get_inner(), key.merchant_id.clone().into())
+                        .await
+                        .change_context(errors::StorageError::DecryptionError),
+                    Err(err) => Err(err),
+                }
+            });
+        futures::future::join_all(futures)
+            .await
+            .into_iter()
+            .collect()
     }
 
     #[cfg(feature = "olap")]
     async fn list_multiple_merchant_accounts(
         &self,
-        _state: &KeyManagerState,
-        _merchant_ids: Vec<common_utils::id_type::MerchantId>,
+        state: &KeyManagerState,
+        merchant_ids: Vec<common_utils::id_type::MerchantId>,
     ) -> CustomResult<Vec<domain::MerchantAccount>, errors::StorageError> {
-        Err(errors::StorageError::MockDbError)?
+        let accounts = self.merchant_accounts.lock().await;
+        let futures = accounts
+            .iter()
+            .filter(|account| merchant_ids.contains(account.get_id()))
+            .map(|account| async {
+                let key_store = self
+                    .get_merchant_key_store_by_merchant_id(
+                        state,
+                        account.get_id(),
+                        &self.get_master_key().to_vec().into(),
+                    )
+                    .await;
+                match key_store {
+                    Ok(key) => account
+                        .clone()
+                        .convert(state, key.key.get_inner(), key.merchant_id.clone().into())
+                        .await
+                        .change_context(errors::StorageError::DecryptionError),
+                    Err(err) => Err(err),
+                }
+            });
+        futures::future::join_all(futures)
+            .await
+            .into_iter()
+            .collect()
     }
 }
 
diff --git a/docker-compose-development.yml b/docker-compose-development.yml
index 3878f880b06..07a2131c6d4 100644
--- a/docker-compose-development.yml
+++ b/docker-compose-development.yml
@@ -64,6 +64,7 @@ services:
         FROM rust:latest
         RUN apt-get update && \
             apt-get install -y protobuf-compiler
+        RUN rustup component add rustfmt clippy  
     command: cargo run --bin router -- -f ./config/docker_compose.toml
     working_dir: /app
     ports:

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

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

## Description
<!-- Describe your changes in detail -->
Mockdb is a stub to avoid costly interactions with db during testing. This PR powers mockdb to have [MerchantAccountInterface](https://github.com/juspay/hyperswitch/blob/3d70b1a31703c5316183adaa9312a38e598fce01/crates/router/src/db/merchant_account.rs#L26) for adding, deleting, searching merchant accounts.

### 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 it is an obvious bug or documentation fix
that will have little conversation).
-->
Fixes https://github.com/juspay/hyperswitch/issues/2438

## 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] addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
