You've already forked AstralRinth
forked from didirus/AstralRinth
Task to retroactively update Mural statuses (#4769)
* Task to retroactively update Mural statuses * cargo sqlx prepare * wip: add tests * Prepare * Fix up test * start on muralpay mock * Move mocking to muralpay crate
This commit is contained in:
@@ -9,6 +9,7 @@ keywords = []
|
||||
categories = ["api-bindings"]
|
||||
|
||||
[dependencies]
|
||||
arc-swap = { workspace = true, optional = true }
|
||||
bytes = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
derive_more = { workspace = true, features = [
|
||||
@@ -37,6 +38,7 @@ tokio = { workspace = true, features = ["full"] }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
[features]
|
||||
mock = ["dep:arc-swap"]
|
||||
utoipa = ["dep:utoipa"]
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -14,6 +14,8 @@ use crate::{
|
||||
|
||||
impl MuralPay {
|
||||
pub async fn get_all_accounts(&self) -> Result<Vec<Account>, MuralError> {
|
||||
mock!(self, get_all_accounts());
|
||||
|
||||
self.http_get(|base| format!("{base}/api/accounts"))
|
||||
.send_mural()
|
||||
.await
|
||||
@@ -23,6 +25,8 @@ impl MuralPay {
|
||||
&self,
|
||||
id: AccountId,
|
||||
) -> Result<Account, MuralError> {
|
||||
mock!(self, get_account(id));
|
||||
|
||||
self.http_get(|base| format!("{base}/api/accounts/{id}"))
|
||||
.send_mural()
|
||||
.await
|
||||
@@ -33,6 +37,14 @@ impl MuralPay {
|
||||
name: impl AsRef<str>,
|
||||
description: Option<impl AsRef<str>>,
|
||||
) -> Result<Account, MuralError> {
|
||||
mock!(
|
||||
self,
|
||||
create_account(
|
||||
name.as_ref(),
|
||||
description.as_ref().map(|x| x.as_ref()),
|
||||
)
|
||||
);
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body<'a> {
|
||||
|
||||
@@ -14,6 +14,8 @@ impl MuralPay {
|
||||
&self,
|
||||
params: Option<SearchParams<CounterpartyId>>,
|
||||
) -> Result<SearchResponse<CounterpartyId, Counterparty>, MuralError> {
|
||||
mock!(self, search_counterparties(params));
|
||||
|
||||
self.http_post(|base| format!("{base}/api/counterparties/search"))
|
||||
.query(¶ms.map(|p| p.to_query()).unwrap_or_default())
|
||||
.send_mural()
|
||||
@@ -24,6 +26,8 @@ impl MuralPay {
|
||||
&self,
|
||||
id: CounterpartyId,
|
||||
) -> Result<Counterparty, MuralError> {
|
||||
mock!(self, get_counterparty(id));
|
||||
|
||||
self.http_get(|base| {
|
||||
format!("{base}/api/counterparties/counterparty/{id}")
|
||||
})
|
||||
@@ -35,6 +39,8 @@ impl MuralPay {
|
||||
&self,
|
||||
counterparty: &CreateCounterparty,
|
||||
) -> Result<Counterparty, MuralError> {
|
||||
mock!(self, create_counterparty(counterparty));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body<'a> {
|
||||
@@ -54,6 +60,8 @@ impl MuralPay {
|
||||
id: CounterpartyId,
|
||||
counterparty: &UpdateCounterparty,
|
||||
) -> Result<Counterparty, MuralError> {
|
||||
mock!(self, update_counterparty(id, counterparty));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body<'a> {
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
macro_rules! mock {
|
||||
($self:expr, $fn:ident ( $($args:expr),* $(,)? )) => {
|
||||
#[cfg(feature = "mock")]
|
||||
if let Some(mock) = &*($self).mock.load() {
|
||||
return (mock.$fn)($($args),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod account;
|
||||
mod counterparty;
|
||||
mod error;
|
||||
@@ -9,6 +18,9 @@ mod payout_method;
|
||||
mod serde_iso3166;
|
||||
mod util;
|
||||
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
|
||||
pub use {
|
||||
account::*, counterparty::*, error::*, organization::*, payout::*,
|
||||
payout_method::*,
|
||||
@@ -32,6 +44,8 @@ pub struct MuralPay {
|
||||
pub api_url: String,
|
||||
pub api_key: SecretString,
|
||||
pub transfer_api_key: Option<SecretString>,
|
||||
#[cfg(feature = "mock")]
|
||||
mock: arc_swap::ArcSwapOption<mock::MuralPayMock>,
|
||||
}
|
||||
|
||||
impl MuralPay {
|
||||
@@ -45,6 +59,21 @@ impl MuralPay {
|
||||
api_url: api_url.into(),
|
||||
api_key: api_key.into(),
|
||||
transfer_api_key: transfer_api_key.map(Into::into),
|
||||
#[cfg(feature = "mock")]
|
||||
mock: arc_swap::ArcSwapOption::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a client which mocks responses.
|
||||
#[cfg(feature = "mock")]
|
||||
#[must_use]
|
||||
pub fn from_mock(mock: mock::MuralPayMock) -> Self {
|
||||
Self {
|
||||
http: reqwest::Client::new(),
|
||||
api_url: "".into(),
|
||||
api_key: SecretString::from(String::new()),
|
||||
transfer_api_key: None,
|
||||
mock: arc_swap::ArcSwapOption::from_pointee(mock),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
packages/muralpay/src/mock.rs
Normal file
65
packages/muralpay/src/mock.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
//! See [`MuralPayMock`].
|
||||
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
use crate::{
|
||||
Account, AccountId, BankDetailsResponse, Counterparty, CounterpartyId,
|
||||
CreateCounterparty, CreatePayout, FiatAndRailCode, FiatFeeRequest,
|
||||
FiatPayoutFee, MuralError, Organization, OrganizationId, PayoutMethod,
|
||||
PayoutMethodDetails, PayoutMethodId, PayoutRequest, PayoutRequestId,
|
||||
PayoutStatusFilter, SearchParams, SearchRequest, SearchResponse,
|
||||
TokenFeeRequest, TokenPayoutFee, TransferError, UpdateCounterparty,
|
||||
};
|
||||
|
||||
macro_rules! impl_mock {
|
||||
(
|
||||
$(fn $fn:ident ( $( $ty:ty ),* ) -> $ret:ty);* $(;)?
|
||||
) => {
|
||||
/// Mock data returned by [`crate::MuralPay`].
|
||||
pub struct MuralPayMock {
|
||||
$(
|
||||
pub $fn: Box<dyn Fn($($ty),*) -> $ret + Send + Sync>,
|
||||
)*
|
||||
}
|
||||
|
||||
impl Default for MuralPayMock {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
$(
|
||||
$fn: Box::new(|$(_: $ty),*| panic!("missing mock for `{}`", stringify!($fn))),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_mock! {
|
||||
fn get_all_accounts() -> Result<Vec<Account>, MuralError>;
|
||||
fn get_account(AccountId) -> Result<Account, MuralError>;
|
||||
fn create_account(&str, Option<&str>) -> Result<Account, MuralError>;
|
||||
fn search_payout_requests(Option<PayoutStatusFilter>, Option<SearchParams<PayoutRequestId>>) -> Result<SearchResponse<PayoutRequestId, PayoutRequest>, MuralError>;
|
||||
fn get_payout_request(PayoutRequestId) -> Result<PayoutRequest, MuralError>;
|
||||
fn get_fees_for_token_amount(&[TokenFeeRequest]) -> Result<Vec<TokenPayoutFee>, MuralError>;
|
||||
fn get_fees_for_fiat_amount(&[FiatFeeRequest]) -> Result<Vec<FiatPayoutFee>, MuralError>;
|
||||
fn create_payout_request(AccountId, Option<&str>, &[CreatePayout]) -> Result<PayoutRequest, MuralError>;
|
||||
fn execute_payout_request(PayoutRequestId) -> Result<PayoutRequest, TransferError>;
|
||||
fn cancel_payout_request(PayoutRequestId) -> Result<PayoutRequest, TransferError>;
|
||||
fn get_bank_details(&[FiatAndRailCode]) -> Result<BankDetailsResponse, MuralError>;
|
||||
fn search_payout_methods(CounterpartyId, Option<SearchParams<PayoutMethodId>>) -> Result<SearchResponse<PayoutMethodId, PayoutMethod>, MuralError>;
|
||||
fn get_payout_method(CounterpartyId, PayoutMethodId) -> Result<PayoutMethod, MuralError>;
|
||||
fn create_payout_method(CounterpartyId, &str, &PayoutMethodDetails) -> Result<PayoutMethod, MuralError>;
|
||||
fn delete_payout_method(CounterpartyId, PayoutMethodId) -> Result<(), MuralError>;
|
||||
fn search_organizations(SearchRequest) -> Result<SearchResponse<OrganizationId, Organization>, MuralError>;
|
||||
fn get_organization(OrganizationId) -> Result<Organization, MuralError>;
|
||||
fn search_counterparties(Option<SearchParams<CounterpartyId>>) -> Result<SearchResponse<CounterpartyId, Counterparty>, MuralError>;
|
||||
fn get_counterparty(CounterpartyId) -> Result<Counterparty, MuralError>;
|
||||
fn create_counterparty(&CreateCounterparty) -> Result<Counterparty, MuralError>;
|
||||
fn update_counterparty(CounterpartyId, &UpdateCounterparty) -> Result<Counterparty, MuralError>;
|
||||
}
|
||||
|
||||
impl Debug for MuralPayMock {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("MuralPayMock").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ impl MuralPay {
|
||||
&self,
|
||||
req: SearchRequest,
|
||||
) -> Result<SearchResponse<OrganizationId, Organization>, MuralError> {
|
||||
mock!(self, search_organizations(req.clone()));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body {
|
||||
@@ -64,6 +66,8 @@ impl MuralPay {
|
||||
&self,
|
||||
id: OrganizationId,
|
||||
) -> Result<Organization, MuralError> {
|
||||
mock!(self, get_organization(id));
|
||||
|
||||
self.http_post(|base| format!("{base}/api/organizations/{id}"))
|
||||
.send_mural()
|
||||
.await
|
||||
|
||||
@@ -29,6 +29,8 @@ impl MuralPay {
|
||||
params: Option<SearchParams<PayoutRequestId>>,
|
||||
) -> Result<SearchResponse<PayoutRequestId, PayoutRequest>, MuralError>
|
||||
{
|
||||
mock!(self, search_payout_requests(filter, params));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body {
|
||||
@@ -50,6 +52,8 @@ impl MuralPay {
|
||||
&self,
|
||||
id: PayoutRequestId,
|
||||
) -> Result<PayoutRequest, MuralError> {
|
||||
mock!(self, get_payout_request(id));
|
||||
|
||||
self.http_get(|base| format!("{base}/api/payouts/payout/{id}"))
|
||||
.send_mural()
|
||||
.await
|
||||
@@ -59,6 +63,8 @@ impl MuralPay {
|
||||
&self,
|
||||
token_fee_requests: &[TokenFeeRequest],
|
||||
) -> Result<Vec<TokenPayoutFee>, MuralError> {
|
||||
mock!(self, get_fees_for_token_amount(token_fee_requests));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body<'a> {
|
||||
@@ -77,6 +83,8 @@ impl MuralPay {
|
||||
&self,
|
||||
fiat_fee_requests: &[FiatFeeRequest],
|
||||
) -> Result<Vec<FiatPayoutFee>, MuralError> {
|
||||
mock!(self, get_fees_for_fiat_amount(fiat_fee_requests));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body<'a> {
|
||||
@@ -97,6 +105,8 @@ impl MuralPay {
|
||||
memo: Option<impl AsRef<str>>,
|
||||
payouts: &[CreatePayout],
|
||||
) -> Result<PayoutRequest, MuralError> {
|
||||
mock!(self, create_payout_request(source_account_id, memo.as_ref().map(|x| x.as_ref()), payouts));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body<'a> {
|
||||
@@ -121,6 +131,8 @@ impl MuralPay {
|
||||
&self,
|
||||
id: PayoutRequestId,
|
||||
) -> Result<PayoutRequest, TransferError> {
|
||||
mock!(self, execute_payout_request(id));
|
||||
|
||||
self.http_post(|base| format!("{base}/api/payouts/payout/{id}/execute"))
|
||||
.transfer_auth(self)?
|
||||
.send_mural()
|
||||
@@ -132,6 +144,8 @@ impl MuralPay {
|
||||
&self,
|
||||
id: PayoutRequestId,
|
||||
) -> Result<PayoutRequest, TransferError> {
|
||||
mock!(self, cancel_payout_request(id));
|
||||
|
||||
self.http_post(|base| format!("{base}/api/payouts/payout/{id}/cancel"))
|
||||
.transfer_auth(self)?
|
||||
.send_mural()
|
||||
@@ -143,6 +157,8 @@ impl MuralPay {
|
||||
&self,
|
||||
fiat_currency_and_rail: &[FiatAndRailCode],
|
||||
) -> Result<BankDetailsResponse, MuralError> {
|
||||
mock!(self, get_bank_details(fiat_currency_and_rail));
|
||||
|
||||
let query = fiat_currency_and_rail
|
||||
.iter()
|
||||
.map(|code| ("fiatCurrencyAndRail", code.to_string()))
|
||||
@@ -207,7 +223,7 @@ impl FromStr for PayoutId {
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum PayoutStatusFilter {
|
||||
PayoutStatus { statuses: Vec<String> },
|
||||
PayoutStatus { statuses: Vec<PayoutStatus> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -19,6 +19,8 @@ impl MuralPay {
|
||||
counterparty_id: CounterpartyId,
|
||||
params: Option<SearchParams<PayoutMethodId>>,
|
||||
) -> Result<SearchResponse<PayoutMethodId, PayoutMethod>, MuralError> {
|
||||
mock!(self, search_payout_methods(counterparty_id, params));
|
||||
|
||||
self.http_post(|base| {
|
||||
format!(
|
||||
"{base}/api/counterparties/{counterparty_id}/payout-methods/search"
|
||||
@@ -34,6 +36,8 @@ impl MuralPay {
|
||||
counterparty_id: CounterpartyId,
|
||||
payout_method_id: PayoutMethodId,
|
||||
) -> Result<PayoutMethod, MuralError> {
|
||||
mock!(self, get_payout_method(counterparty_id, payout_method_id));
|
||||
|
||||
self.http_get(|base| format!("{base}/api/counterparties/{counterparty_id}/payout-methods/{payout_method_id}"))
|
||||
.send_mural()
|
||||
.await
|
||||
@@ -45,6 +49,8 @@ impl MuralPay {
|
||||
alias: impl AsRef<str>,
|
||||
payout_method: &PayoutMethodDetails,
|
||||
) -> Result<PayoutMethod, MuralError> {
|
||||
mock!(self, create_payout_method(counterparty_id, alias.as_ref(), payout_method));
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Body<'a> {
|
||||
@@ -72,6 +78,8 @@ impl MuralPay {
|
||||
counterparty_id: CounterpartyId,
|
||||
payout_method_id: PayoutMethodId,
|
||||
) -> Result<(), MuralError> {
|
||||
mock!(self, delete_payout_method(counterparty_id, payout_method_id));
|
||||
|
||||
self.http_delete(|base| format!("{base}/api/counterparties/{counterparty_id}/payout-methods/{payout_method_id}"))
|
||||
.send_mural()
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user