Test-Implementation Pair

Implementation: crates/router/src/db/merchant_connector_account.rs
================================================================================
use common_utils::ext_traits::{ByteSliceExt, Encode};
use error_stack::ResultExt;
pub use hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccountInterface;
use router_env::{instrument, tracing};
use storage_impl::redis::kv_store::RedisConnInterface;

use super::{MockDb, Store};
use crate::{
    core::errors::{self, CustomResult},
    types,
};

#[async_trait::async_trait]
pub trait ConnectorAccessToken {
    async fn get_access_token(
        &self,
        &key: String,
    ) -> CustomResult<Option<types::AccessToken>, errors::StorageError>;

    async fn set_access_token(
        &self,
        key: String,
        access_token: types::AccessToken,
    ) -> CustomResult<(), errors::StorageError>;
}

#[async_trait::async_trait]
impl ConnectorAccessToken for Store {
    #[instrument(skip_all)]
    async fn get_access_token(
        &self,
        key: String,
    ) -> CustomResult<Option<types::AccessToken>, errors::StorageError> {
        //TODO: Handle race condition
        // This function should acquire a global lock on some resource, if access token is already
        // being refreshed by other request then wait till it finishes and use the same access token

        let maybe_token = self
            .get_redis_conn()
            .map_err(Into::<errors::StorageError>::into)?
            .get_key::<Option<Vec<u8>>>(&key.into())
            .await
            .change_context(errors::StorageError::KVError)
            .attach_printable("DB error when getting access token")?;

        let access_token = maybe_token
            .map(|token| token.parse_struct::<types::AccessToken>("AccessToken"))
            .transpose()
            .change_context(errors::StorageError::DeserializationFailed)?;

        Ok(access_token)
    }

    #[instrument(skip_all)]
    async fn set_access_token(
        &self,
        key: String,
        access_token: types::AccessToken,
    ) -> CustomResult<(), errors::StorageError> {
        let serialized_access_token = access_token
            .encode_to_string_of_json()
            .change_context(errors::StorageError::SerializationFailed)?;
        self.get_redis_conn()
            .map_err(Into::<errors::StorageError>::into)?
            .set_key_with_expiry(&key.into(), serialized_access_token, access_token.expires)
            .await
            .change_context(errors::StorageError::KVError)
    }
}

#[async_trait::async_trait]
impl ConnectorAccessToken for MockDb {
    async fn get_access_token(
        &self,
        _key: String,
    ) -> CustomResult<Option<types::AccessToken>, errors::StorageError> {
        Ok(None)
    }

    async fn set_access_token(
        &self,
        _key: String,
        _access_token: types::AccessToken,
    ) -> CustomResult<(), errors::StorageError> {
        Ok(())
    }
}

#[cfg(feature = "accounts_cache")]
#[cfg(test)]
mod merchant_connector_account_cache_tests {
    use std::sync::Arc;

    #[cfg(feature = "v1")]
    use api_models::enums::CountryAlpha2;
    use common_utils::{
        date_time, type_name,
        types::keymanager::{Identifier, KeyManagerState},
    };
    use diesel_models::enums::ConnectorType;
    use error_stack::ResultExt;
    use hyperswitch_domain_models::master_key::MasterKeyInterface;
    use masking::PeekInterface;
    use storage_impl::redis::{
        cache::{self, CacheKey, CacheKind, ACCOUNTS_CACHE},
        kv_store::RedisConnInterface,
        pub_sub::PubSubInterface,
    };
    use time::macros::datetime;
    use tokio::sync::oneshot;

    use crate::{
        core::errors,
        db::{
            merchant_connector_account::MerchantConnectorAccountInterface,
            merchant_key_store::MerchantKeyStoreInterface, MockDb,
        },
        routes::{
            self,
            app::{settings::Settings, StorageImpl},
        },
        services,
        types::{
            domain::{self, behaviour::Conversion},
            storage,
        },
    };

    #[tokio::test]
    #[cfg(feature = "v1")]
    async fn test_connector_profile_id_cache() {
        let conf = Settings::new().unwrap();
        let tx: oneshot::Sender<()> = oneshot::channel().0;

        let app_state = Box::pin(routes::AppState::with_storage(
            conf,
            StorageImpl::PostgresqlTest,
            tx,
            Box::new(services::MockApiClient),
        ))
        .await;

        let state = &Arc::new(app_state)
            .get_session_state(
                &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(),
                None,
                || {},
            )
            .unwrap();
        let db = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let redis_conn = db.get_redis_conn().unwrap();
        let master_key = db.get_master_key();
        redis_conn
            .subscribe("hyperswitch_invalidate")
            .await
            .unwrap();

        let merchant_id =
            common_utils::id_type::MerchantId::try_from(std::borrow::Cow::from("test_merchant"))
                .unwrap();

        let connector_label = "stripe_USA";
        let merchant_connector_id = "simple_merchant_connector_id";
        let profile_id =
            common_utils::id_type::ProfileId::try_from(std::borrow::Cow::from("pro_max_ultra"))
                .unwrap();
        let key_manager_state = &state.into();
        db.insert_merchant_key_store(
            domain::MerchantKeyStore {
                merchant_id: merchant_id.clone(),
                key: domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantKeyStore),
                    domain::types::CryptoOperation::Encrypt(
                        services::generate_aes256_key().unwrap().to_vec().into(),
                    ),
                    Identifier::Merchant(merchant_id.clone()),
                    master_key,
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
                created_at: datetime!(2023-02-01 0:00),
            },
            &master_key.to_vec().into(),
        )
        .await
        .unwrap();

        let merchant_key = db
            .get_merchant_key_store_by_merchant_id(&merchant_id, &master_key.to_vec().into())
            .await
            .unwrap();

        let mca = domain::MerchantConnectorAccount {
            merchant_id: merchant_id.to_owned(),
            connector_name: "stripe".to_string(),
            connector_account_details: domain::types::crypto_operation(
                key_manager_state,
                type_name!(domain::MerchantConnectorAccount),
                domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                Identifier::Merchant(merchant_key.merchant_id.clone()),
                merchant_key.key.get_inner().peek(),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .unwrap(),
            test_mode: None,
            disabled: None,
            merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId::wrap(
                merchant_connector_id.to_string(),
            )
            .unwrap(),
            payment_methods_enabled: None,
            connector_type: ConnectorType::FinOperations,
            metadata: None,
            frm_configs: None,
            connector_label: Some(connector_label.to_string()),
            business_country: Some(CountryAlpha2::US),
            business_label: Some("cloth".to_string()),
            business_sub_label: None,
            created_at: date_time::now(),
            modified_at: date_time::now(),
            connector_webhook_details: None,
            profile_id: profile_id.to_owned(),
            applepay_verified_domains: None,
            pm_auth_config: None,
            status: common_enums::ConnectorStatus::Inactive,
            connector_wallets_details: Some(
                domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantConnectorAccount),
                    domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                    Identifier::Merchant(merchant_key.merchant_id.clone()),
                    merchant_key.key.get_inner().peek(),
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
            ),
            additional_merchant_data: None,
            version: common_types::consts::API_VERSION,
        };

        db.insert_merchant_connector_account(mca.clone(), &merchant_key)
            .await
            .unwrap();

        let find_call = || async {
            Conversion::convert(
                db.find_merchant_connector_account_by_profile_id_connector_name(
                    &profile_id,
                    &mca.connector_name,
                    &merchant_key,
                )
                .await
                .unwrap(),
            )
            .await
            .change_context(errors::StorageError::DecryptionError)
        };
        let _: storage::MerchantConnectorAccount = cache::get_or_populate_in_memory(
            &db,
            &format!(
                "{}_{}",
                merchant_id.get_string_repr(),
                profile_id.get_string_repr(),
            ),
            find_call,
            &ACCOUNTS_CACHE,
        )
        .await
        .unwrap();

        let delete_call = || async {
            db.delete_merchant_connector_account_by_merchant_id_merchant_connector_id(
                &merchant_id,
                &common_utils::id_type::MerchantConnectorAccountId::wrap(
                    merchant_connector_id.to_string(),
                )
                .unwrap(),
            )
            .await
        };

        cache::publish_and_redact(
            &db,
            CacheKind::Accounts(
                format!("{}_{}", merchant_id.get_string_repr(), connector_label).into(),
            ),
            delete_call,
        )
        .await
        .unwrap();

        assert!(ACCOUNTS_CACHE
            .get_val::<domain::MerchantConnectorAccount>(CacheKey {
                key: format!("{}_{}", merchant_id.get_string_repr(), connector_label),
                prefix: String::default(),
            },)
            .await
            .is_none())
    }

    #[tokio::test]
    #[cfg(feature = "v2")]
    async fn test_connector_profile_id_cache() {
        let conf = Settings::new().unwrap();
        let tx: oneshot::Sender<()> = oneshot::channel().0;

        let app_state = Box::pin(routes::AppState::with_storage(
            conf,
            StorageImpl::PostgresqlTest,
            tx,
            Box::new(services::MockApiClient),
        ))
        .await;
        let state = &Arc::new(app_state)
            .get_session_state(
                &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(),
                None,
                || {},
            )
            .unwrap();
        let db = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let redis_conn = db.get_redis_conn().unwrap();
        let master_key = db.get_master_key();
        redis_conn
            .subscribe("hyperswitch_invalidate")
            .await
            .unwrap();

        let merchant_id =
            common_utils::id_type::MerchantId::try_from(std::borrow::Cow::from("test_merchant"))
                .unwrap();
        let connector_label = "stripe_USA";
        let id = common_utils::generate_merchant_connector_account_id_of_default_length();
        let profile_id =
            common_utils::id_type::ProfileId::try_from(std::borrow::Cow::from("pro_max_ultra"))
                .unwrap();
        let key_manager_state = &state.into();
        db.insert_merchant_key_store(
            domain::MerchantKeyStore {
                merchant_id: merchant_id.clone(),
                key: domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantConnectorAccount),
                    domain::types::CryptoOperation::Encrypt(
                        services::generate_aes256_key().unwrap().to_vec().into(),
                    ),
                    Identifier::Merchant(merchant_id.clone()),
                    master_key,
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
                created_at: datetime!(2023-02-01 0:00),
            },
            &master_key.to_vec().into(),
        )
        .await
        .unwrap();

        let merchant_key = db
            .get_merchant_key_store_by_merchant_id(&merchant_id, &master_key.to_vec().into())
            .await
            .unwrap();

        let mca = domain::MerchantConnectorAccount {
            id: id.clone(),
            merchant_id: merchant_id.clone(),
            connector_name: common_enums::connector_enums::Connector::Stripe,
            connector_account_details: domain::types::crypto_operation(
                key_manager_state,
                type_name!(domain::MerchantConnectorAccount),
                domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                Identifier::Merchant(merchant_key.merchant_id.clone()),
                merchant_key.key.get_inner().peek(),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .unwrap(),
            disabled: None,
            payment_methods_enabled: None,
            connector_type: ConnectorType::FinOperations,
            metadata: None,
            frm_configs: None,
            connector_label: Some(connector_label.to_string()),
            created_at: date_time::now(),
            modified_at: date_time::now(),
            connector_webhook_details: None,
            profile_id: profile_id.to_owned(),
            applepay_verified_domains: None,
            pm_auth_config: None,
            status: common_enums::ConnectorStatus::Inactive,
            connector_wallets_details: Some(
                domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantConnectorAccount),
                    domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                    Identifier::Merchant(merchant_key.merchant_id.clone()),
                    merchant_key.key.get_inner().peek(),
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
            ),
            additional_merchant_data: None,
            version: common_types::consts::API_VERSION,
            feature_metadata: None,
        };

        db.insert_merchant_connector_account(mca.clone(), &merchant_key)
            .await
            .unwrap();

        let find_call = || async {
            #[cfg(feature = "v1")]
            let mca = db
                .find_merchant_connector_account_by_profile_id_connector_name(
                    profile_id,
                    &mca.connector_name,
                    &merchant_key,
                )
                .await
                .unwrap();
            #[cfg(feature = "v2")]
            let mca: domain::MerchantConnectorAccount = { todo!() };
            Conversion::convert(mca)
                .await
                .change_context(errors::StorageError::DecryptionError)
        };

        let _: storage::MerchantConnectorAccount = cache::get_or_populate_in_memory(
            &db,
            &format!(
                "{}_{}",
                merchant_id.clone().get_string_repr(),
                profile_id.get_string_repr()
            ),
            find_call,
            &ACCOUNTS_CACHE,
        )
        .await
        .unwrap();

        let delete_call = || async { db.delete_merchant_connector_account_by_id(&id).await };

        cache::publish_and_redact(
            &db,
            CacheKind::Accounts(
                format!("{}_{}", merchant_id.get_string_repr(), connector_label).into(),
            ),
            delete_call,
        )
        .await
        .unwrap();

        assert!(ACCOUNTS_CACHE
            .get_val::<domain::MerchantConnectorAccount>(CacheKey {
                key: format!("{}_{}", merchant_id.get_string_repr(), connector_label),
                prefix: String::default(),
            },)
            .await
            .is_none())
    }
}

Tests: crates/router/src/db/merchant_connector_account.rs
================================================================================
use common_utils::ext_traits::{ByteSliceExt, Encode};
use error_stack::ResultExt;
pub use hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccountInterface;
use router_env::{instrument, tracing};
use storage_impl::redis::kv_store::RedisConnInterface;

use super::{MockDb, Store};
use crate::{
    core::errors::{self, CustomResult},
    types,
};

#[async_trait::async_trait]
pub trait ConnectorAccessToken {
    async fn get_access_token(
        &self,
        &key: String,
    ) -> CustomResult<Option<types::AccessToken>, errors::StorageError>;

    async fn set_access_token(
        &self,
        key: String,
        access_token: types::AccessToken,
    ) -> CustomResult<(), errors::StorageError>;
}

#[async_trait::async_trait]
impl ConnectorAccessToken for Store {
    #[instrument(skip_all)]
    async fn get_access_token(
        &self,
        key: String,
    ) -> CustomResult<Option<types::AccessToken>, errors::StorageError> {
        //TODO: Handle race condition
        // This function should acquire a global lock on some resource, if access token is already
        // being refreshed by other request then wait till it finishes and use the same access token

        let maybe_token = self
            .get_redis_conn()
            .map_err(Into::<errors::StorageError>::into)?
            .get_key::<Option<Vec<u8>>>(&key.into())
            .await
            .change_context(errors::StorageError::KVError)
            .attach_printable("DB error when getting access token")?;

        let access_token = maybe_token
            .map(|token| token.parse_struct::<types::AccessToken>("AccessToken"))
            .transpose()
            .change_context(errors::StorageError::DeserializationFailed)?;

        Ok(access_token)
    }

    #[instrument(skip_all)]
    async fn set_access_token(
        &self,
        key: String,
        access_token: types::AccessToken,
    ) -> CustomResult<(), errors::StorageError> {
        let serialized_access_token = access_token
            .encode_to_string_of_json()
            .change_context(errors::StorageError::SerializationFailed)?;
        self.get_redis_conn()
            .map_err(Into::<errors::StorageError>::into)?
            .set_key_with_expiry(&key.into(), serialized_access_token, access_token.expires)
            .await
            .change_context(errors::StorageError::KVError)
    }
}

#[async_trait::async_trait]
impl ConnectorAccessToken for MockDb {
    async fn get_access_token(
        &self,
        _key: String,
    ) -> CustomResult<Option<types::AccessToken>, errors::StorageError> {
        Ok(None)
    }

    async fn set_access_token(
        &self,
        _key: String,
        _access_token: types::AccessToken,
    ) -> CustomResult<(), errors::StorageError> {
        Ok(())
    }
}

#[cfg(feature = "accounts_cache")]
#[cfg(test)]
mod merchant_connector_account_cache_tests {
    use std::sync::Arc;

    #[cfg(feature = "v1")]
    use api_models::enums::CountryAlpha2;
    use common_utils::{
        date_time, type_name,
        types::keymanager::{Identifier, KeyManagerState},
    };
    use diesel_models::enums::ConnectorType;
    use error_stack::ResultExt;
    use hyperswitch_domain_models::master_key::MasterKeyInterface;
    use masking::PeekInterface;
    use storage_impl::redis::{
        cache::{self, CacheKey, CacheKind, ACCOUNTS_CACHE},
        kv_store::RedisConnInterface,
        pub_sub::PubSubInterface,
    };
    use time::macros::datetime;
    use tokio::sync::oneshot;

    use crate::{
        core::errors,
        db::{
            merchant_connector_account::MerchantConnectorAccountInterface,
            merchant_key_store::MerchantKeyStoreInterface, MockDb,
        },
        routes::{
            self,
            app::{settings::Settings, StorageImpl},
        },
        services,
        types::{
            domain::{self, behaviour::Conversion},
            storage,
        },
    };

    #[tokio::test]
    #[cfg(feature = "v1")]
    async fn test_connector_profile_id_cache() {
        let conf = Settings::new().unwrap();
        let tx: oneshot::Sender<()> = oneshot::channel().0;

        let app_state = Box::pin(routes::AppState::with_storage(
            conf,
            StorageImpl::PostgresqlTest,
            tx,
            Box::new(services::MockApiClient),
        ))
        .await;

        let state = &Arc::new(app_state)
            .get_session_state(
                &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(),
                None,
                || {},
            )
            .unwrap();
        let db = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let redis_conn = db.get_redis_conn().unwrap();
        let master_key = db.get_master_key();
        redis_conn
            .subscribe("hyperswitch_invalidate")
            .await
            .unwrap();

        let merchant_id =
            common_utils::id_type::MerchantId::try_from(std::borrow::Cow::from("test_merchant"))
                .unwrap();

        let connector_label = "stripe_USA";
        let merchant_connector_id = "simple_merchant_connector_id";
        let profile_id =
            common_utils::id_type::ProfileId::try_from(std::borrow::Cow::from("pro_max_ultra"))
                .unwrap();
        let key_manager_state = &state.into();
        db.insert_merchant_key_store(
            domain::MerchantKeyStore {
                merchant_id: merchant_id.clone(),
                key: domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantKeyStore),
                    domain::types::CryptoOperation::Encrypt(
                        services::generate_aes256_key().unwrap().to_vec().into(),
                    ),
                    Identifier::Merchant(merchant_id.clone()),
                    master_key,
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
                created_at: datetime!(2023-02-01 0:00),
            },
            &master_key.to_vec().into(),
        )
        .await
        .unwrap();

        let merchant_key = db
            .get_merchant_key_store_by_merchant_id(&merchant_id, &master_key.to_vec().into())
            .await
            .unwrap();

        let mca = domain::MerchantConnectorAccount {
            merchant_id: merchant_id.to_owned(),
            connector_name: "stripe".to_string(),
            connector_account_details: domain::types::crypto_operation(
                key_manager_state,
                type_name!(domain::MerchantConnectorAccount),
                domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                Identifier::Merchant(merchant_key.merchant_id.clone()),
                merchant_key.key.get_inner().peek(),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .unwrap(),
            test_mode: None,
            disabled: None,
            merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId::wrap(
                merchant_connector_id.to_string(),
            )
            .unwrap(),
            payment_methods_enabled: None,
            connector_type: ConnectorType::FinOperations,
            metadata: None,
            frm_configs: None,
            connector_label: Some(connector_label.to_string()),
            business_country: Some(CountryAlpha2::US),
            business_label: Some("cloth".to_string()),
            business_sub_label: None,
            created_at: date_time::now(),
            modified_at: date_time::now(),
            connector_webhook_details: None,
            profile_id: profile_id.to_owned(),
            applepay_verified_domains: None,
            pm_auth_config: None,
            status: common_enums::ConnectorStatus::Inactive,
            connector_wallets_details: Some(
                domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantConnectorAccount),
                    domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                    Identifier::Merchant(merchant_key.merchant_id.clone()),
                    merchant_key.key.get_inner().peek(),
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
            ),
            additional_merchant_data: None,
            version: common_types::consts::API_VERSION,
        };

        db.insert_merchant_connector_account(mca.clone(), &merchant_key)
            .await
            .unwrap();

        let find_call = || async {
            Conversion::convert(
                db.find_merchant_connector_account_by_profile_id_connector_name(
                    &profile_id,
                    &mca.connector_name,
                    &merchant_key,
                )
                .await
                .unwrap(),
            )
            .await
            .change_context(errors::StorageError::DecryptionError)
        };
        let _: storage::MerchantConnectorAccount = cache::get_or_populate_in_memory(
            &db,
            &format!(
                "{}_{}",
                merchant_id.get_string_repr(),
                profile_id.get_string_repr(),
            ),
            find_call,
            &ACCOUNTS_CACHE,
        )
        .await
        .unwrap();

        let delete_call = || async {
            db.delete_merchant_connector_account_by_merchant_id_merchant_connector_id(
                &merchant_id,
                &common_utils::id_type::MerchantConnectorAccountId::wrap(
                    merchant_connector_id.to_string(),
                )
                .unwrap(),
            )
            .await
        };

        cache::publish_and_redact(
            &db,
            CacheKind::Accounts(
                format!("{}_{}", merchant_id.get_string_repr(), connector_label).into(),
            ),
            delete_call,
        )
        .await
        .unwrap();

        assert!(ACCOUNTS_CACHE
            .get_val::<domain::MerchantConnectorAccount>(CacheKey {
                key: format!("{}_{}", merchant_id.get_string_repr(), connector_label),
                prefix: String::default(),
            },)
            .await
            .is_none())
    }

    #[tokio::test]
    #[cfg(feature = "v2")]
    async fn test_connector_profile_id_cache() {
        let conf = Settings::new().unwrap();
        let tx: oneshot::Sender<()> = oneshot::channel().0;

        let app_state = Box::pin(routes::AppState::with_storage(
            conf,
            StorageImpl::PostgresqlTest,
            tx,
            Box::new(services::MockApiClient),
        ))
        .await;
        let state = &Arc::new(app_state)
            .get_session_state(
                &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(),
                None,
                || {},
            )
            .unwrap();
        let db = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let redis_conn = db.get_redis_conn().unwrap();
        let master_key = db.get_master_key();
        redis_conn
            .subscribe("hyperswitch_invalidate")
            .await
            .unwrap();

        let merchant_id =
            common_utils::id_type::MerchantId::try_from(std::borrow::Cow::from("test_merchant"))
                .unwrap();
        let connector_label = "stripe_USA";
        let id = common_utils::generate_merchant_connector_account_id_of_default_length();
        let profile_id =
            common_utils::id_type::ProfileId::try_from(std::borrow::Cow::from("pro_max_ultra"))
                .unwrap();
        let key_manager_state = &state.into();
        db.insert_merchant_key_store(
            domain::MerchantKeyStore {
                merchant_id: merchant_id.clone(),
                key: domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantConnectorAccount),
                    domain::types::CryptoOperation::Encrypt(
                        services::generate_aes256_key().unwrap().to_vec().into(),
                    ),
                    Identifier::Merchant(merchant_id.clone()),
                    master_key,
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
                created_at: datetime!(2023-02-01 0:00),
            },
            &master_key.to_vec().into(),
        )
        .await
        .unwrap();

        let merchant_key = db
            .get_merchant_key_store_by_merchant_id(&merchant_id, &master_key.to_vec().into())
            .await
            .unwrap();

        let mca = domain::MerchantConnectorAccount {
            id: id.clone(),
            merchant_id: merchant_id.clone(),
            connector_name: common_enums::connector_enums::Connector::Stripe,
            connector_account_details: domain::types::crypto_operation(
                key_manager_state,
                type_name!(domain::MerchantConnectorAccount),
                domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                Identifier::Merchant(merchant_key.merchant_id.clone()),
                merchant_key.key.get_inner().peek(),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .unwrap(),
            disabled: None,
            payment_methods_enabled: None,
            connector_type: ConnectorType::FinOperations,
            metadata: None,
            frm_configs: None,
            connector_label: Some(connector_label.to_string()),
            created_at: date_time::now(),
            modified_at: date_time::now(),
            connector_webhook_details: None,
            profile_id: profile_id.to_owned(),
            applepay_verified_domains: None,
            pm_auth_config: None,
            status: common_enums::ConnectorStatus::Inactive,
            connector_wallets_details: Some(
                domain::types::crypto_operation(
                    key_manager_state,
                    type_name!(domain::MerchantConnectorAccount),
                    domain::types::CryptoOperation::Encrypt(serde_json::Value::default().into()),
                    Identifier::Merchant(merchant_key.merchant_id.clone()),
                    merchant_key.key.get_inner().peek(),
                )
                .await
                .and_then(|val| val.try_into_operation())
                .unwrap(),
            ),
            additional_merchant_data: None,
            version: common_types::consts::API_VERSION,
            feature_metadata: None,
        };

        db.insert_merchant_connector_account(mca.clone(), &merchant_key)
            .await
            .unwrap();

        let find_call = || async {
            #[cfg(feature = "v1")]
            let mca = db
                .find_merchant_connector_account_by_profile_id_connector_name(
                    profile_id,
                    &mca.connector_name,
                    &merchant_key,
                )
                .await
                .unwrap();
            #[cfg(feature = "v2")]
            let mca: domain::MerchantConnectorAccount = { todo!() };
            Conversion::convert(mca)
                .await
                .change_context(errors::StorageError::DecryptionError)
        };

        let _: storage::MerchantConnectorAccount = cache::get_or_populate_in_memory(
            &db,
            &format!(
                "{}_{}",
                merchant_id.clone().get_string_repr(),
                profile_id.get_string_repr()
            ),
            find_call,
            &ACCOUNTS_CACHE,
        )
        .await
        .unwrap();

        let delete_call = || async { db.delete_merchant_connector_account_by_id(&id).await };

        cache::publish_and_redact(
            &db,
            CacheKind::Accounts(
                format!("{}_{}", merchant_id.get_string_repr(), connector_label).into(),
            ),
            delete_call,
        )
        .await
        .unwrap();

        assert!(ACCOUNTS_CACHE
            .get_val::<domain::MerchantConnectorAccount>(CacheKey {
                key: format!("{}_{}", merchant_id.get_string_repr(), connector_label),
                prefix: String::default(),
            },)
            .await
            .is_none())
    }
}


Test-Implementation Pair

Implementation: crates/router/src/db/locker_mock_up.rs
================================================================================
use error_stack::report;
use router_env::{instrument, tracing};

use super::{MockDb, Store};
use crate::{
    connection,
    core::errors::{self, CustomResult},
    types::storage,
};

#[async_trait::async_trait]
pub trait LockerMockUpInterface {
    async fn find_locker_by_card_id(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError>;

    async fn insert_locker_mock_up(
        &self,
        new: storage::LockerMockUpNew,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError>;

    async fn delete_locker_mock_up(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError>;
}

#[async_trait::async_trait]
impl LockerMockUpInterface for Store {
    #[instrument(skip_all)]
    async fn find_locker_by_card_id(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::LockerMockUp::find_by_card_id(&conn, card_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn insert_locker_mock_up(
        &self,
        new: storage::LockerMockUpNew,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        new.insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn delete_locker_mock_up(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        storage::LockerMockUp::delete_by_card_id(&conn, card_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl LockerMockUpInterface for MockDb {
    async fn find_locker_by_card_id(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        self.lockers
            .lock()
            .await
            .iter()
            .find(|l| l.card_id == card_id)
            .cloned()
            .ok_or(errors::StorageError::MockDbError.into())
    }

    async fn insert_locker_mock_up(
        &self,
        new: storage::LockerMockUpNew,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let mut locked_lockers = self.lockers.lock().await;

        if locked_lockers.iter().any(|l| l.card_id == new.card_id) {
            Err(errors::StorageError::MockDbError)?;
        }

        let created_locker = storage::LockerMockUp {
            card_id: new.card_id,
            external_id: new.external_id,
            card_fingerprint: new.card_fingerprint,
            card_global_fingerprint: new.card_global_fingerprint,
            merchant_id: new.merchant_id,
            card_number: new.card_number,
            card_exp_year: new.card_exp_year,
            card_exp_month: new.card_exp_month,
            name_on_card: new.name_on_card,
            nickname: None,
            customer_id: new.customer_id,
            duplicate: None,
            card_cvc: new.card_cvc,
            payment_method_id: new.payment_method_id,
            enc_card_data: new.enc_card_data,
        };

        locked_lockers.push(created_locker.clone());

        Ok(created_locker)
    }

    async fn delete_locker_mock_up(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let mut locked_lockers = self.lockers.lock().await;

        let position = locked_lockers
            .iter()
            .position(|l| l.card_id == card_id)
            .ok_or(errors::StorageError::MockDbError)?;

        Ok(locked_lockers.remove(position))
    }
}

#[cfg(test)]
mod tests {
    mod mockdb_locker_mock_up_interface {
        use common_utils::{
            generate_customer_id_of_default_length, id_type, types::keymanager::KeyManagerState,
        };

        use crate::{
            db::{locker_mock_up::LockerMockUpInterface, MockDb},
            types::storage,
        };

        pub struct LockerMockUpIds {
            card_id: String,
            external_id: String,
            merchant_id: id_type::MerchantId,
            customer_id: id_type::CustomerId,
        }

        fn create_locker_mock_up_new(locker_ids: LockerMockUpIds) -> storage::LockerMockUpNew {
            storage::LockerMockUpNew {
                card_id: locker_ids.card_id,
                external_id: locker_ids.external_id,
                card_fingerprint: "card_fingerprint".into(),
                card_global_fingerprint: "card_global_fingerprint".into(),
                merchant_id: locker_ids.merchant_id,
                card_number: "1234123412341234".into(),
                card_exp_year: "2023".into(),
                card_exp_month: "06".into(),
                name_on_card: Some("name_on_card".into()),
                card_cvc: Some("123".into()),
                payment_method_id: Some("payment_method_id".into()),
                customer_id: Some(locker_ids.customer_id),
                nickname: Some("card_holder_nickname".into()),
                enc_card_data: Some("enc_card_data".into()),
            }
        }

        #[tokio::test]
        async fn find_locker_by_card_id() {
            let mockdb = MockDb::new(
                &redis_interface::RedisSettings::default(),
                KeyManagerState::new(),
            )
            .await
            .expect("Failed to create Mock store");

            let created_locker = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_1".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await
                .unwrap();

            let _ = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_2".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await;

            let found_locker = mockdb.find_locker_by_card_id("card_1").await.unwrap();

            assert_eq!(created_locker, found_locker)
        }

        #[tokio::test]
        async fn insert_locker_mock_up() {
            let mockdb = MockDb::new(
                &redis_interface::RedisSettings::default(),
                KeyManagerState::new(),
            )
            .await
            .expect("Failed to create Mock store");

            let created_locker = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_1".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await
                .unwrap();

            let found_locker = mockdb
                .lockers
                .lock()
                .await
                .iter()
                .find(|l| l.card_id == "card_1")
                .cloned();

            assert!(found_locker.is_some());

            assert_eq!(created_locker, found_locker.unwrap())
        }

        #[tokio::test]
        async fn delete_locker_mock_up() {
            let mockdb = MockDb::new(
                &redis_interface::RedisSettings::default(),
                KeyManagerState::new(),
            )
            .await
            .expect("Failed to create Mock store");

            let created_locker = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_1".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await
                .unwrap();

            let deleted_locker = mockdb.delete_locker_mock_up("card_1").await.unwrap();

            assert_eq!(created_locker, deleted_locker);

            let exist = mockdb
                .lockers
                .lock()
                .await
                .iter()
                .any(|l| l.card_id == "card_1");

            assert!(!exist)
        }
    }
}

Tests: crates/router/src/db/locker_mock_up.rs
================================================================================
use error_stack::report;
use router_env::{instrument, tracing};

use super::{MockDb, Store};
use crate::{
    connection,
    core::errors::{self, CustomResult},
    types::storage,
};

#[async_trait::async_trait]
pub trait LockerMockUpInterface {
    async fn find_locker_by_card_id(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError>;

    async fn insert_locker_mock_up(
        &self,
        new: storage::LockerMockUpNew,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError>;

    async fn delete_locker_mock_up(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError>;
}

#[async_trait::async_trait]
impl LockerMockUpInterface for Store {
    #[instrument(skip_all)]
    async fn find_locker_by_card_id(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::LockerMockUp::find_by_card_id(&conn, card_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn insert_locker_mock_up(
        &self,
        new: storage::LockerMockUpNew,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        new.insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn delete_locker_mock_up(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        storage::LockerMockUp::delete_by_card_id(&conn, card_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl LockerMockUpInterface for MockDb {
    async fn find_locker_by_card_id(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        self.lockers
            .lock()
            .await
            .iter()
            .find(|l| l.card_id == card_id)
            .cloned()
            .ok_or(errors::StorageError::MockDbError.into())
    }

    async fn insert_locker_mock_up(
        &self,
        new: storage::LockerMockUpNew,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let mut locked_lockers = self.lockers.lock().await;

        if locked_lockers.iter().any(|l| l.card_id == new.card_id) {
            Err(errors::StorageError::MockDbError)?;
        }

        let created_locker = storage::LockerMockUp {
            card_id: new.card_id,
            external_id: new.external_id,
            card_fingerprint: new.card_fingerprint,
            card_global_fingerprint: new.card_global_fingerprint,
            merchant_id: new.merchant_id,
            card_number: new.card_number,
            card_exp_year: new.card_exp_year,
            card_exp_month: new.card_exp_month,
            name_on_card: new.name_on_card,
            nickname: None,
            customer_id: new.customer_id,
            duplicate: None,
            card_cvc: new.card_cvc,
            payment_method_id: new.payment_method_id,
            enc_card_data: new.enc_card_data,
        };

        locked_lockers.push(created_locker.clone());

        Ok(created_locker)
    }

    async fn delete_locker_mock_up(
        &self,
        card_id: &str,
    ) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
        let mut locked_lockers = self.lockers.lock().await;

        let position = locked_lockers
            .iter()
            .position(|l| l.card_id == card_id)
            .ok_or(errors::StorageError::MockDbError)?;

        Ok(locked_lockers.remove(position))
    }
}

#[cfg(test)]
mod tests {
    mod mockdb_locker_mock_up_interface {
        use common_utils::{
            generate_customer_id_of_default_length, id_type, types::keymanager::KeyManagerState,
        };

        use crate::{
            db::{locker_mock_up::LockerMockUpInterface, MockDb},
            types::storage,
        };

        pub struct LockerMockUpIds {
            card_id: String,
            external_id: String,
            merchant_id: id_type::MerchantId,
            customer_id: id_type::CustomerId,
        }

        fn create_locker_mock_up_new(locker_ids: LockerMockUpIds) -> storage::LockerMockUpNew {
            storage::LockerMockUpNew {
                card_id: locker_ids.card_id,
                external_id: locker_ids.external_id,
                card_fingerprint: "card_fingerprint".into(),
                card_global_fingerprint: "card_global_fingerprint".into(),
                merchant_id: locker_ids.merchant_id,
                card_number: "1234123412341234".into(),
                card_exp_year: "2023".into(),
                card_exp_month: "06".into(),
                name_on_card: Some("name_on_card".into()),
                card_cvc: Some("123".into()),
                payment_method_id: Some("payment_method_id".into()),
                customer_id: Some(locker_ids.customer_id),
                nickname: Some("card_holder_nickname".into()),
                enc_card_data: Some("enc_card_data".into()),
            }
        }

        #[tokio::test]
        async fn find_locker_by_card_id() {
            let mockdb = MockDb::new(
                &redis_interface::RedisSettings::default(),
                KeyManagerState::new(),
            )
            .await
            .expect("Failed to create Mock store");

            let created_locker = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_1".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await
                .unwrap();

            let _ = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_2".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await;

            let found_locker = mockdb.find_locker_by_card_id("card_1").await.unwrap();

            assert_eq!(created_locker, found_locker)
        }

        #[tokio::test]
        async fn insert_locker_mock_up() {
            let mockdb = MockDb::new(
                &redis_interface::RedisSettings::default(),
                KeyManagerState::new(),
            )
            .await
            .expect("Failed to create Mock store");

            let created_locker = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_1".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await
                .unwrap();

            let found_locker = mockdb
                .lockers
                .lock()
                .await
                .iter()
                .find(|l| l.card_id == "card_1")
                .cloned();

            assert!(found_locker.is_some());

            assert_eq!(created_locker, found_locker.unwrap())
        }

        #[tokio::test]
        async fn delete_locker_mock_up() {
            let mockdb = MockDb::new(
                &redis_interface::RedisSettings::default(),
                KeyManagerState::new(),
            )
            .await
            .expect("Failed to create Mock store");

            let created_locker = mockdb
                .insert_locker_mock_up(create_locker_mock_up_new(LockerMockUpIds {
                    card_id: "card_1".into(),
                    external_id: "external_1".into(),
                    merchant_id: id_type::MerchantId::default(),
                    customer_id: generate_customer_id_of_default_length(),
                }))
                .await
                .unwrap();

            let deleted_locker = mockdb.delete_locker_mock_up("card_1").await.unwrap();

            assert_eq!(created_locker, deleted_locker);

            let exist = mockdb
                .lockers
                .lock()
                .await
                .iter()
                .any(|l| l.card_id == "card_1");

            assert!(!exist)
        }
    }
}


Test-Implementation Pair

Implementation: crates/router/src/db/api_keys.rs
================================================================================
use error_stack::report;
use router_env::{instrument, tracing};
#[cfg(feature = "accounts_cache")]
use storage_impl::redis::cache::{self, CacheKind, ACCOUNTS_CACHE};

use super::{MockDb, Store};
use crate::{
    connection,
    core::errors::{self, CustomResult},
    types::storage,
};

#[async_trait::async_trait]
pub trait ApiKeyInterface {
    async fn insert_api_key(
        &self,
        api_key: storage::ApiKeyNew,
    ) -> CustomResult<storage::ApiKey, errors::StorageError>;

    async fn update_api_key(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
        api_key: storage::ApiKeyUpdate,
    ) -> CustomResult<storage::ApiKey, errors::StorageError>;

    async fn revoke_api_key(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<bool, errors::StorageError>;

    async fn find_api_key_by_merchant_id_key_id_optional(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError>;

    async fn find_api_key_by_hash_optional(
        &self,
        hashed_api_key: storage::HashedApiKey,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError>;

    async fn list_api_keys_by_merchant_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: Option<i64>,
        offset: Option<i64>,
    ) -> CustomResult<Vec<storage::ApiKey>, errors::StorageError>;
}

#[async_trait::async_trait]
impl ApiKeyInterface for Store {
    #[instrument(skip_all)]
    async fn insert_api_key(
        &self,
        api_key: storage::ApiKeyNew,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        api_key
            .insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn update_api_key(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
        api_key: storage::ApiKeyUpdate,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        let _merchant_id = merchant_id.clone();
        let _key_id = key_id.clone();
        let update_call = || async {
            storage::ApiKey::update_by_merchant_id_key_id(&conn, merchant_id, key_id, api_key)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        };

        #[cfg(not(feature = "accounts_cache"))]
        {
            update_call().await
        }

        #[cfg(feature = "accounts_cache")]
        {
            use error_stack::report;

            // We need to fetch api_key here because the key that's saved in cache in HashedApiKey.
            // Used function from storage model to reuse the connection that made here instead of
            // creating new.
            let api_key = storage::ApiKey::find_optional_by_merchant_id_key_id(
                &conn,
                &_merchant_id,
                &_key_id,
            )
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))?
            .ok_or(report!(errors::StorageError::ValueNotFound(format!(
                "ApiKey of {} not found",
                _key_id.get_string_repr()
            ))))?;

            cache::publish_and_redact(
                self,
                CacheKind::Accounts(api_key.hashed_api_key.into_inner().into()),
                update_call,
            )
            .await
        }
    }

    #[instrument(skip_all)]
    async fn revoke_api_key(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<bool, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        let delete_call = || async {
            storage::ApiKey::revoke_by_merchant_id_key_id(&conn, merchant_id, key_id)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        };
        #[cfg(not(feature = "accounts_cache"))]
        {
            delete_call().await
        }

        #[cfg(feature = "accounts_cache")]
        {
            use error_stack::report;

            // We need to fetch api_key here because the key that's saved in cache in HashedApiKey.
            // Used function from storage model to reuse the connection that made here instead of
            // creating new.

            let api_key =
                storage::ApiKey::find_optional_by_merchant_id_key_id(&conn, merchant_id, key_id)
                    .await
                    .map_err(|error| report!(errors::StorageError::from(error)))?
                    .ok_or(report!(errors::StorageError::ValueNotFound(format!(
                        "ApiKey of {} not found",
                        key_id.get_string_repr()
                    ))))?;

            cache::publish_and_redact(
                self,
                CacheKind::Accounts(api_key.hashed_api_key.into_inner().into()),
                delete_call,
            )
            .await
        }
    }

    #[instrument(skip_all)]
    async fn find_api_key_by_merchant_id_key_id_optional(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::ApiKey::find_optional_by_merchant_id_key_id(&conn, merchant_id, key_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn find_api_key_by_hash_optional(
        &self,
        hashed_api_key: storage::HashedApiKey,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        let _hashed_api_key = hashed_api_key.clone();
        let find_call = || async {
            let conn = connection::pg_connection_read(self).await?;
            storage::ApiKey::find_optional_by_hashed_api_key(&conn, hashed_api_key)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        };

        #[cfg(not(feature = "accounts_cache"))]
        {
            find_call().await
        }

        #[cfg(feature = "accounts_cache")]
        {
            cache::get_or_populate_in_memory(
                self,
                &_hashed_api_key.into_inner(),
                find_call,
                &ACCOUNTS_CACHE,
            )
            .await
        }
    }

    #[instrument(skip_all)]
    async fn list_api_keys_by_merchant_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: Option<i64>,
        offset: Option<i64>,
    ) -> CustomResult<Vec<storage::ApiKey>, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::ApiKey::find_by_merchant_id(&conn, merchant_id, limit, offset)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl ApiKeyInterface for MockDb {
    async fn insert_api_key(
        &self,
        api_key: storage::ApiKeyNew,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let mut locked_api_keys = self.api_keys.lock().await;
        // don't allow duplicate key_ids, a those would be a unique constraint violation in the
        // real db as it is used as the primary key
        if locked_api_keys.iter().any(|k| k.key_id == api_key.key_id) {
            Err(errors::StorageError::MockDbError)?;
        }
        let stored_key = storage::ApiKey {
            key_id: api_key.key_id,
            merchant_id: api_key.merchant_id,
            name: api_key.name,
            description: api_key.description,
            hashed_api_key: api_key.hashed_api_key,
            prefix: api_key.prefix,
            created_at: api_key.created_at,
            expires_at: api_key.expires_at,
            last_used: api_key.last_used,
        };
        locked_api_keys.push(stored_key.clone());

        Ok(stored_key)
    }

    async fn update_api_key(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
        api_key: storage::ApiKeyUpdate,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let mut locked_api_keys = self.api_keys.lock().await;
        // find a key with the given merchant_id and key_id and update, otherwise return an error
        let key_to_update = locked_api_keys
            .iter_mut()
            .find(|k| k.merchant_id == merchant_id && k.key_id == key_id)
            .ok_or(errors::StorageError::MockDbError)?;

        match api_key {
            storage::ApiKeyUpdate::Update {
                name,
                description,
                expires_at,
                last_used,
            } => {
                if let Some(name) = name {
                    key_to_update.name = name;
                }
                // only update these fields if the value was Some(_)
                if description.is_some() {
                    key_to_update.description = description;
                }
                if let Some(expires_at) = expires_at {
                    key_to_update.expires_at = expires_at;
                }
                if last_used.is_some() {
                    key_to_update.last_used = last_used
                }
            }
            storage::ApiKeyUpdate::LastUsedUpdate { last_used } => {
                key_to_update.last_used = Some(last_used);
            }
        }

        Ok(key_to_update.clone())
    }

    async fn revoke_api_key(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<bool, errors::StorageError> {
        let mut locked_api_keys = self.api_keys.lock().await;
        // find the key to remove, if it exists
        if let Some(pos) = locked_api_keys
            .iter()
            .position(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
        {
            // use `remove` instead of `swap_remove` so we have a consistent order, which might
            // matter to someone using limit/offset in `list_api_keys_by_merchant_id`
            locked_api_keys.remove(pos);
            Ok(true)
        } else {
            Ok(false)
        }
    }

    async fn find_api_key_by_merchant_id_key_id_optional(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        Ok(self
            .api_keys
            .lock()
            .await
            .iter()
            .find(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
            .cloned())
    }

    async fn find_api_key_by_hash_optional(
        &self,
        hashed_api_key: storage::HashedApiKey,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        Ok(self
            .api_keys
            .lock()
            .await
            .iter()
            .find(|k| k.hashed_api_key == hashed_api_key)
            .cloned())
    }

    async fn list_api_keys_by_merchant_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: Option<i64>,
        offset: Option<i64>,
    ) -> CustomResult<Vec<storage::ApiKey>, errors::StorageError> {
        // mimic the SQL limit/offset behavior
        let offset: usize = if let Some(offset) = offset {
            if offset < 0 {
                Err(errors::StorageError::MockDbError)?;
            }
            offset
                .try_into()
                .map_err(|_| errors::StorageError::MockDbError)?
        } else {
            0
        };

        let limit: usize = if let Some(limit) = limit {
            if limit < 0 {
                Err(errors::StorageError::MockDbError)?;
            }
            limit
                .try_into()
                .map_err(|_| errors::StorageError::MockDbError)?
        } else {
            usize::MAX
        };

        let keys_for_merchant_id: Vec<storage::ApiKey> = self
            .api_keys
            .lock()
            .await
            .iter()
            .filter(|k| k.merchant_id == *merchant_id)
            .skip(offset)
            .take(limit)
            .cloned()
            .collect();

        Ok(keys_for_merchant_id)
    }
}

#[cfg(test)]
mod tests {
    use std::borrow::Cow;

    use common_utils::types::keymanager::KeyManagerState;
    use storage_impl::redis::{
        cache::{self, CacheKey, CacheKind, ACCOUNTS_CACHE},
        kv_store::RedisConnInterface,
        pub_sub::PubSubInterface,
    };
    use time::macros::datetime;

    use crate::{
        db::{api_keys::ApiKeyInterface, MockDb},
        types::storage,
    };

    #[tokio::test]
    async fn test_mockdb_api_key_interface() {
        #[allow(clippy::expect_used)]
        let mockdb = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let merchant_id =
            common_utils::id_type::MerchantId::try_from(Cow::from("merchant1")).unwrap();

        let key_id1 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id1")).unwrap();

        let key_id2 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id2")).unwrap();

        let non_existent_key_id =
            common_utils::id_type::ApiKeyId::try_from(Cow::from("does_not_exist")).unwrap();

        let key1 = mockdb
            .insert_api_key(storage::ApiKeyNew {
                key_id: key_id1.clone(),
                merchant_id: merchant_id.clone(),
                name: "Key 1".into(),
                description: None,
                hashed_api_key: "hashed_key1".to_string().into(),
                prefix: "abc".into(),
                created_at: datetime!(2023-02-01 0:00),
                expires_at: Some(datetime!(2023-03-01 0:00)),
                last_used: None,
            })
            .await
            .unwrap();

        mockdb
            .insert_api_key(storage::ApiKeyNew {
                key_id: key_id2.clone(),
                merchant_id: merchant_id.clone(),
                name: "Key 2".into(),
                description: None,
                hashed_api_key: "hashed_key2".to_string().into(),
                prefix: "abc".into(),
                created_at: datetime!(2023-03-01 0:00),
                expires_at: None,
                last_used: None,
            })
            .await
            .unwrap();

        let found_key1 = mockdb
            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
            .await
            .unwrap()
            .unwrap();
        assert_eq!(found_key1.key_id, key1.key_id);
        assert!(mockdb
            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &non_existent_key_id)
            .await
            .unwrap()
            .is_none());

        mockdb
            .update_api_key(
                merchant_id.clone(),
                key_id1.clone(),
                storage::ApiKeyUpdate::LastUsedUpdate {
                    last_used: datetime!(2023-02-04 1:11),
                },
            )
            .await
            .unwrap();
        let updated_key1 = mockdb
            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
            .await
            .unwrap()
            .unwrap();
        assert_eq!(updated_key1.last_used, Some(datetime!(2023-02-04 1:11)));

        assert_eq!(
            mockdb
                .list_api_keys_by_merchant_id(&merchant_id, None, None)
                .await
                .unwrap()
                .len(),
            2
        );
        mockdb.revoke_api_key(&merchant_id, &key_id1).await.unwrap();
        assert_eq!(
            mockdb
                .list_api_keys_by_merchant_id(&merchant_id, None, None)
                .await
                .unwrap()
                .len(),
            1
        );
    }

    #[tokio::test]
    async fn test_api_keys_cache() {
        let merchant_id =
            common_utils::id_type::MerchantId::try_from(Cow::from("test_merchant")).unwrap();

        let db = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let redis_conn = db.get_redis_conn().unwrap();
        redis_conn
            .subscribe("hyperswitch_invalidate")
            .await
            .unwrap();

        let test_key = common_utils::id_type::ApiKeyId::try_from(Cow::from("test_ey")).unwrap();

        let api = storage::ApiKeyNew {
            key_id: test_key.clone(),
            merchant_id: merchant_id.clone(),
            name: "My test key".into(),
            description: None,
            hashed_api_key: "a_hashed_key".to_string().into(),
            prefix: "pre".into(),
            created_at: datetime!(2023-06-01 0:00),
            expires_at: None,
            last_used: None,
        };

        let api = db.insert_api_key(api).await.unwrap();

        let hashed_api_key = api.hashed_api_key.clone();
        let find_call = || async {
            db.find_api_key_by_hash_optional(hashed_api_key.clone())
                .await
        };
        let _: Option<storage::ApiKey> = cache::get_or_populate_in_memory(
            &db,
            &format!(
                "{}_{}",
                merchant_id.get_string_repr(),
                hashed_api_key.clone().into_inner()
            ),
            find_call,
            &ACCOUNTS_CACHE,
        )
        .await
        .unwrap();

        let delete_call = || async { db.revoke_api_key(&merchant_id, &api.key_id).await };

        cache::publish_and_redact(
            &db,
            CacheKind::Accounts(
                format!(
                    "{}_{}",
                    merchant_id.get_string_repr(),
                    hashed_api_key.clone().into_inner()
                )
                .into(),
            ),
            delete_call,
        )
        .await
        .unwrap();

        assert!(ACCOUNTS_CACHE
            .get_val::<storage::ApiKey>(CacheKey {
                key: format!(
                    "{}_{}",
                    merchant_id.get_string_repr(),
                    hashed_api_key.into_inner()
                ),
                prefix: String::default(),
            },)
            .await
            .is_none())
    }
}

Tests: crates/router/src/db/api_keys.rs
================================================================================
use error_stack::report;
use router_env::{instrument, tracing};
#[cfg(feature = "accounts_cache")]
use storage_impl::redis::cache::{self, CacheKind, ACCOUNTS_CACHE};

use super::{MockDb, Store};
use crate::{
    connection,
    core::errors::{self, CustomResult},
    types::storage,
};

#[async_trait::async_trait]
pub trait ApiKeyInterface {
    async fn insert_api_key(
        &self,
        api_key: storage::ApiKeyNew,
    ) -> CustomResult<storage::ApiKey, errors::StorageError>;

    async fn update_api_key(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
        api_key: storage::ApiKeyUpdate,
    ) -> CustomResult<storage::ApiKey, errors::StorageError>;

    async fn revoke_api_key(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<bool, errors::StorageError>;

    async fn find_api_key_by_merchant_id_key_id_optional(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError>;

    async fn find_api_key_by_hash_optional(
        &self,
        hashed_api_key: storage::HashedApiKey,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError>;

    async fn list_api_keys_by_merchant_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: Option<i64>,
        offset: Option<i64>,
    ) -> CustomResult<Vec<storage::ApiKey>, errors::StorageError>;
}

#[async_trait::async_trait]
impl ApiKeyInterface for Store {
    #[instrument(skip_all)]
    async fn insert_api_key(
        &self,
        api_key: storage::ApiKeyNew,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        api_key
            .insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn update_api_key(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
        api_key: storage::ApiKeyUpdate,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        let _merchant_id = merchant_id.clone();
        let _key_id = key_id.clone();
        let update_call = || async {
            storage::ApiKey::update_by_merchant_id_key_id(&conn, merchant_id, key_id, api_key)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        };

        #[cfg(not(feature = "accounts_cache"))]
        {
            update_call().await
        }

        #[cfg(feature = "accounts_cache")]
        {
            use error_stack::report;

            // We need to fetch api_key here because the key that's saved in cache in HashedApiKey.
            // Used function from storage model to reuse the connection that made here instead of
            // creating new.
            let api_key = storage::ApiKey::find_optional_by_merchant_id_key_id(
                &conn,
                &_merchant_id,
                &_key_id,
            )
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))?
            .ok_or(report!(errors::StorageError::ValueNotFound(format!(
                "ApiKey of {} not found",
                _key_id.get_string_repr()
            ))))?;

            cache::publish_and_redact(
                self,
                CacheKind::Accounts(api_key.hashed_api_key.into_inner().into()),
                update_call,
            )
            .await
        }
    }

    #[instrument(skip_all)]
    async fn revoke_api_key(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<bool, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        let delete_call = || async {
            storage::ApiKey::revoke_by_merchant_id_key_id(&conn, merchant_id, key_id)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        };
        #[cfg(not(feature = "accounts_cache"))]
        {
            delete_call().await
        }

        #[cfg(feature = "accounts_cache")]
        {
            use error_stack::report;

            // We need to fetch api_key here because the key that's saved in cache in HashedApiKey.
            // Used function from storage model to reuse the connection that made here instead of
            // creating new.

            let api_key =
                storage::ApiKey::find_optional_by_merchant_id_key_id(&conn, merchant_id, key_id)
                    .await
                    .map_err(|error| report!(errors::StorageError::from(error)))?
                    .ok_or(report!(errors::StorageError::ValueNotFound(format!(
                        "ApiKey of {} not found",
                        key_id.get_string_repr()
                    ))))?;

            cache::publish_and_redact(
                self,
                CacheKind::Accounts(api_key.hashed_api_key.into_inner().into()),
                delete_call,
            )
            .await
        }
    }

    #[instrument(skip_all)]
    async fn find_api_key_by_merchant_id_key_id_optional(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::ApiKey::find_optional_by_merchant_id_key_id(&conn, merchant_id, key_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn find_api_key_by_hash_optional(
        &self,
        hashed_api_key: storage::HashedApiKey,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        let _hashed_api_key = hashed_api_key.clone();
        let find_call = || async {
            let conn = connection::pg_connection_read(self).await?;
            storage::ApiKey::find_optional_by_hashed_api_key(&conn, hashed_api_key)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        };

        #[cfg(not(feature = "accounts_cache"))]
        {
            find_call().await
        }

        #[cfg(feature = "accounts_cache")]
        {
            cache::get_or_populate_in_memory(
                self,
                &_hashed_api_key.into_inner(),
                find_call,
                &ACCOUNTS_CACHE,
            )
            .await
        }
    }

    #[instrument(skip_all)]
    async fn list_api_keys_by_merchant_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: Option<i64>,
        offset: Option<i64>,
    ) -> CustomResult<Vec<storage::ApiKey>, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::ApiKey::find_by_merchant_id(&conn, merchant_id, limit, offset)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl ApiKeyInterface for MockDb {
    async fn insert_api_key(
        &self,
        api_key: storage::ApiKeyNew,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let mut locked_api_keys = self.api_keys.lock().await;
        // don't allow duplicate key_ids, a those would be a unique constraint violation in the
        // real db as it is used as the primary key
        if locked_api_keys.iter().any(|k| k.key_id == api_key.key_id) {
            Err(errors::StorageError::MockDbError)?;
        }
        let stored_key = storage::ApiKey {
            key_id: api_key.key_id,
            merchant_id: api_key.merchant_id,
            name: api_key.name,
            description: api_key.description,
            hashed_api_key: api_key.hashed_api_key,
            prefix: api_key.prefix,
            created_at: api_key.created_at,
            expires_at: api_key.expires_at,
            last_used: api_key.last_used,
        };
        locked_api_keys.push(stored_key.clone());

        Ok(stored_key)
    }

    async fn update_api_key(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
        api_key: storage::ApiKeyUpdate,
    ) -> CustomResult<storage::ApiKey, errors::StorageError> {
        let mut locked_api_keys = self.api_keys.lock().await;
        // find a key with the given merchant_id and key_id and update, otherwise return an error
        let key_to_update = locked_api_keys
            .iter_mut()
            .find(|k| k.merchant_id == merchant_id && k.key_id == key_id)
            .ok_or(errors::StorageError::MockDbError)?;

        match api_key {
            storage::ApiKeyUpdate::Update {
                name,
                description,
                expires_at,
                last_used,
            } => {
                if let Some(name) = name {
                    key_to_update.name = name;
                }
                // only update these fields if the value was Some(_)
                if description.is_some() {
                    key_to_update.description = description;
                }
                if let Some(expires_at) = expires_at {
                    key_to_update.expires_at = expires_at;
                }
                if last_used.is_some() {
                    key_to_update.last_used = last_used
                }
            }
            storage::ApiKeyUpdate::LastUsedUpdate { last_used } => {
                key_to_update.last_used = Some(last_used);
            }
        }

        Ok(key_to_update.clone())
    }

    async fn revoke_api_key(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<bool, errors::StorageError> {
        let mut locked_api_keys = self.api_keys.lock().await;
        // find the key to remove, if it exists
        if let Some(pos) = locked_api_keys
            .iter()
            .position(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
        {
            // use `remove` instead of `swap_remove` so we have a consistent order, which might
            // matter to someone using limit/offset in `list_api_keys_by_merchant_id`
            locked_api_keys.remove(pos);
            Ok(true)
        } else {
            Ok(false)
        }
    }

    async fn find_api_key_by_merchant_id_key_id_optional(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        Ok(self
            .api_keys
            .lock()
            .await
            .iter()
            .find(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
            .cloned())
    }

    async fn find_api_key_by_hash_optional(
        &self,
        hashed_api_key: storage::HashedApiKey,
    ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
        Ok(self
            .api_keys
            .lock()
            .await
            .iter()
            .find(|k| k.hashed_api_key == hashed_api_key)
            .cloned())
    }

    async fn list_api_keys_by_merchant_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: Option<i64>,
        offset: Option<i64>,
    ) -> CustomResult<Vec<storage::ApiKey>, errors::StorageError> {
        // mimic the SQL limit/offset behavior
        let offset: usize = if let Some(offset) = offset {
            if offset < 0 {
                Err(errors::StorageError::MockDbError)?;
            }
            offset
                .try_into()
                .map_err(|_| errors::StorageError::MockDbError)?
        } else {
            0
        };

        let limit: usize = if let Some(limit) = limit {
            if limit < 0 {
                Err(errors::StorageError::MockDbError)?;
            }
            limit
                .try_into()
                .map_err(|_| errors::StorageError::MockDbError)?
        } else {
            usize::MAX
        };

        let keys_for_merchant_id: Vec<storage::ApiKey> = self
            .api_keys
            .lock()
            .await
            .iter()
            .filter(|k| k.merchant_id == *merchant_id)
            .skip(offset)
            .take(limit)
            .cloned()
            .collect();

        Ok(keys_for_merchant_id)
    }
}

#[cfg(test)]
mod tests {
    use std::borrow::Cow;

    use common_utils::types::keymanager::KeyManagerState;
    use storage_impl::redis::{
        cache::{self, CacheKey, CacheKind, ACCOUNTS_CACHE},
        kv_store::RedisConnInterface,
        pub_sub::PubSubInterface,
    };
    use time::macros::datetime;

    use crate::{
        db::{api_keys::ApiKeyInterface, MockDb},
        types::storage,
    };

    #[tokio::test]
    async fn test_mockdb_api_key_interface() {
        #[allow(clippy::expect_used)]
        let mockdb = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let merchant_id =
            common_utils::id_type::MerchantId::try_from(Cow::from("merchant1")).unwrap();

        let key_id1 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id1")).unwrap();

        let key_id2 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id2")).unwrap();

        let non_existent_key_id =
            common_utils::id_type::ApiKeyId::try_from(Cow::from("does_not_exist")).unwrap();

        let key1 = mockdb
            .insert_api_key(storage::ApiKeyNew {
                key_id: key_id1.clone(),
                merchant_id: merchant_id.clone(),
                name: "Key 1".into(),
                description: None,
                hashed_api_key: "hashed_key1".to_string().into(),
                prefix: "abc".into(),
                created_at: datetime!(2023-02-01 0:00),
                expires_at: Some(datetime!(2023-03-01 0:00)),
                last_used: None,
            })
            .await
            .unwrap();

        mockdb
            .insert_api_key(storage::ApiKeyNew {
                key_id: key_id2.clone(),
                merchant_id: merchant_id.clone(),
                name: "Key 2".into(),
                description: None,
                hashed_api_key: "hashed_key2".to_string().into(),
                prefix: "abc".into(),
                created_at: datetime!(2023-03-01 0:00),
                expires_at: None,
                last_used: None,
            })
            .await
            .unwrap();

        let found_key1 = mockdb
            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
            .await
            .unwrap()
            .unwrap();
        assert_eq!(found_key1.key_id, key1.key_id);
        assert!(mockdb
            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &non_existent_key_id)
            .await
            .unwrap()
            .is_none());

        mockdb
            .update_api_key(
                merchant_id.clone(),
                key_id1.clone(),
                storage::ApiKeyUpdate::LastUsedUpdate {
                    last_used: datetime!(2023-02-04 1:11),
                },
            )
            .await
            .unwrap();
        let updated_key1 = mockdb
            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
            .await
            .unwrap()
            .unwrap();
        assert_eq!(updated_key1.last_used, Some(datetime!(2023-02-04 1:11)));

        assert_eq!(
            mockdb
                .list_api_keys_by_merchant_id(&merchant_id, None, None)
                .await
                .unwrap()
                .len(),
            2
        );
        mockdb.revoke_api_key(&merchant_id, &key_id1).await.unwrap();
        assert_eq!(
            mockdb
                .list_api_keys_by_merchant_id(&merchant_id, None, None)
                .await
                .unwrap()
                .len(),
            1
        );
    }

    #[tokio::test]
    async fn test_api_keys_cache() {
        let merchant_id =
            common_utils::id_type::MerchantId::try_from(Cow::from("test_merchant")).unwrap();

        let db = MockDb::new(
            &redis_interface::RedisSettings::default(),
            KeyManagerState::new(),
        )
        .await
        .expect("Failed to create Mock store");

        let redis_conn = db.get_redis_conn().unwrap();
        redis_conn
            .subscribe("hyperswitch_invalidate")
            .await
            .unwrap();

        let test_key = common_utils::id_type::ApiKeyId::try_from(Cow::from("test_ey")).unwrap();

        let api = storage::ApiKeyNew {
            key_id: test_key.clone(),
            merchant_id: merchant_id.clone(),
            name: "My test key".into(),
            description: None,
            hashed_api_key: "a_hashed_key".to_string().into(),
            prefix: "pre".into(),
            created_at: datetime!(2023-06-01 0:00),
            expires_at: None,
            last_used: None,
        };

        let api = db.insert_api_key(api).await.unwrap();

        let hashed_api_key = api.hashed_api_key.clone();
        let find_call = || async {
            db.find_api_key_by_hash_optional(hashed_api_key.clone())
                .await
        };
        let _: Option<storage::ApiKey> = cache::get_or_populate_in_memory(
            &db,
            &format!(
                "{}_{}",
                merchant_id.get_string_repr(),
                hashed_api_key.clone().into_inner()
            ),
            find_call,
            &ACCOUNTS_CACHE,
        )
        .await
        .unwrap();

        let delete_call = || async { db.revoke_api_key(&merchant_id, &api.key_id).await };

        cache::publish_and_redact(
            &db,
            CacheKind::Accounts(
                format!(
                    "{}_{}",
                    merchant_id.get_string_repr(),
                    hashed_api_key.clone().into_inner()
                )
                .into(),
            ),
            delete_call,
        )
        .await
        .unwrap();

        assert!(ACCOUNTS_CACHE
            .get_val::<storage::ApiKey>(CacheKey {
                key: format!(
                    "{}_{}",
                    merchant_id.get_string_repr(),
                    hashed_api_key.into_inner()
                ),
                prefix: String::default(),
            },)
            .await
            .is_none())
    }
}