mod redis_helpers;
mod test_helpers;
use base64;
use futures::Future;
use redis_helpers::*;
use reqwest;
use serde_json::{json, Number, Value};
use std::str::FromStr;
use std::{thread, time::Duration};
use test_helpers::*;
use tokio::runtime::Builder as RuntimeBuilder;
use warp::http;
use ilp_node::InterledgerNode;
use interledger::{packet::Address, stream::StreamDelivery};
const NODE_ILP_ADDRESS: &str = "example.node";
const ILP_ADDRESS_1: &str = "example.node.1";
const USERNAME_1: &str = "test_account_1";
const USERNAME_2: &str = "test_account_2";
const ASSET_CODE: &str = "XRP";
const ASSET_SCALE: usize = 9;
const MAX_PACKET_AMOUNT: u64 = 100;
const MIN_BALANCE: i64 = -10000;
const ILP_OVER_HTTP_INCOMING_TOKEN_1: &str = "test_1";
const ILP_OVER_HTTP_INCOMING_TOKEN_2: &str = "test_2";
const ASSET_SCALE_TO_BE: usize = 6;
const SETTLE_THRESHOLD_TO_BE: usize = 1000;
const SETTLE_TO_TO_BE: usize = 10;
const ROUTING_RELATION: &str = "NonRoutingAccount";
const ROUND_TRIP_TIME: u32 = 500;
const SETTLEMENT_ENGINE_URL: &str = "http://localhost:3000/";
#[test]
fn accounts_test() {
install_tracing_subscriber();
let mut runtime = RuntimeBuilder::new().build().unwrap();
let redis_test_context = TestContext::new();
let node_http_port = get_open_port(Some(7770));
let node_settlement_port = get_open_port(Some(3000));
let mut connection_info = redis_test_context.get_client_connection_info();
connection_info.db = 1;
let node: InterledgerNode = serde_json::from_value(json!({
"ilp_address": NODE_ILP_ADDRESS,
"default_spsp_account": USERNAME_1,
"admin_auth_token": "admin",
"redis_connection": connection_info_to_string(connection_info),
"http_bind_address": format!("127.0.0.1:{}", node_http_port),
"settlement_api_bind_address": format!("127.0.0.1:{}", node_settlement_port),
"secret_seed": random_secret(),
"route_broadcast_interval": 200,
"exchange_rate_poll_interval": 60000,
"exchange_rate_poll_failure_tolerance": 5,
}))
.unwrap();
let node_to_serve = node.clone();
let node_context = move |_| Ok(node);
let wait_a_sec = |node| {
thread::sleep(Duration::from_millis(1000));
Ok(node)
};
let get_accounts = move |node: InterledgerNode| {
println!("Testing: GET /accounts");
let client = reqwest::r#async::Client::new();
client
.get(&format!(
"http://localhost:{}/accounts",
node.http_bind_address.port()
))
.header(
"Authorization",
&format!("Bearer {}", node.admin_auth_token),
)
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
assert_eq!(json, Value::Array(vec![]));
Ok(node)
})
};
let post_accounts_1 = move |node: InterledgerNode| {
println!("Testing: POST /accounts");
let client = reqwest::r#async::Client::new();
client
.post(&format!(
"http://localhost:{}/accounts",
node.http_bind_address.port()
))
.header(
"Authorization",
&format!("Bearer {}", node.admin_auth_token),
)
.json(&json!({
"ilp_address": ILP_ADDRESS_1,
"username": USERNAME_1,
"asset_code": ASSET_CODE,
"asset_scale": ASSET_SCALE,
"max_packet_amount": MAX_PACKET_AMOUNT,
"min_balance": MIN_BALANCE,
"ilp_over_http_incoming_token": ILP_OVER_HTTP_INCOMING_TOKEN_1,
"routing_relation": ROUTING_RELATION,
"round_trip_time": ROUND_TRIP_TIME,
"settlement_engine_url": SETTLEMENT_ENGINE_URL,
}))
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account
.get("ilp_address")
.expect("ilp_address was expected"),
&Value::String(ILP_ADDRESS_1.to_owned())
);
assert_eq!(
account.get("username").expect("username was expected"),
&Value::String(USERNAME_1.to_owned())
);
assert_eq!(
account.get("asset_code").expect("asset_code was expected"),
&Value::String(ASSET_CODE.to_owned())
);
assert_eq!(
account
.get("asset_scale")
.expect("asset_scale was expected"),
&Value::Number(Number::from(ASSET_SCALE))
);
assert_eq!(
account
.get("max_packet_amount")
.expect("max_packet_amount was expected"),
&Value::Number(Number::from(MAX_PACKET_AMOUNT))
);
assert_eq!(
account
.get("min_balance")
.expect("min_balance was expected"),
&Value::Number(Number::from(MIN_BALANCE))
);
assert_eq!(
account
.get("routing_relation")
.expect("routing_relation was expected"),
&Value::String(ROUTING_RELATION.to_owned())
);
assert_eq!(
account
.get("round_trip_time")
.expect("round_trip_time was expected"),
&Value::Number(Number::from(ROUND_TRIP_TIME))
);
assert_eq!(
account
.get("settlement_engine_url")
.expect("settlement_engine_url was expected"),
&Value::String(SETTLEMENT_ENGINE_URL.to_owned())
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let post_accounts_2 = move |node: InterledgerNode| {
println!("Testing: POST /accounts");
let client = reqwest::r#async::Client::new();
client
.post(&format!(
"http://localhost:{}/accounts",
node.http_bind_address.port()
))
.header(
"Authorization",
&format!("Bearer {}", node.admin_auth_token),
)
.json(&json!({
"username": USERNAME_2,
"asset_code": ASSET_CODE,
"asset_scale": 12,
"max_packet_amount": MAX_PACKET_AMOUNT,
"min_balance": MIN_BALANCE,
"ilp_over_http_incoming_token": ILP_OVER_HTTP_INCOMING_TOKEN_2,
"routing_relation": ROUTING_RELATION,
"round_trip_time": ROUND_TRIP_TIME,
"settlement_engine_url": SETTLEMENT_ENGINE_URL,
}))
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account.get("username").expect("username was expected"),
&Value::String(USERNAME_2.to_owned())
);
assert_eq!(
account.get("asset_code").expect("asset_code was expected"),
&Value::String(ASSET_CODE.to_owned())
);
assert_eq!(
account
.get("asset_scale")
.expect("asset_scale was expected"),
12
);
assert_eq!(
account
.get("max_packet_amount")
.expect("max_packet_amount was expected"),
&Value::Number(Number::from(MAX_PACKET_AMOUNT))
);
assert_eq!(
account
.get("min_balance")
.expect("min_balance was expected"),
&Value::Number(Number::from(MIN_BALANCE))
);
assert_eq!(
account
.get("routing_relation")
.expect("routing_relation was expected"),
&Value::String(ROUTING_RELATION.to_owned())
);
assert_eq!(
account
.get("round_trip_time")
.expect("round_trip_time was expected"),
&Value::Number(Number::from(ROUND_TRIP_TIME))
);
assert_eq!(
account
.get("settlement_engine_url")
.expect("settlement_engine_url was expected"),
&Value::String(SETTLEMENT_ENGINE_URL.to_owned())
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let put_accounts_username = move |node: InterledgerNode| {
println!("Testing: PUT /accounts/:username");
let client = reqwest::r#async::Client::new();
client
.put(&format!(
"http://localhost:{}/accounts/{}",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}", node.admin_auth_token),
)
.json(&json!({
"ilp_address": ILP_ADDRESS_1,
"username": USERNAME_1,
"asset_code": ASSET_CODE,
"asset_scale": ASSET_SCALE_TO_BE
}))
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account
.get("ilp_address")
.expect("ilp_address was expected"),
&Value::String(ILP_ADDRESS_1.to_owned())
);
assert_eq!(
account.get("username").expect("username was expected"),
&Value::String(USERNAME_1.to_owned())
);
assert_eq!(
account.get("asset_code").expect("asset_code was expected"),
&Value::String(ASSET_CODE.to_owned())
);
assert_eq!(
account
.get("asset_scale")
.expect("asset_scale was expected"),
&Value::Number(Number::from(ASSET_SCALE_TO_BE))
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let get_accounts_username = move |node: InterledgerNode| {
println!("Testing: GET /accounts/:username");
let client = reqwest::r#async::Client::new();
client
.get(&format!(
"http://localhost:{}/accounts/{}",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}:{}", USERNAME_1, ILP_OVER_HTTP_INCOMING_TOKEN_1),
)
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account
.get("ilp_address")
.expect("ilp_address was expected"),
&Value::String(ILP_ADDRESS_1.to_owned())
);
assert_eq!(
account.get("username").expect("username was expected"),
&Value::String(USERNAME_1.to_owned())
);
assert_eq!(
account.get("asset_code").expect("asset_code was expected"),
&Value::String(ASSET_CODE.to_owned())
);
assert_eq!(
account
.get("asset_scale")
.expect("asset_scale was expected"),
&Value::Number(Number::from(ASSET_SCALE_TO_BE))
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let get_accounts_username_balance = move |node: InterledgerNode| {
println!("Testing: GET /accounts/:username/balance");
let client = reqwest::r#async::Client::new();
client
.get(&format!(
"http://localhost:{}/accounts/{}/balance",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}:{}", USERNAME_1, ILP_OVER_HTTP_INCOMING_TOKEN_1),
)
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(balance) = json {
assert_eq!(
balance.get("balance").expect("balance was expected"),
&Value::Number(Number::from(0))
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let put_accounts_username_settings = move |node: InterledgerNode| {
println!("Testing: PUT /accounts/:username/settings");
let client = reqwest::r#async::Client::new();
client
.put(&format!(
"http://localhost:{}/accounts/{}/settings",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}:{}", USERNAME_1, ILP_OVER_HTTP_INCOMING_TOKEN_1),
)
.json(&json!({
"settle_threshold": SETTLE_THRESHOLD_TO_BE,
"settle_to": SETTLE_TO_TO_BE,
}))
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account
.get("settle_threshold")
.expect("settle_threshold was expected"),
&Value::Number(Number::from(SETTLE_THRESHOLD_TO_BE))
);
assert_eq!(
account.get("settle_to").expect("settle_to was expected"),
&Value::Number(Number::from(SETTLE_TO_TO_BE))
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let delete_accounts_username = move |node: InterledgerNode| {
println!("Testing: DELETE /accounts/:username");
let client = reqwest::r#async::Client::new();
client
.delete(&format!(
"http://localhost:{}/accounts/{}",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}", node.admin_auth_token),
)
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account.get("username").expect("username was expected"),
&Value::String(USERNAME_1.to_owned())
);
assert_eq!(
account.get("asset_code").expect("asset_code was expected"),
&Value::String(ASSET_CODE.to_owned())
);
assert_eq!(
account
.get("asset_scale")
.expect("asset_scale was expected"),
&Value::Number(Number::from(ASSET_SCALE_TO_BE))
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let post_accounts_username_payments = move |node: InterledgerNode| {
println!("Testing: POST /accounts/:username/payments");
let amount = 100;
let client = reqwest::r#async::Client::new();
client
.post(&format!(
"http://localhost:{}/accounts/{}/payments",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}:{}", USERNAME_1, ILP_OVER_HTTP_INCOMING_TOKEN_1),
)
.json(&json!({
"receiver": &format!("http://localhost:{}/accounts/{}/spsp", node.http_bind_address.port(), USERNAME_2),
"source_amount": amount,
}))
.send()
.map_err(|err| panic!(err))
.and_then(move|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let receipt: StreamDelivery = serde_json::from_str(&content).unwrap();
assert_eq!(receipt.from, Address::from_str(ILP_ADDRESS_1).unwrap());
assert!(receipt.to.to_string().starts_with(&format!("{}.{}", NODE_ILP_ADDRESS, USERNAME_2)));
assert_eq!(receipt.sent_amount, amount);
assert_eq!(receipt.sent_asset_scale as usize, ASSET_SCALE);
assert_eq!(receipt.sent_asset_code, ASSET_CODE);
assert_eq!(receipt.delivered_amount, amount * 1000);
assert_eq!(receipt.delivered_asset_scale.unwrap() as usize, 12);
assert_eq!(receipt.delivered_asset_code.unwrap(), ASSET_CODE);
Ok(node)
})
};
let get_accounts_username_spsp = move |node: InterledgerNode| {
println!("Testing: GET /accounts/:username/spsp");
let client = reqwest::r#async::Client::new();
client
.get(&format!(
"http://localhost:{}/accounts/{}/spsp",
node.http_bind_address.port(),
USERNAME_1
))
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(spsp) = json {
if let Value::String(account) = spsp
.get("destination_account")
.expect("destination_account was expected")
{
assert!(
account.starts_with(ILP_ADDRESS_1),
"destination_account doesn't start with address of {}",
USERNAME_1
);
} else {
panic!("Invalid response JSON! {}", &content);
}
if let Value::String(shared_secret) = spsp
.get("shared_secret")
.expect("shared_secret was expected")
{
let shared_secret_bytes = base64::decode(&shared_secret)
.expect("shared_secret is expected to be BASE64.");
assert_eq!(
shared_secret_bytes.len(),
32,
"shared_secret was not 32bytes!"
);
} else {
panic!("Invalid response JSON! {}", &content);
}
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let get_well_known_pay = move |node: InterledgerNode| {
println!("Testing: GET /.well-known/pay");
let client = reqwest::r#async::Client::new();
client
.get(&format!(
"http://localhost:{}/.well-known/pay",
node.http_bind_address.port()
))
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(spsp) = json {
if let Value::String(account) = spsp
.get("destination_account")
.expect("destination_account was expected")
{
assert!(
account.starts_with(ILP_ADDRESS_1),
"destination_account doesn't start with address of {}",
USERNAME_1
);
} else {
panic!("Invalid response JSON! {}", &content);
}
if let Value::String(shared_secret) = spsp
.get("shared_secret")
.expect("shared_secret was expected")
{
let shared_secret_bytes = base64::decode(&shared_secret)
.expect("shared_secret is expected to be BASE64.");
assert_eq!(
shared_secret_bytes.len(),
32,
"shared_secret was not 32bytes!"
);
} else {
panic!("Invalid response JSON! {}", &content);
}
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let get_accounts_username_by_admin = move |node: InterledgerNode| {
println!("Testing: GET /accounts/:username [ADMIN]");
let client = reqwest::r#async::Client::new();
client
.get(&format!(
"http://localhost:{}/accounts/{}",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}", node.admin_auth_token),
)
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
assert!(res.error_for_status_ref().is_ok(), "{}", &content);
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account
.get("ilp_address")
.expect("ilp_address was expected"),
&Value::String(ILP_ADDRESS_1.to_owned())
);
assert_eq!(
account.get("username").expect("username was expected"),
&Value::String(USERNAME_1.to_owned())
);
assert_eq!(
account.get("asset_code").expect("asset_code was expected"),
&Value::String(ASSET_CODE.to_owned())
);
assert_eq!(
account
.get("asset_scale")
.expect("asset_scale was expected"),
&Value::Number(Number::from(ASSET_SCALE))
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
let get_accounts_username_unauthorized = move |node: InterledgerNode| {
println!("Testing: GET /accounts/:username");
let client = reqwest::r#async::Client::new();
client
.get(&format!(
"http://localhost:{}/accounts/{}",
node.http_bind_address.port(),
USERNAME_1
))
.header(
"Authorization",
&format!("Bearer {}:{}", USERNAME_2, ILP_OVER_HTTP_INCOMING_TOKEN_2),
)
.send()
.map_err(|err| panic!(err))
.and_then(|mut res| {
let content = res.text().wait().expect("Error getting response!");
let json: Value = serde_json::from_str(&content)
.unwrap_or_else(|_| panic!("Could not parse JSON! JSON: {}", &content));
if let Value::Object(account) = json {
assert_eq!(
account.get("status").expect("status was expected"),
&Value::Number(Number::from(http::StatusCode::UNAUTHORIZED.as_u16()))
);
} else {
panic!("Invalid response JSON! {}", &content);
}
Ok(node)
})
};
runtime
.block_on(
node_to_serve
.serve()
.and_then(node_context)
.and_then(get_accounts)
.and_then(post_accounts_1)
.and_then(put_accounts_username)
.and_then(get_accounts_username)
.and_then(get_accounts_username_balance)
.and_then(put_accounts_username_settings)
.and_then(delete_accounts_username)
.and_then(post_accounts_1)
.and_then(post_accounts_2)
.and_then(wait_a_sec) .and_then(post_accounts_username_payments)
.and_then(get_accounts_username_spsp)
.and_then(get_well_known_pay)
.and_then(get_accounts_username_by_admin)
.and_then(get_accounts_username_unauthorized),
)
.expect("Could not spin up node and tests.");
}