ilp-node 0.6.0

Interledger node (sender, connector, receiver bundle)
use futures::Future;
use ilp_node::InterledgerNode;
use reqwest::r#async::Client;
use secrecy::SecretString;
use serde_json::{self, json, Value};
use std::env;
use tokio::runtime::Builder as RuntimeBuilder;
use tokio_retry::{strategy::FibonacciBackoff, Retry};
use tracing::error;
use tracing_subscriber;

mod redis_helpers;
use redis_helpers::*;
mod test_helpers;
use test_helpers::*;

#[test]
fn coincap() {
    install_tracing_subscriber();
    let context = TestContext::new();

    let mut runtime = RuntimeBuilder::new()
        .panic_handler(|err| std::panic::resume_unwind(err))
        .build()
        .unwrap();

    let http_port = get_open_port(Some(3010));

    let node: InterledgerNode = serde_json::from_value(json!({
        "ilp_address": "example.one",
        "default_spsp_account": "one",
        "admin_auth_token": "admin",
        "redis_connection": connection_info_to_string(context.get_client_connection_info()),
        "http_bind_address": format!("127.0.0.1:{}", http_port),
        "settlement_api_bind_address": format!("127.0.0.1:{}", get_open_port(None)),
        "secret_seed": random_secret(),
        "route_broadcast_interval": 200,
        "exchange_rate_poll_interval": 60000,
        "exchange_rate_provider": "CoinCap",
    }))
    .unwrap();
    runtime.spawn(node.serve());

    let get_rates = move || {
        Client::new()
            .get(&format!("http://localhost:{}/rates", http_port))
            .send()
            .map_err(|_| panic!("Error getting rates"))
            .and_then(|mut res| res.json().map_err(|_| panic!("Error getting body")))
            .and_then(|body: Value| {
                if let Value::Object(obj) = body {
                    if obj.is_empty() {
                        error!("Rates are empty");
                        return Err(());
                    }
                    assert_eq!(
                        format!("{}", obj.get("USD").expect("Should have USD rate")).as_str(),
                        "1.0"
                    );
                    assert!(obj.contains_key("EUR"));
                    assert!(obj.contains_key("JPY"));
                    assert!(obj.contains_key("BTC"));
                    assert!(obj.contains_key("ETH"));
                    assert!(obj.contains_key("XRP"));
                } else {
                    panic!("Not an object");
                }

                Ok(())
            })
    };

    runtime
        .block_on(
            delay(1000)
                .map_err(|_| panic!("Something strange happened"))
                .and_then(move |_| {
                    Retry::spawn(FibonacciBackoff::from_millis(1000).take(5), get_rates)
                }),
        )
        .unwrap();
}

// TODO can we disable this with conditional compilation?
#[test]
fn cryptocompare() {
    tracing_subscriber::fmt::try_init().unwrap_or(());
    let context = TestContext::new();

    let api_key = env::var("ILP_TEST_CRYPTOCOMPARE_API_KEY");
    if api_key.is_err() {
        error!("Skipping cryptocompare test. Must configure an API key by setting ILP_TEST_CRYPTOCOMPARE_API_KEY to run this test");
        return;
    }
    let api_key = SecretString::new(api_key.unwrap());

    let mut runtime = RuntimeBuilder::new()
        .panic_handler(|err| std::panic::resume_unwind(err))
        .build()
        .unwrap();

    let http_port = get_open_port(Some(3011));

    let node: InterledgerNode = serde_json::from_value(json!({
        "ilp_address": "example.one",
        "default_spsp_account": "one",
        "admin_auth_token": "admin",
        "redis_connection": connection_info_to_string(context.get_client_connection_info()),
        "http_bind_address": format!("127.0.0.1:{}", http_port),
        "settlement_api_bind_address": format!("127.0.0.1:{}", get_open_port(None)),
        "secret_seed": random_secret(),
        "route_broadcast_interval": 200,
        "exchange_rate_poll_interval": 60000,
        "exchange_rate_provider": {
            "crypto_compare": api_key
        },
        "exchange_rate_spread": 0.0,
    }))
    .unwrap();
    runtime.spawn(node.serve());

    let get_rates = move || {
        Client::new()
            .get(&format!("http://localhost:{}/rates", http_port))
            .send()
            .map_err(|_| panic!("Error getting rates"))
            .and_then(|mut res| res.json().map_err(|_| panic!("Error getting body")))
            .and_then(|body: Value| {
                if let Value::Object(obj) = body {
                    if obj.is_empty() {
                        error!("Rates are empty");
                        return Err(());
                    }
                    assert_eq!(
                        format!("{}", obj.get("USD").expect("Should have USD rate")).as_str(),
                        "1.0"
                    );
                    assert!(obj.contains_key("BTC"));
                    assert!(obj.contains_key("ETH"));
                    assert!(obj.contains_key("XRP"));
                } else {
                    panic!("Not an object");
                }

                Ok(())
            })
    };

    runtime
        .block_on(
            delay(1000)
                .map_err(|_| panic!("Something strange happened"))
                .and_then(move |_| {
                    Retry::spawn(FibonacciBackoff::from_millis(1000).take(5), get_rates)
                }),
        )
        .unwrap();
}