Browse Source
458: Roll-out usage of a mocking framework in actor tests r=klochowicz a=klochowicz Mockall is a mocking framework that removes the need for writing more actors, making tests easier to write. Summary: - add one more layer of indirection (a trait per actor type: Wallet, Oracle, Monitor) - Mocks implementing the actor traits (with default stubbed implementations if no extra behaviour needed) - references to the mocks are being passed into the tests (via Arc<Mutex>>), allowing for dynamically changing the behaviour and adding assertions. This also aids readability, as the mock setup can be collocated with a particular test, if the test needs something extra Co-authored-by: Mariusz Klochowicz <mariusz@klochowicz.com>burn-down-handle
bors[bot]
3 years ago
committed by
GitHub
8 changed files with 376 additions and 189 deletions
@ -1,3 +1,57 @@ |
|||
use std::sync::Arc; |
|||
|
|||
use tokio::sync::{Mutex, MutexGuard}; |
|||
|
|||
use self::monitor::MonitorActor; |
|||
use self::oracle::OracleActor; |
|||
use self::wallet::WalletActor; |
|||
|
|||
pub mod monitor; |
|||
pub mod oracle; |
|||
pub mod wallet; |
|||
|
|||
#[derive(Clone)] |
|||
pub struct Mocks { |
|||
pub wallet: Arc<Mutex<wallet::MockWallet>>, |
|||
pub monitor: Arc<Mutex<monitor::MockMonitor>>, |
|||
pub oracle: Arc<Mutex<oracle::MockOracle>>, |
|||
} |
|||
|
|||
impl Mocks { |
|||
pub async fn wallet(&mut self) -> MutexGuard<'_, wallet::MockWallet> { |
|||
self.wallet.lock().await |
|||
} |
|||
|
|||
#[allow(dead_code)] // will be used soon
|
|||
pub async fn monitor(&mut self) -> MutexGuard<'_, monitor::MockMonitor> { |
|||
self.monitor.lock().await |
|||
} |
|||
|
|||
pub async fn oracle(&mut self) -> MutexGuard<'_, oracle::MockOracle> { |
|||
self.oracle.lock().await |
|||
} |
|||
} |
|||
|
|||
impl Default for Mocks { |
|||
fn default() -> Self { |
|||
Self { |
|||
oracle: Arc::new(Mutex::new(oracle::MockOracle::new())), |
|||
monitor: Arc::new(Mutex::new(monitor::MockMonitor::new())), |
|||
wallet: Arc::new(Mutex::new(wallet::MockWallet::new())), |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// Creates actors with embedded mock handlers
|
|||
pub fn create_actors(mocks: &Mocks) -> (OracleActor, MonitorActor, WalletActor) { |
|||
let oracle = OracleActor { |
|||
mock: mocks.oracle.clone(), |
|||
}; |
|||
let monitor = MonitorActor { |
|||
mock: mocks.monitor.clone(), |
|||
}; |
|||
let wallet = WalletActor { |
|||
mock: mocks.wallet.clone(), |
|||
}; |
|||
(oracle, monitor, wallet) |
|||
} |
|||
|
@ -1,29 +1,53 @@ |
|||
use std::sync::Arc; |
|||
|
|||
use daemon::{monitor, oracle}; |
|||
use mockall::*; |
|||
use tokio::sync::Mutex; |
|||
use xtra_productivity::xtra_productivity; |
|||
|
|||
/// Test Stub simulating the Monitor actor
|
|||
pub struct Monitor; |
|||
impl xtra::Actor for Monitor {} |
|||
/// Test Stub simulating the Monitor actor.
|
|||
/// Serves as an entrypoint for injected mock handlers.
|
|||
pub struct MonitorActor { |
|||
pub mock: Arc<Mutex<dyn Monitor + Send>>, |
|||
} |
|||
|
|||
impl xtra::Actor for MonitorActor {} |
|||
impl Monitor for MonitorActor {} |
|||
|
|||
#[xtra_productivity(message_impl = false)] |
|||
impl Monitor { |
|||
async fn handle(&mut self, _msg: monitor::Sync) {} |
|||
impl MonitorActor { |
|||
async fn handle(&mut self, msg: monitor::Sync) { |
|||
self.mock.lock().await.sync(msg) |
|||
} |
|||
|
|||
async fn handle(&mut self, _msg: monitor::StartMonitoring) { |
|||
todo!("stub this if needed") |
|||
async fn handle(&mut self, msg: monitor::StartMonitoring) { |
|||
self.mock.lock().await.start_monitoring(msg) |
|||
} |
|||
|
|||
async fn handle(&mut self, _msg: monitor::CollaborativeSettlement) { |
|||
todo!("stub this if needed") |
|||
async fn handle(&mut self, msg: monitor::CollaborativeSettlement) { |
|||
self.mock.lock().await.collaborative_settlement(msg) |
|||
} |
|||
|
|||
async fn handle(&mut self, _msg: oracle::Attestation) { |
|||
todo!("stub this if needed") |
|||
async fn handle(&mut self, msg: oracle::Attestation) { |
|||
self.mock.lock().await.oracle_attestation(msg) |
|||
} |
|||
} |
|||
|
|||
impl Default for Monitor { |
|||
fn default() -> Self { |
|||
Monitor |
|||
#[automock] |
|||
pub trait Monitor { |
|||
fn sync(&mut self, _msg: monitor::Sync) { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
fn start_monitoring(&mut self, _msg: monitor::StartMonitoring) { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
fn collaborative_settlement(&mut self, _msg: monitor::CollaborativeSettlement) { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
fn oracle_attestation(&mut self, _msg: oracle::Attestation) { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
} |
|||
|
@ -1,45 +1,57 @@ |
|||
use crate::harness::cfd_protocol::OliviaData; |
|||
use daemon::model::BitMexPriceEventId; |
|||
use daemon::oracle; |
|||
use mockall::*; |
|||
use std::sync::Arc; |
|||
use time::OffsetDateTime; |
|||
use tokio::sync::Mutex; |
|||
use xtra_productivity::xtra_productivity; |
|||
|
|||
pub struct Oracle { |
|||
announcement: Option<oracle::Announcement>, |
|||
/// Test Stub simulating the Oracle actor.
|
|||
/// Serves as an entrypoint for injected mock handlers.
|
|||
pub struct OracleActor { |
|||
pub mock: Arc<Mutex<dyn Oracle + Send>>, |
|||
} |
|||
|
|||
impl Oracle { |
|||
pub fn with_dummy_announcement( |
|||
mut self, |
|||
dummy_announcement: cfd_protocol::Announcement, |
|||
) -> Self { |
|||
self.announcement = Some(oracle::Announcement { |
|||
id: BitMexPriceEventId::new(OffsetDateTime::UNIX_EPOCH, 0), |
|||
expected_outcome_time: OffsetDateTime::now_utc(), |
|||
nonce_pks: dummy_announcement.nonce_pks, |
|||
}); |
|||
|
|||
self |
|||
impl xtra::Actor for OracleActor {} |
|||
impl Oracle for OracleActor {} |
|||
|
|||
#[xtra_productivity(message_impl = false)] |
|||
impl OracleActor { |
|||
async fn handle(&mut self, msg: oracle::GetAnnouncement) -> Option<oracle::Announcement> { |
|||
self.mock.lock().await.get_announcement(msg) |
|||
} |
|||
} |
|||
|
|||
impl xtra::Actor for Oracle {} |
|||
async fn handle(&mut self, msg: oracle::MonitorAttestation) { |
|||
self.mock.lock().await.monitor_attestation(msg) |
|||
} |
|||
|
|||
#[xtra_productivity(message_impl = false)] |
|||
impl Oracle { |
|||
async fn handle_get_announcement( |
|||
&mut self, |
|||
_msg: oracle::GetAnnouncement, |
|||
) -> Option<oracle::Announcement> { |
|||
self.announcement.clone() |
|||
async fn handle(&mut self, msg: oracle::Sync) { |
|||
self.mock.lock().await.sync(msg) |
|||
} |
|||
} |
|||
|
|||
async fn handle(&mut self, _msg: oracle::MonitorAttestation) {} |
|||
#[automock] |
|||
pub trait Oracle { |
|||
fn get_announcement(&mut self, _msg: oracle::GetAnnouncement) -> Option<oracle::Announcement> { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
fn monitor_attestation(&mut self, _msg: oracle::MonitorAttestation) { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
async fn handle(&mut self, _msg: oracle::Sync) {} |
|||
fn sync(&mut self, _msg: oracle::Sync) { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
} |
|||
|
|||
impl Default for Oracle { |
|||
fn default() -> Self { |
|||
Oracle { announcement: None } |
|||
pub fn dummy_announcement() -> oracle::Announcement { |
|||
let announcement = OliviaData::example_0().announcement(); |
|||
|
|||
oracle::Announcement { |
|||
id: BitMexPriceEventId::new(OffsetDateTime::UNIX_EPOCH, 0), |
|||
expected_outcome_time: OffsetDateTime::now_utc(), |
|||
nonce_pks: announcement.nonce_pks, |
|||
} |
|||
} |
|||
|
@ -1,93 +1,81 @@ |
|||
use crate::harness::cfd_protocol::dummy_wallet; |
|||
use anyhow::{anyhow, Result}; |
|||
use anyhow::Result; |
|||
use bdk::bitcoin::util::psbt::PartiallySignedTransaction; |
|||
use bdk::bitcoin::{ecdsa, Amount, Txid}; |
|||
use cfd_protocol::secp256k1_zkp::Secp256k1; |
|||
use cfd_protocol::{PartyParams, WalletExt}; |
|||
use daemon::model::{Timestamp, WalletInfo}; |
|||
use daemon::wallet; |
|||
use daemon::wallet::{self}; |
|||
use mockall::*; |
|||
use rand::thread_rng; |
|||
use std::sync::Arc; |
|||
use tokio::sync::Mutex; |
|||
use xtra_productivity::xtra_productivity; |
|||
|
|||
pub struct Wallet { |
|||
party_params: bool, |
|||
wallet_info: Option<WalletInfo>, |
|||
psbt: Option<PartiallySignedTransaction>, |
|||
txid: Option<Txid>, |
|||
/// Test Stub simulating the Wallet actor.
|
|||
/// Serves as an entrypoint for injected mock handlers.
|
|||
pub struct WalletActor { |
|||
pub mock: Arc<Mutex<dyn Wallet + Send>>, |
|||
} |
|||
|
|||
impl Wallet { |
|||
pub fn with_party_params(mut self) -> Self { |
|||
self.party_params = true; |
|||
self |
|||
} |
|||
impl xtra::Actor for WalletActor {} |
|||
|
|||
pub fn with_wallet_info(mut self) -> Self { |
|||
let s = Secp256k1::new(); |
|||
let public_key = ecdsa::PublicKey::new(s.generate_keypair(&mut thread_rng()).1); |
|||
let address = bdk::bitcoin::Address::p2pkh(&public_key, bdk::bitcoin::Network::Testnet); |
|||
#[xtra_productivity(message_impl = false)] |
|||
impl WalletActor { |
|||
async fn handle(&mut self, msg: wallet::BuildPartyParams) -> Result<PartyParams> { |
|||
self.mock.lock().await.build_party_params(msg) |
|||
} |
|||
async fn handle(&mut self, msg: wallet::Sync) -> Result<WalletInfo> { |
|||
tracing::info!("We are handling the wallet sync msg"); |
|||
self.mock.lock().await.sync(msg) |
|||
} |
|||
async fn handle(&mut self, msg: wallet::Sign) -> Result<PartiallySignedTransaction> { |
|||
self.mock.lock().await.sign(msg) |
|||
} |
|||
async fn handle(&mut self, msg: wallet::TryBroadcastTransaction) -> Result<Txid> { |
|||
self.mock.lock().await.broadcast(msg) |
|||
} |
|||
} |
|||
|
|||
self.wallet_info = Some(WalletInfo { |
|||
balance: bdk::bitcoin::Amount::ONE_BTC, |
|||
address, |
|||
last_updated_at: Timestamp::now().unwrap(), |
|||
}); |
|||
#[automock] |
|||
pub trait Wallet { |
|||
fn build_party_params(&mut self, _msg: wallet::BuildPartyParams) -> Result<PartyParams> { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
self |
|||
fn sign(&mut self, _msg: wallet::Sign) -> Result<PartiallySignedTransaction> { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
pub fn with_psbt(mut self, psbt: PartiallySignedTransaction) -> Self { |
|||
self.psbt = Some(psbt); |
|||
self |
|||
fn broadcast(&mut self, _msg: wallet::TryBroadcastTransaction) -> Result<Txid> { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
|
|||
pub fn with_txid(mut self, txid: Txid) -> Self { |
|||
self.txid = Some(txid); |
|||
self |
|||
fn sync(&mut self, _msg: wallet::Sync) -> Result<WalletInfo> { |
|||
unreachable!("mockall will reimplement this method") |
|||
} |
|||
} |
|||
|
|||
impl xtra::Actor for Wallet {} |
|||
|
|||
#[xtra_productivity(message_impl = false)] |
|||
impl Wallet { |
|||
async fn handle(&mut self, msg: wallet::BuildPartyParams) -> Result<PartyParams> { |
|||
if self.party_params { |
|||
let mut rng = thread_rng(); |
|||
let wallet = dummy_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); |
|||
|
|||
let party_params = wallet |
|||
.build_party_params(msg.amount, msg.identity_pk) |
|||
.unwrap(); |
|||
#[allow(dead_code)] |
|||
/// tells the user they have plenty of moneys
|
|||
fn dummy_wallet_info() -> Result<WalletInfo> { |
|||
let s = Secp256k1::new(); |
|||
let public_key = ecdsa::PublicKey::new(s.generate_keypair(&mut thread_rng()).1); |
|||
let address = bdk::bitcoin::Address::p2pkh(&public_key, bdk::bitcoin::Network::Testnet); |
|||
|
|||
return Ok(party_params); |
|||
} |
|||
|
|||
panic!("Stub not configured to return party params") |
|||
} |
|||
async fn handle(&mut self, _msg: wallet::Sync) -> Result<WalletInfo> { |
|||
self.wallet_info |
|||
.clone() |
|||
.ok_or_else(|| anyhow!("Stub not configured to return WalletInfo")) |
|||
} |
|||
async fn handle(&mut self, _msg: wallet::Sign) -> Result<PartiallySignedTransaction> { |
|||
self.psbt |
|||
.clone() |
|||
.ok_or_else(|| anyhow!("Stub not configured to return PartiallySignedTransaction")) |
|||
} |
|||
async fn handle(&mut self, _msg: wallet::TryBroadcastTransaction) -> Result<Txid> { |
|||
self.txid |
|||
.ok_or_else(|| anyhow!("Stub not configured to return Txid")) |
|||
} |
|||
Ok(WalletInfo { |
|||
balance: bdk::bitcoin::Amount::ONE_BTC, |
|||
address, |
|||
last_updated_at: Timestamp::now()?, |
|||
}) |
|||
} |
|||
|
|||
impl Default for Wallet { |
|||
fn default() -> Self { |
|||
Wallet { |
|||
party_params: false, |
|||
wallet_info: None, |
|||
psbt: None, |
|||
txid: None, |
|||
} |
|||
} |
|||
pub fn build_party_params(msg: wallet::BuildPartyParams) -> Result<PartyParams> { |
|||
let mut rng = thread_rng(); |
|||
let wallet = dummy_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); |
|||
|
|||
let party_params = wallet |
|||
.build_party_params(msg.amount, msg.identity_pk) |
|||
.unwrap(); |
|||
Ok(party_params) |
|||
} |
|||
|
Loading…
Reference in new issue