1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
use crate::{
    client_error::ClientError,
    generic_rpc_client_request::GenericRpcClientRequest,
    rpc_request::RpcRequest,
    rpc_response::{Response, RpcResponseContext},
};
use serde_json::{Number, Value};
use solana_sdk::{
    fee_calculator::FeeCalculator,
    instruction::InstructionError,
    transaction::{self, TransactionError},
};
use std::{collections::HashMap, sync::RwLock};

pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
pub const SIGNATURE: &str =
    "43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8";

pub type Mocks = HashMap<RpcRequest, Value>;
pub struct MockRpcClientRequest {
    mocks: RwLock<Mocks>,
    url: String,
}

impl MockRpcClientRequest {
    pub fn new(url: String) -> Self {
        Self::new_with_mocks(url, Mocks::default())
    }

    pub fn new_with_mocks(url: String, mocks: Mocks) -> Self {
        Self {
            url,
            mocks: RwLock::new(mocks),
        }
    }
}

impl GenericRpcClientRequest for MockRpcClientRequest {
    fn send(
        &self,
        request: &RpcRequest,
        params: serde_json::Value,
        _retries: usize,
    ) -> Result<serde_json::Value, ClientError> {
        if let Some(value) = self.mocks.write().unwrap().remove(request) {
            return Ok(value);
        }
        if self.url == "fails" {
            return Ok(Value::Null);
        }
        let val = match request {
            RpcRequest::ConfirmTransaction => {
                if let Some(params_array) = params.as_array() {
                    if let Value::String(param_string) = &params_array[0] {
                        Value::Bool(param_string == SIGNATURE)
                    } else {
                        Value::Null
                    }
                } else {
                    Value::Null
                }
            }
            RpcRequest::GetBalance => {
                let n = if self.url == "airdrop" { 0 } else { 50 };
                serde_json::to_value(Response {
                    context: RpcResponseContext { slot: 1 },
                    value: Value::Number(Number::from(n)),
                })?
            }
            RpcRequest::GetRecentBlockhash => serde_json::to_value(Response {
                context: RpcResponseContext { slot: 1 },
                value: (
                    Value::String(PUBKEY.to_string()),
                    serde_json::to_value(FeeCalculator::default()).unwrap(),
                ),
            })?,
            RpcRequest::GetSignatureStatus => {
                let response: Option<transaction::Result<()>> = if self.url == "account_in_use" {
                    Some(Err(TransactionError::AccountInUse))
                } else if self.url == "instruction_error" {
                    Some(Err(TransactionError::InstructionError(
                        0,
                        InstructionError::UninitializedAccount,
                    )))
                } else if self.url == "sig_not_found" {
                    None
                } else {
                    Some(Ok(()))
                };
                serde_json::to_value(response).unwrap()
            }
            RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
            RpcRequest::GetSlot => Value::Number(Number::from(0)),
            RpcRequest::SendTransaction => Value::String(SIGNATURE.to_string()),
            RpcRequest::GetMinimumBalanceForRentExemption => Value::Number(Number::from(1234)),
            _ => Value::Null,
        };
        Ok(val)
    }
}