Skip to content

Commit

Permalink
add integration tests to the cli lib
Browse files Browse the repository at this point in the history
  • Loading branch information
Davidson-Souza committed Jan 18, 2024
1 parent f50ac46 commit 8376604
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 33 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
# run all tests
- uses: actions/checkout@v3
- name: Run tests
run: cargo test --verbose
run: cargo build && cargo test --verbose

linting:

Expand Down Expand Up @@ -54,4 +54,4 @@ jobs:
toolchain: ${{ matrix.rust }}

- name: Cross compile
run: cargo test --verbose
run: cargo build && cargo test --verbose
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/floresta-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ reqwest = { version = "0.11.23", optional = true, features = ["blocking"] }
default = ["with-reqwest"]
with-reqwest = ["reqwest"]

[dev-dependencies]
rand = "0.8.5"
tempfile = "3.9.0"

198 changes: 194 additions & 4 deletions crates/floresta-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub mod rpc_types;
#[cfg(feature = "with-reqwest")]
pub mod reqwest_client;

use std::fmt::Debug;

use bitcoin::block::Header as BlockHeader;
use bitcoin::BlockHash;
use bitcoin::Txid;
Expand Down Expand Up @@ -125,7 +127,9 @@ pub trait JsonRPCClient: Sized {
/// Calls a method on the client
///
/// This should call the appropriated rpc method and return a parsed response or error.
fn call<T: serde::de::DeserializeOwned>(&self, method: &str, params: &[Value]) -> Result<T>;
fn call<T: serde::de::DeserializeOwned>(&self, method: &str, params: &[Value]) -> Result<T>
where
T: for<'a> serde::de::Deserialize<'a> + Debug;
}

impl<T: JsonRPCClient> FlorestaRPC for T {
Expand All @@ -138,7 +142,7 @@ impl<T: JsonRPCClient> FlorestaRPC for T {
}

fn rescan(&self, rescan: u32) -> Result<bool> {
self.call("rescanblockchain", &[Value::Number(Number::from(rescan))])
self.call("rescan", &[Value::Number(Number::from(rescan))])
}

fn get_roots(&self) -> Result<Vec<String>> {
Expand All @@ -157,7 +161,7 @@ impl<T: JsonRPCClient> FlorestaRPC for T {
}

fn get_height(&self) -> Result<u32> {
self.call("getblockcount", &[])
self.call("getheight", &[])
}

fn get_tx_out(&self, tx_id: Txid, outpoint: u32) -> Result<Value> {
Expand Down Expand Up @@ -193,7 +197,7 @@ impl<T: JsonRPCClient> FlorestaRPC for T {
fn load_descriptor(&self, descriptor: String, rescan: Option<u32>) -> Result<()> {
let rescan = rescan.unwrap_or(0);
self.call(
"importdescriptors",
"loaddescriptor",
&[
Value::String(descriptor),
Value::Number(Number::from(rescan)),
Expand All @@ -217,3 +221,189 @@ impl<T: JsonRPCClient> FlorestaRPC for T {
self.call("sendrawtransaction", &[Value::String(tx)])
}
}

#[cfg(test)]
mod tests {
use std::fs;
use std::process::Child;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;

use bitcoin::Txid;

use crate::reqwest_client::ReqwestClient;
use crate::FlorestaRPC;

struct Florestad {
proc: Child,
}

impl Drop for Florestad {
fn drop(&mut self) {
self.proc.kill().unwrap();
}
}

/// A helper function for tests.
///
/// This function will start a florestad process and return a client that can be used to
/// interact with it through RPC. It also returns a handle to the process itself, so that
/// you can poke at the stdin and out for this process. You don't have to kill it though,
/// once the handle goes out of scope, the process will be killed.
///
/// The process created by this method will run in a random datadir and use random ports
/// for both RPC and Electrum. The datadir will be in the current dir, under a `tmp` subdir.
/// If you're at $HOME/floresta it will run on $HOME/floresta/tmp/<random_name>/
fn start_florestad() -> (Florestad, ReqwestClient) {
let here = env!("PWD");
let port = rand::random::<u16>() % 1000 + 18443;

// makes a temporary directory
let test_code = rand::random::<u64>();
let dirname = format!("{here}/tmp/floresta.{test_code}");
fs::DirBuilder::new()
.recursive(true)
.create(dirname.clone())
.unwrap();

let fld = Command::new(format!("{here}/target/debug/florestad"))
.args(["-n", "regtest"])
.args(["run"])
.args(["--data-dir", &dirname])
.args(["--rpc-address", &format!("127.0.0.1:{}", port)])
.args(["--electrum-address", &format!("127.0.0.1:{}", port + 1)])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();

let client = ReqwestClient::new(format!("http://127.0.0.1:{port}"));

let mut retries = 10;

loop {
sleep(Duration::from_secs(1));
retries -= 1;
if retries == 0 {
panic!("florestad didn't start {:?}", fld.stdout);
}
match client.get_blockchain_info() {
Ok(_) => break,
Err(_) => continue,
}
}

(Florestad { proc: fld }, client)
}

#[test]
fn test_rescan() {
let (_proc, client) = start_florestad();

let rescan = client.rescan(0).expect("rpc not working");
assert!(rescan);
}

#[test]
fn test_stop() {
let (mut _proc, client) = start_florestad();

let stop = client.stop().expect("rpc not working");
assert!(stop);
}

#[test]
fn test_get_blockchaininfo() {
let (_proc, client) = start_florestad();

let gbi = client.get_blockchain_info().expect("rpc not working");

assert_eq!(gbi.height, 0);
assert_eq!(gbi.chain, "regtest".to_owned());
assert!(gbi.ibd);
assert_eq!(gbi.leaf_count, 0);
assert_eq!(gbi.root_hashes, Vec::<String>::new());
}

#[test]
fn test_get_roots() {
let (_proc, client) = start_florestad();

let gbi = client.get_blockchain_info().expect("rpc not working");

assert_eq!(gbi.root_hashes, Vec::<String>::new());
}

#[test]
fn test_get_block_hash() {
let (_proc, client) = start_florestad();

let blockhash = client.get_block_hash(0).expect("rpc not working");

assert_eq!(
blockhash,
"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
.parse()
.unwrap()
);
}

#[test]
fn test_get_block_header() {
let (_proc, client) = start_florestad();

let blockhash = client.get_block_hash(0).expect("rpc not working");
let block_header = client.get_block_header(blockhash).expect("rpc not working");

assert_eq!(block_header.block_hash(), blockhash);
}

#[test]
fn test_get_block_filter() {
let (_proc, client) = start_florestad();

let block_filter = client.get_block_filter(0);

// this should err, because there is no filter for genesis block
assert!(block_filter.is_err());
}

#[test]
fn test_load_descriptor() {
let (_proc, client) = start_florestad();

let desc = "
wsh(sortedmulti(1,[54ff5a12/48h/1h/0h/2h]tpubDDw6pwZA3hYxcSN32q7a5ynsKmWr4BbkBNHydHPKkM4BZwUfiK7tQ26h7USm8kA1E2FvCy7f7Er7QXKF8RNptATywydARtzgrxuPDwyYv4x/<0;1>/*,[bcf969c0/48h/1h/0h/2h]tpubDEFdgZdCPgQBTNtGj4h6AehK79Jm4LH54JrYBJjAtHMLEAth7LuY87awx9ZMiCURFzFWhxToRJK6xp39aqeJWrG5nuW3eBnXeMJcvDeDxfp/<0;1>/*))#fuw35j0q";

let res = client
.load_descriptor(desc.to_string(), Some(0))
.unwrap_err();

assert!(matches!(res, super::Error::EmtpyResponse))
}

#[test]
fn test_get_height() {
let (_proc, client) = start_florestad();

let height = client.get_height().unwrap();
assert_eq!(height, 0);
}

#[test]
fn test_send_raw_transaction() {
let (_proc, client) = start_florestad();

let tx = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000".to_string();

let res = client.send_raw_transaction(tx).unwrap();
assert_eq!(
res,
Txid::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
.unwrap()
);
}
}
71 changes: 55 additions & 16 deletions crates/floresta-cli/src/reqwest_client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use std::fmt::Debug;

use serde::Deserialize;
use serde_json::json;

use crate::JsonRPCClient;

#[derive(Debug, Default, Clone)]
pub struct ReqwestClient {
client: reqwest::blocking::Client,
Expand All @@ -16,6 +21,13 @@ pub struct ReqwestConfig {
}

impl ReqwestClient {
pub fn new(url: String) -> Self {
Self {
url,
..Default::default()
}
}

pub fn new_with_config(config: ReqwestConfig) -> Self {
let mut client_builder = reqwest::blocking::Client::builder();

Expand All @@ -42,29 +54,56 @@ impl ReqwestClient {
pub fn rpc_call<Response>(
&self,
method: &str,
params: Vec<serde_json::Value>,
) -> anyhow::Result<Response>
params: &[serde_json::Value],
) -> Result<Response, crate::Error>
where
Response: for<'a> serde::de::Deserialize<'a>,
Response: for<'a> serde::de::Deserialize<'a> + Debug,
{
let mut req = self.client.post(&self.url).body(
json!({
"jsonrpc": "2.0",
"id": 0,
"method": method,
"params": params,
})
.to_string(),
);
let mut req = self
.client
.post(&self.url)
.body(
json!({
"jsonrpc": "2.0",
"id": 0,
"method": method,
"params": params,
})
.to_string(),
)
.header("Content-Type", "application/json");

if let Some((user, pass)) = &self.auth {
req = req.basic_auth(user, Some(pass));
}

let resp = req.send()?;
let resp = resp.text()?;
let resp = req.send();
let resp = resp?.text();

let resp: Response = serde_json::from_str(&resp)?;
let resp = serde_json::from_str::<JsonRpcResponse<Response>>(&resp?)?;

Ok(resp)
match resp.result {
Some(resp) => Ok(resp),
None if resp.error.is_some() => Err(crate::Error::Api(resp.error.unwrap())),
None => Err(crate::Error::EmtpyResponse),
}
}
}

impl JsonRPCClient for ReqwestClient {
fn call<T: for<'a> serde::de::Deserialize<'a> + Debug>(
&self,
method: &str,
params: &[serde_json::Value],
) -> Result<T, crate::Error> {
self.rpc_call(method, params)
}
}

#[derive(Debug, Deserialize)]
pub struct JsonRpcResponse<Res> {
pub jsonrpc: String,
pub id: u64,
pub result: Option<Res>,
pub error: Option<serde_json::Value>,
}
Loading

0 comments on commit 8376604

Please sign in to comment.