mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-01-20 19:11:09 +03:00
Test refactoring and additions to support API Testing (#205)
* move wallet tests into integration module in advance of further test creation, add v3 API * rustfmt * fix return value from incorrect mask supplied * rustfmt * rework send request call some * rustfmt * more refactoring, turn proxy startup + wallet init into macros * rustfmt * move sample requests into subdirectories * add missing wallet files, change gitignore
This commit is contained in:
parent
f8c316a351
commit
62d976f9ef
16 changed files with 1011 additions and 671 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,5 +10,4 @@ target
|
||||||
grin.log
|
grin.log
|
||||||
wallet.seed
|
wallet.seed
|
||||||
test_output
|
test_output
|
||||||
wallet*
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -765,6 +765,7 @@ dependencies = [
|
||||||
"built 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"built 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"easy-jsonrpc 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grin_wallet_api 2.1.0-beta.1",
|
"grin_wallet_api 2.1.0-beta.1",
|
||||||
|
@ -778,6 +779,10 @@ dependencies = [
|
||||||
"prettytable-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"prettytable-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_derive 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -40,3 +40,10 @@ grin_wallet_util = { path = "./util", version = "2.1.0-beta.1" }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = "0.3"
|
built = "0.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
url = "1.7.2"
|
||||||
|
serde = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
serde_json = "1"
|
||||||
|
easy-jsonrpc = "0.5.1"
|
||||||
|
|
|
@ -32,7 +32,7 @@ use serde_json;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::apiwallet::{Foreign, ForeignCheckMiddlewareFn, ForeignRpc, Owner, OwnerRpc};
|
use crate::apiwallet::{Foreign, ForeignCheckMiddlewareFn, ForeignRpc, Owner, OwnerRpc, OwnerRpcS};
|
||||||
use easy_jsonrpc;
|
use easy_jsonrpc;
|
||||||
use easy_jsonrpc::{Handler, MaybeReply};
|
use easy_jsonrpc::{Handler, MaybeReply};
|
||||||
|
|
||||||
|
@ -126,8 +126,6 @@ where
|
||||||
C: NodeClient + 'static,
|
C: NodeClient + 'static,
|
||||||
K: Keychain + 'static,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
let api_handler_v2 = OwnerAPIHandlerV2::new(wallet.clone());
|
|
||||||
|
|
||||||
let mut router = Router::new();
|
let mut router = Router::new();
|
||||||
if api_secret.is_some() {
|
if api_secret.is_some() {
|
||||||
let api_basic_auth =
|
let api_basic_auth =
|
||||||
|
@ -139,10 +137,18 @@ where
|
||||||
router.add_middleware(basic_auth_middleware);
|
router.add_middleware(basic_auth_middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let api_handler_v2 = OwnerAPIHandlerV2::new(wallet.clone());
|
||||||
|
|
||||||
|
let api_handler_v3 = OwnerAPIHandlerV3::new(wallet.clone());
|
||||||
|
|
||||||
router
|
router
|
||||||
.add_route("/v2/owner", Arc::new(api_handler_v2))
|
.add_route("/v2/owner", Arc::new(api_handler_v2))
|
||||||
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||||
|
|
||||||
|
router
|
||||||
|
.add_route("/v3/owner", Arc::new(api_handler_v3))
|
||||||
|
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||||
|
|
||||||
// If so configured, add the foreign API to the same port
|
// If so configured, add the foreign API to the same port
|
||||||
if owner_api_include_foreign.unwrap_or(false) {
|
if owner_api_include_foreign.unwrap_or(false) {
|
||||||
warn!("Starting HTTP Foreign API on Owner server at {}.", addr);
|
warn!("Starting HTTP Foreign API on Owner server at {}.", addr);
|
||||||
|
@ -277,6 +283,79 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// V3 API Handler/Wrapper for owner functions, which include a secure
|
||||||
|
/// mode + lifecycle functions
|
||||||
|
pub struct OwnerAPIHandlerV3<L, C, K>
|
||||||
|
where
|
||||||
|
L: WalletLCProvider<'static, C, K> + 'static,
|
||||||
|
C: NodeClient + 'static,
|
||||||
|
K: Keychain + 'static,
|
||||||
|
{
|
||||||
|
/// Wallet instance
|
||||||
|
pub wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, C, K> OwnerAPIHandlerV3<L, C, K>
|
||||||
|
where
|
||||||
|
L: WalletLCProvider<'static, C, K>,
|
||||||
|
C: NodeClient + 'static,
|
||||||
|
K: Keychain + 'static,
|
||||||
|
{
|
||||||
|
/// Create a new owner API handler for GET methods
|
||||||
|
pub fn new(
|
||||||
|
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
|
||||||
|
) -> OwnerAPIHandlerV3<L, C, K> {
|
||||||
|
OwnerAPIHandlerV3 { wallet }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_api(
|
||||||
|
&self,
|
||||||
|
req: Request<Body>,
|
||||||
|
api: Owner<'static, L, C, K>,
|
||||||
|
) -> Box<dyn Future<Item = serde_json::Value, Error = Error> + Send> {
|
||||||
|
Box::new(parse_body(req).and_then(move |val: serde_json::Value| {
|
||||||
|
let owner_api_s = &api as &dyn OwnerRpcS;
|
||||||
|
match owner_api_s.handle_request(val) {
|
||||||
|
MaybeReply::Reply(r) => ok(r),
|
||||||
|
MaybeReply::DontReply => {
|
||||||
|
// Since it's http, we need to return something. We return [] because jsonrpc
|
||||||
|
// clients will parse it as an empty batch response.
|
||||||
|
ok(serde_json::json!([]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
||||||
|
let api = Owner::new(self.wallet.clone());
|
||||||
|
Box::new(
|
||||||
|
self.call_api(req, api)
|
||||||
|
.and_then(|resp| ok(json_response_pretty(&resp))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, C, K> api::Handler for OwnerAPIHandlerV3<L, C, K>
|
||||||
|
where
|
||||||
|
L: WalletLCProvider<'static, C, K> + 'static,
|
||||||
|
C: NodeClient + 'static,
|
||||||
|
K: Keychain + 'static,
|
||||||
|
{
|
||||||
|
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
|
Box::new(
|
||||||
|
self.handle_post_request(req)
|
||||||
|
.and_then(|r| ok(r))
|
||||||
|
.or_else(|e| {
|
||||||
|
error!("Request Error: {:?}", e);
|
||||||
|
ok(create_error_response(e))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn options(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
Box::new(ok(create_ok_response("{}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
/// V2 API Handler/Wrapper for foreign functions
|
/// V2 API Handler/Wrapper for foreign functions
|
||||||
pub struct ForeignAPIHandlerV2<L, C, K>
|
pub struct ForeignAPIHandlerV2<L, C, K>
|
||||||
where
|
where
|
||||||
|
|
|
@ -84,7 +84,7 @@ where
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = update_outputs(w, keychain_mask, false);
|
validated = update_outputs(w, keychain_mask, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -116,7 +116,7 @@ where
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = update_outputs(w, keychain_mask, false);
|
validated = update_outputs(w, keychain_mask, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -141,7 +141,7 @@ where
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = update_outputs(w, keychain_mask, false);
|
validated = update_outputs(w, keychain_mask, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let wallet_info = updater::retrieve_info(&mut *w, &parent_key_id, minimum_confirmations)?;
|
let wallet_info = updater::retrieve_info(&mut *w, &parent_key_id, minimum_confirmations)?;
|
||||||
|
@ -418,7 +418,7 @@ where
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
let parent_key_id = w.parent_key_id();
|
let parent_key_id = w.parent_key_id();
|
||||||
if !update_outputs(w, keychain_mask, false) {
|
if !update_outputs(w, keychain_mask, false)? {
|
||||||
return Err(ErrorKind::TransactionCancellationError(
|
return Err(ErrorKind::TransactionCancellationError(
|
||||||
"Can't contact running Grin node. Not Cancelling.",
|
"Can't contact running Grin node. Not Cancelling.",
|
||||||
))?;
|
))?;
|
||||||
|
@ -489,7 +489,7 @@ where
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
update_outputs(w, keychain_mask, true);
|
update_outputs(w, keychain_mask, true)?;
|
||||||
w.check_repair(keychain_mask, delete_unconfirmed)
|
w.check_repair(keychain_mask, delete_unconfirmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,7 +528,7 @@ fn update_outputs<'a, T: ?Sized, C, K>(
|
||||||
w: &mut T,
|
w: &mut T,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
update_all: bool,
|
update_all: bool,
|
||||||
) -> bool
|
) -> Result<bool, Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<'a, C, K>,
|
T: WalletBackend<'a, C, K>,
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
|
@ -536,7 +536,12 @@ where
|
||||||
{
|
{
|
||||||
let parent_key_id = w.parent_key_id();
|
let parent_key_id = w.parent_key_id();
|
||||||
match updater::refresh_outputs(&mut *w, keychain_mask, &parent_key_id, update_all) {
|
match updater::refresh_outputs(&mut *w, keychain_mask, &parent_key_id, update_all) {
|
||||||
Ok(_) => true,
|
Ok(_) => Ok(true),
|
||||||
Err(_) => false,
|
Err(e) => {
|
||||||
|
if let ErrorKind::InvalidKeychainMask = e.kind() {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,651 +0,0 @@
|
||||||
// Copyright 2018 The Grin Developers
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//! Test wallet command line works as expected
|
|
||||||
#[cfg(test)]
|
|
||||||
mod wallet_tests {
|
|
||||||
use clap;
|
|
||||||
use grin_wallet_util::grin_util as util;
|
|
||||||
|
|
||||||
use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy};
|
|
||||||
|
|
||||||
use clap::{App, ArgMatches};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::{env, fs};
|
|
||||||
use util::{Mutex, ZeroingString};
|
|
||||||
|
|
||||||
use grin_wallet_config::{GlobalWalletConfig, WalletConfig, GRIN_WALLET_DIR};
|
|
||||||
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl};
|
|
||||||
use grin_wallet_libwallet::WalletInst;
|
|
||||||
use grin_wallet_util::grin_core::global::{self, ChainTypes};
|
|
||||||
use grin_wallet_util::grin_keychain::ExtKeychain;
|
|
||||||
use util::secp::key::SecretKey;
|
|
||||||
|
|
||||||
use super::super::wallet_args;
|
|
||||||
|
|
||||||
fn clean_output_dir(test_dir: &str) {
|
|
||||||
let _ = fs::remove_dir_all(test_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup(test_dir: &str) {
|
|
||||||
util::init_test_logger();
|
|
||||||
clean_output_dir(test_dir);
|
|
||||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a wallet config file in the given current directory
|
|
||||||
pub fn config_command_wallet(
|
|
||||||
dir_name: &str,
|
|
||||||
wallet_name: &str,
|
|
||||||
) -> Result<(), grin_wallet_controller::Error> {
|
|
||||||
let mut current_dir;
|
|
||||||
let mut default_config = GlobalWalletConfig::default();
|
|
||||||
current_dir = env::current_dir().unwrap_or_else(|e| {
|
|
||||||
panic!("Error creating config file: {}", e);
|
|
||||||
});
|
|
||||||
current_dir.push(dir_name);
|
|
||||||
current_dir.push(wallet_name);
|
|
||||||
let _ = fs::create_dir_all(current_dir.clone());
|
|
||||||
let mut config_file_name = current_dir.clone();
|
|
||||||
config_file_name.push("grin-wallet.toml");
|
|
||||||
if config_file_name.exists() {
|
|
||||||
return Err(grin_wallet_controller::ErrorKind::ArgumentError(
|
|
||||||
"grin-wallet.toml already exists in the target directory. Please remove it first"
|
|
||||||
.to_owned(),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
default_config.update_paths(¤t_dir);
|
|
||||||
default_config
|
|
||||||
.write_to_file(config_file_name.to_str().unwrap())
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
panic!("Error creating config file: {}", e);
|
|
||||||
});
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"File {} configured and created",
|
|
||||||
config_file_name.to_str().unwrap(),
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles setup and detection of paths for wallet
|
|
||||||
pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> WalletConfig {
|
|
||||||
let mut current_dir;
|
|
||||||
current_dir = env::current_dir().unwrap_or_else(|e| {
|
|
||||||
panic!("Error creating config file: {}", e);
|
|
||||||
});
|
|
||||||
current_dir.push(dir_name);
|
|
||||||
current_dir.push(wallet_name);
|
|
||||||
let _ = fs::create_dir_all(current_dir.clone());
|
|
||||||
let mut config_file_name = current_dir.clone();
|
|
||||||
config_file_name.push("grin-wallet.toml");
|
|
||||||
GlobalWalletConfig::new(config_file_name.to_str().unwrap())
|
|
||||||
.unwrap()
|
|
||||||
.members
|
|
||||||
.unwrap()
|
|
||||||
.wallet
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_wallet_subcommand<'a>(
|
|
||||||
wallet_dir: &str,
|
|
||||||
wallet_name: &str,
|
|
||||||
args: ArgMatches<'a>,
|
|
||||||
) -> ArgMatches<'a> {
|
|
||||||
match args.subcommand() {
|
|
||||||
("init", Some(init_args)) => {
|
|
||||||
// wallet init command should spit out its config file then continue
|
|
||||||
// (if desired)
|
|
||||||
if init_args.is_present("here") {
|
|
||||||
let _ = config_command_wallet(wallet_dir, wallet_name);
|
|
||||||
}
|
|
||||||
init_args.to_owned()
|
|
||||||
}
|
|
||||||
_ => ArgMatches::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// Helper to create an instance of the LMDB wallet
|
|
||||||
fn instantiate_wallet(
|
|
||||||
mut wallet_config: WalletConfig,
|
|
||||||
node_client: LocalWalletClient,
|
|
||||||
passphrase: &str,
|
|
||||||
account: &str,
|
|
||||||
) -> Result<
|
|
||||||
(
|
|
||||||
Arc<
|
|
||||||
Mutex<
|
|
||||||
Box<
|
|
||||||
WalletInst<
|
|
||||||
'static,
|
|
||||||
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
|
||||||
LocalWalletClient,
|
|
||||||
ExtKeychain,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
Option<SecretKey>,
|
|
||||||
),
|
|
||||||
grin_wallet_controller::Error,
|
|
||||||
> {
|
|
||||||
wallet_config.chain_type = None;
|
|
||||||
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(node_client).unwrap())
|
|
||||||
as Box<
|
|
||||||
WalletInst<
|
|
||||||
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
|
||||||
LocalWalletClient,
|
|
||||||
ExtKeychain,
|
|
||||||
>,
|
|
||||||
>;
|
|
||||||
let lc = wallet.lc_provider().unwrap();
|
|
||||||
// legacy hack to avoid the need for changes in existing grin-wallet.toml files
|
|
||||||
// remove `wallet_data` from end of path as
|
|
||||||
// new lifecycle provider assumes grin_wallet.toml is in root of data directory
|
|
||||||
let mut top_level_wallet_dir = PathBuf::from(wallet_config.clone().data_file_dir);
|
|
||||||
if top_level_wallet_dir.ends_with(GRIN_WALLET_DIR) {
|
|
||||||
top_level_wallet_dir.pop();
|
|
||||||
wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into();
|
|
||||||
}
|
|
||||||
lc.set_wallet_directory(&wallet_config.data_file_dir);
|
|
||||||
let keychain_mask = lc
|
|
||||||
.open_wallet(None, ZeroingString::from(passphrase), true, false)
|
|
||||||
.unwrap();
|
|
||||||
let wallet_inst = lc.wallet_inst()?;
|
|
||||||
wallet_inst.set_parent_key_id_by_name(account)?;
|
|
||||||
Ok((Arc::new(Mutex::new(wallet)), keychain_mask))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_command(
|
|
||||||
app: &App,
|
|
||||||
test_dir: &str,
|
|
||||||
wallet_name: &str,
|
|
||||||
client: &LocalWalletClient,
|
|
||||||
arg_vec: Vec<&str>,
|
|
||||||
) -> Result<String, grin_wallet_controller::Error> {
|
|
||||||
let args = app.clone().get_matches_from(arg_vec);
|
|
||||||
let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone());
|
|
||||||
let mut config = initial_setup_wallet(test_dir, wallet_name);
|
|
||||||
//unset chain type so it doesn't get reset
|
|
||||||
config.chain_type = None;
|
|
||||||
wallet_args::wallet_command(&args, config.clone(), client.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// command line tests
|
|
||||||
fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::Error> {
|
|
||||||
setup(test_dir);
|
|
||||||
// Create a new proxy to simulate server and wallet responses
|
|
||||||
let mut wallet_proxy: WalletProxy<
|
|
||||||
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
|
|
||||||
LocalWalletClient,
|
|
||||||
ExtKeychain,
|
|
||||||
> = WalletProxy::new(test_dir);
|
|
||||||
let chain = wallet_proxy.chain.clone();
|
|
||||||
|
|
||||||
// load app yaml. If it don't exist, just say so and exit
|
|
||||||
let yml = load_yaml!("../grin-wallet.yml");
|
|
||||||
let app = App::from_yaml(yml);
|
|
||||||
|
|
||||||
// wallet init
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "init", "-h"];
|
|
||||||
// should create new wallet file
|
|
||||||
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?;
|
|
||||||
|
|
||||||
// trying to init twice - should fail
|
|
||||||
assert!(execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone()).is_err());
|
|
||||||
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
|
||||||
|
|
||||||
// add wallet to proxy
|
|
||||||
//let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
|
|
||||||
let config1 = initial_setup_wallet(test_dir, "wallet1");
|
|
||||||
let (wallet1, mask1_i) =
|
|
||||||
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
|
||||||
wallet_proxy.add_wallet(
|
|
||||||
"wallet1",
|
|
||||||
client1.get_send_instance(),
|
|
||||||
wallet1.clone(),
|
|
||||||
mask1_i.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create wallet 2
|
|
||||||
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
|
||||||
|
|
||||||
let config2 = initial_setup_wallet(test_dir, "wallet2");
|
|
||||||
let (wallet2, mask2_i) =
|
|
||||||
instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?;
|
|
||||||
wallet_proxy.add_wallet(
|
|
||||||
"wallet2",
|
|
||||||
client2.get_send_instance(),
|
|
||||||
wallet2.clone(),
|
|
||||||
mask2_i.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set the wallet proxy listener running
|
|
||||||
thread::spawn(move || {
|
|
||||||
if let Err(e) = wallet_proxy.run() {
|
|
||||||
error!("Wallet Proxy error: {}", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create some accounts in wallet 1
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "account", "-c", "mining"];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"account",
|
|
||||||
"-c",
|
|
||||||
"account_1",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// Create some accounts in wallet 2
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"account",
|
|
||||||
"-c",
|
|
||||||
"account_1",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
|
||||||
// already exists
|
|
||||||
assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err());
|
|
||||||
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"account",
|
|
||||||
"-c",
|
|
||||||
"account_2",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
|
|
||||||
// let's see those accounts
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "account"];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
|
|
||||||
// let's see those accounts
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "account"];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
|
|
||||||
// Mine a bit into wallet 1 so we have something to send
|
|
||||||
// (TODO: Be able to stop listeners so we can test this better)
|
|
||||||
let (wallet1, mask1_i) =
|
|
||||||
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
|
||||||
let mask1 = (&mask1_i).as_ref();
|
|
||||||
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
|
||||||
api.set_active_account(m, "mining")?;
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut bh = 10u64;
|
|
||||||
let _ = test_framework::award_blocks_to_wallet(
|
|
||||||
&chain,
|
|
||||||
wallet1.clone(),
|
|
||||||
mask1,
|
|
||||||
bh as usize,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let very_long_message = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
|
||||||
This part should all be truncated";
|
|
||||||
|
|
||||||
// Update info and check
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "info"];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// try a file exchange
|
|
||||||
let file_name = format!("{}/tx1.part_tx", test_dir);
|
|
||||||
let response_file_name = format!("{}/tx1.part_tx.response", test_dir);
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"send",
|
|
||||||
"-m",
|
|
||||||
"file",
|
|
||||||
"-d",
|
|
||||||
&file_name,
|
|
||||||
"-g",
|
|
||||||
very_long_message,
|
|
||||||
"10",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"account_1",
|
|
||||||
"receive",
|
|
||||||
"-i",
|
|
||||||
&file_name,
|
|
||||||
"-g",
|
|
||||||
"Thanks, Yeast!",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
|
||||||
|
|
||||||
// shouldn't be allowed to receive twice
|
|
||||||
assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err());
|
|
||||||
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"finalize",
|
|
||||||
"-i",
|
|
||||||
&response_file_name,
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
bh += 1;
|
|
||||||
|
|
||||||
let (wallet1, mask1_i) =
|
|
||||||
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
|
||||||
let mask1 = (&mask1_i).as_ref();
|
|
||||||
|
|
||||||
// Check our transaction log, should have 10 entries
|
|
||||||
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
|
||||||
api.set_active_account(m, "mining")?;
|
|
||||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?;
|
|
||||||
assert!(refreshed);
|
|
||||||
assert_eq!(txs.len(), bh as usize);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false);
|
|
||||||
bh += 10;
|
|
||||||
|
|
||||||
// update info for each
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "info"];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "account_1", "info"];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// check results in wallet 2
|
|
||||||
let (wallet2, mask2_i) =
|
|
||||||
instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?;
|
|
||||||
let mask2 = (&mask2_i).as_ref();
|
|
||||||
|
|
||||||
grin_wallet_controller::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
|
|
||||||
api.set_active_account(m, "account_1")?;
|
|
||||||
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
|
||||||
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
|
||||||
assert_eq!(wallet1_info.amount_currently_spendable, 10_000_000_000);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Self-send to same account, using smallest strategy
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"send",
|
|
||||||
"-m",
|
|
||||||
"file",
|
|
||||||
"-d",
|
|
||||||
&file_name,
|
|
||||||
"-g",
|
|
||||||
"Love, Yeast, Smallest",
|
|
||||||
"-s",
|
|
||||||
"smallest",
|
|
||||||
"10",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"receive",
|
|
||||||
"-i",
|
|
||||||
&file_name,
|
|
||||||
"-g",
|
|
||||||
"Thanks, Yeast!",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?;
|
|
||||||
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"finalize",
|
|
||||||
"-i",
|
|
||||||
&response_file_name,
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
bh += 1;
|
|
||||||
|
|
||||||
// Check our transaction log, should have bh entries + one for the self receive
|
|
||||||
let (wallet1, mask1_i) =
|
|
||||||
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
|
||||||
let mask1 = (&mask1_i).as_ref();
|
|
||||||
|
|
||||||
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
|
||||||
api.set_active_account(m, "mining")?;
|
|
||||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?;
|
|
||||||
assert!(refreshed);
|
|
||||||
assert_eq!(txs.len(), bh as usize + 1);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Try using the self-send method, splitting up outputs for the fun of it
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"send",
|
|
||||||
"-m",
|
|
||||||
"self",
|
|
||||||
"-d",
|
|
||||||
"mining",
|
|
||||||
"-g",
|
|
||||||
"Self love",
|
|
||||||
"-o",
|
|
||||||
"3",
|
|
||||||
"-s",
|
|
||||||
"smallest",
|
|
||||||
"10",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
bh += 1;
|
|
||||||
|
|
||||||
// Check our transaction log, should have bh entries + 2 for the self receives
|
|
||||||
let (wallet1, mask1_i) =
|
|
||||||
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
|
||||||
let mask1 = (&mask1_i).as_ref();
|
|
||||||
|
|
||||||
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
|
||||||
api.set_active_account(m, "mining")?;
|
|
||||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?;
|
|
||||||
assert!(refreshed);
|
|
||||||
assert_eq!(txs.len(), bh as usize + 2);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Another file exchange, don't send, but unlock with repair command
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"send",
|
|
||||||
"-m",
|
|
||||||
"file",
|
|
||||||
"-d",
|
|
||||||
&file_name,
|
|
||||||
"-g",
|
|
||||||
"Ain't sending",
|
|
||||||
"10",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "check", "-d"];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// Another file exchange, cancel this time
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"send",
|
|
||||||
"-m",
|
|
||||||
"file",
|
|
||||||
"-d",
|
|
||||||
&file_name,
|
|
||||||
"-g",
|
|
||||||
"Ain't sending 2",
|
|
||||||
"10",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"cancel",
|
|
||||||
"-i",
|
|
||||||
"26",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// issue an invoice tx, wallet 2
|
|
||||||
let file_name = format!("{}/invoice.slate", test_dir);
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"invoice",
|
|
||||||
"-d",
|
|
||||||
&file_name,
|
|
||||||
"-g",
|
|
||||||
"Please give me your precious grins. Love, Yeast",
|
|
||||||
"65",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
let output_file_name = format!("{}/invoice.slate.paid", test_dir);
|
|
||||||
|
|
||||||
// now pay the invoice tx, wallet 1
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"pay",
|
|
||||||
"-i",
|
|
||||||
&file_name,
|
|
||||||
"-d",
|
|
||||||
&output_file_name,
|
|
||||||
"-g",
|
|
||||||
"Here you go",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// and finalize, wallet 2
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"finalize",
|
|
||||||
"-i",
|
|
||||||
&output_file_name,
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
|
|
||||||
// bit more mining
|
|
||||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
|
|
||||||
//bh += 5;
|
|
||||||
|
|
||||||
// txs and outputs (mostly spit out for a visual in test logs)
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "txs"];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// message output (mostly spit out for a visual in test logs)
|
|
||||||
let arg_vec = vec![
|
|
||||||
"grin-wallet",
|
|
||||||
"-p",
|
|
||||||
"password",
|
|
||||||
"-a",
|
|
||||||
"mining",
|
|
||||||
"txs",
|
|
||||||
"-i",
|
|
||||||
"10",
|
|
||||||
];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
// txs and outputs (mostly spit out for a visual in test logs)
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "outputs"];
|
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "txs"];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "outputs"];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
|
|
||||||
// get tx output via -tx parameter
|
|
||||||
let mut tx_id = "".to_string();
|
|
||||||
grin_wallet_controller::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
|
|
||||||
api.set_active_account(m, "default")?;
|
|
||||||
let (_, txs) = api.retrieve_txs(m, true, None, None)?;
|
|
||||||
let some_tx_id = txs[0].tx_slate_id.clone();
|
|
||||||
assert!(some_tx_id.is_some());
|
|
||||||
tx_id = some_tx_id.unwrap().to_hyphenated().to_string().clone();
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "txs", "-t", &tx_id[..]];
|
|
||||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
|
||||||
|
|
||||||
// let logging finish
|
|
||||||
thread::sleep(Duration::from_millis(200));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn wallet_command_line() {
|
|
||||||
let test_dir = "target/test_output/command_line";
|
|
||||||
if let Err(e) = command_line_test_impl(test_dir) {
|
|
||||||
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,12 +23,11 @@ use crate::core::global;
|
||||||
use crate::util::init_logger;
|
use crate::util::init_logger;
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use grin_wallet_config as config;
|
use grin_wallet_config as config;
|
||||||
use grin_wallet_util::grin_api as api;
|
|
||||||
use grin_wallet_util::grin_core as core;
|
use grin_wallet_util::grin_core as core;
|
||||||
use grin_wallet_util::grin_util as util;
|
use grin_wallet_util::grin_util as util;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
mod cmd;
|
use grin_wallet::cmd;
|
||||||
|
|
||||||
// include build information
|
// include build information
|
||||||
pub mod built_info {
|
pub mod built_info {
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
mod wallet;
|
mod wallet;
|
||||||
mod wallet_args;
|
pub mod wallet_args;
|
||||||
mod wallet_tests;
|
|
||||||
|
|
||||||
pub use self::wallet::wallet_command;
|
pub use self::wallet::wallet_command;
|
|
@ -51,7 +51,7 @@ pub fn wallet_command(wallet_args: &ArgMatches<'_>, config: GlobalWalletConfig)
|
||||||
}
|
}
|
||||||
// ... if node isn't available, allow offline functions
|
// ... if node isn't available, allow offline functions
|
||||||
|
|
||||||
let res = wallet_args::wallet_command(wallet_args, wallet_config, node_client);
|
let res = wallet_args::wallet_command(wallet_args, wallet_config, node_client, false);
|
||||||
|
|
||||||
// we need to give log output a chance to catch up before exiting
|
// we need to give log output a chance to catch up before exiting
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
|
@ -582,6 +582,7 @@ pub fn parse_issue_invoice_args(
|
||||||
|
|
||||||
pub fn parse_process_invoice_args(
|
pub fn parse_process_invoice_args(
|
||||||
args: &ArgMatches,
|
args: &ArgMatches,
|
||||||
|
prompt: bool,
|
||||||
) -> Result<command::ProcessInvoiceArgs, ParseError> {
|
) -> Result<command::ProcessInvoiceArgs, ParseError> {
|
||||||
// TODO: display and prompt for confirmation of what we're doing
|
// TODO: display and prompt for confirmation of what we're doing
|
||||||
// message
|
// message
|
||||||
|
@ -636,7 +637,7 @@ pub fn parse_process_invoice_args(
|
||||||
// file input only
|
// file input only
|
||||||
let tx_file = parse_required(args, "input")?;
|
let tx_file = parse_required(args, "input")?;
|
||||||
|
|
||||||
if cfg!(not(test)) {
|
if prompt {
|
||||||
// Now we need to prompt the user whether they want to do this,
|
// Now we need to prompt the user whether they want to do this,
|
||||||
// which requires reading the slate
|
// which requires reading the slate
|
||||||
|
|
||||||
|
@ -754,6 +755,7 @@ pub fn wallet_command<C>(
|
||||||
wallet_args: &ArgMatches,
|
wallet_args: &ArgMatches,
|
||||||
mut wallet_config: WalletConfig,
|
mut wallet_config: WalletConfig,
|
||||||
mut node_client: C,
|
mut node_client: C,
|
||||||
|
test_mode: bool,
|
||||||
) -> Result<String, Error>
|
) -> Result<String, Error>
|
||||||
where
|
where
|
||||||
C: NodeClient + 'static + Clone,
|
C: NodeClient + 'static + Clone,
|
||||||
|
@ -815,7 +817,7 @@ where
|
||||||
let mask = lc.open_wallet(
|
let mask = lc.open_wallet(
|
||||||
None,
|
None,
|
||||||
prompt_password(&global_wallet_args.password),
|
prompt_password(&global_wallet_args.password),
|
||||||
true,
|
false,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
if let Some(account) = wallet_args.value_of("account") {
|
if let Some(account) = wallet_args.value_of("account") {
|
||||||
|
@ -854,6 +856,7 @@ where
|
||||||
("owner_api", Some(_)) => {
|
("owner_api", Some(_)) => {
|
||||||
let mut g = global_wallet_args.clone();
|
let mut g = global_wallet_args.clone();
|
||||||
g.tls_conf = None;
|
g.tls_conf = None;
|
||||||
|
print!("mask: {:?}", keychain_mask);
|
||||||
command::owner_api(wallet, keychain_mask, &wallet_config, &g)
|
command::owner_api(wallet, keychain_mask, &wallet_config, &g)
|
||||||
}
|
}
|
||||||
("web", Some(_)) => {
|
("web", Some(_)) => {
|
||||||
|
@ -885,7 +888,7 @@ where
|
||||||
command::issue_invoice_tx(wallet, km, a)
|
command::issue_invoice_tx(wallet, km, a)
|
||||||
}
|
}
|
||||||
("pay", Some(args)) => {
|
("pay", Some(args)) => {
|
||||||
let a = arg_parse!(parse_process_invoice_args(&args));
|
let a = arg_parse!(parse_process_invoice_args(&args, !test_mode));
|
||||||
command::process_invoice(
|
command::process_invoice(
|
||||||
wallet,
|
wallet,
|
||||||
km,
|
km,
|
18
src/lib.rs
Normal file
18
src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2019 The Grin Developers
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use grin_wallet_config as config;
|
||||||
|
use grin_wallet_util::grin_api as api;
|
||||||
|
use grin_wallet_util::grin_util as util;
|
||||||
|
|
||||||
|
pub mod cmd;
|
494
tests/cmd_line_basic.rs
Normal file
494
tests/cmd_line_basic.rs
Normal file
|
@ -0,0 +1,494 @@
|
||||||
|
// Copyright 2019 The Grin Developers
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Test wallet command line works as expected
|
||||||
|
#[macro_use]
|
||||||
|
extern crate clap;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
extern crate grin_wallet;
|
||||||
|
|
||||||
|
use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy};
|
||||||
|
|
||||||
|
use clap::App;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use grin_wallet_impls::DefaultLCProvider;
|
||||||
|
use grin_wallet_util::grin_keychain::ExtKeychain;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::{execute_command, initial_setup_wallet, instantiate_wallet, setup};
|
||||||
|
|
||||||
|
/// command line tests
|
||||||
|
fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::Error> {
|
||||||
|
setup(test_dir);
|
||||||
|
// Create a new proxy to simulate server and wallet responses
|
||||||
|
let mut wallet_proxy: WalletProxy<
|
||||||
|
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
|
||||||
|
LocalWalletClient,
|
||||||
|
ExtKeychain,
|
||||||
|
> = WalletProxy::new(test_dir);
|
||||||
|
let chain = wallet_proxy.chain.clone();
|
||||||
|
|
||||||
|
// load app yaml. If it don't exist, just say so and exit
|
||||||
|
let yml = load_yaml!("../src/bin/grin-wallet.yml");
|
||||||
|
let app = App::from_yaml(yml);
|
||||||
|
|
||||||
|
// wallet init
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "init", "-h"];
|
||||||
|
// should create new wallet file
|
||||||
|
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?;
|
||||||
|
|
||||||
|
// trying to init twice - should fail
|
||||||
|
assert!(execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone()).is_err());
|
||||||
|
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
||||||
|
|
||||||
|
// add wallet to proxy
|
||||||
|
//let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
|
||||||
|
let config1 = initial_setup_wallet(test_dir, "wallet1");
|
||||||
|
let (wallet1, mask1_i) =
|
||||||
|
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||||
|
wallet_proxy.add_wallet(
|
||||||
|
"wallet1",
|
||||||
|
client1.get_send_instance(),
|
||||||
|
wallet1.clone(),
|
||||||
|
mask1_i.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create wallet 2
|
||||||
|
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
||||||
|
|
||||||
|
let config2 = initial_setup_wallet(test_dir, "wallet2");
|
||||||
|
let (wallet2, mask2_i) =
|
||||||
|
instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?;
|
||||||
|
wallet_proxy.add_wallet(
|
||||||
|
"wallet2",
|
||||||
|
client2.get_send_instance(),
|
||||||
|
wallet2.clone(),
|
||||||
|
mask2_i.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the wallet proxy listener running
|
||||||
|
thread::spawn(move || {
|
||||||
|
if let Err(e) = wallet_proxy.run() {
|
||||||
|
error!("Wallet Proxy error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create some accounts in wallet 1
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "account", "-c", "mining"];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"account",
|
||||||
|
"-c",
|
||||||
|
"account_1",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// Create some accounts in wallet 2
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"account",
|
||||||
|
"-c",
|
||||||
|
"account_1",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
||||||
|
// already exists
|
||||||
|
assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err());
|
||||||
|
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"account",
|
||||||
|
"-c",
|
||||||
|
"account_2",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
|
||||||
|
// let's see those accounts
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "account"];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
|
||||||
|
// let's see those accounts
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "account"];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
|
||||||
|
// Mine a bit into wallet 1 so we have something to send
|
||||||
|
// (TODO: Be able to stop listeners so we can test this better)
|
||||||
|
let (wallet1, mask1_i) =
|
||||||
|
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||||
|
let mask1 = (&mask1_i).as_ref();
|
||||||
|
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||||
|
api.set_active_account(m, "mining")?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut bh = 10u64;
|
||||||
|
let _ =
|
||||||
|
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||||
|
|
||||||
|
let very_long_message = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
This part should all be truncated";
|
||||||
|
|
||||||
|
// Update info and check
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "info"];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// try a file exchange
|
||||||
|
let file_name = format!("{}/tx1.part_tx", test_dir);
|
||||||
|
let response_file_name = format!("{}/tx1.part_tx.response", test_dir);
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"send",
|
||||||
|
"-m",
|
||||||
|
"file",
|
||||||
|
"-d",
|
||||||
|
&file_name,
|
||||||
|
"-g",
|
||||||
|
very_long_message,
|
||||||
|
"10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"account_1",
|
||||||
|
"receive",
|
||||||
|
"-i",
|
||||||
|
&file_name,
|
||||||
|
"-g",
|
||||||
|
"Thanks, Yeast!",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
||||||
|
|
||||||
|
// shouldn't be allowed to receive twice
|
||||||
|
assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err());
|
||||||
|
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"finalize",
|
||||||
|
"-i",
|
||||||
|
&response_file_name,
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
bh += 1;
|
||||||
|
|
||||||
|
let (wallet1, mask1_i) =
|
||||||
|
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||||
|
let mask1 = (&mask1_i).as_ref();
|
||||||
|
|
||||||
|
// Check our transaction log, should have 10 entries
|
||||||
|
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||||
|
api.set_active_account(m, "mining")?;
|
||||||
|
let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?;
|
||||||
|
assert!(refreshed);
|
||||||
|
assert_eq!(txs.len(), bh as usize);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false);
|
||||||
|
bh += 10;
|
||||||
|
|
||||||
|
// update info for each
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "info"];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "account_1", "info"];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// check results in wallet 2
|
||||||
|
let (wallet2, mask2_i) =
|
||||||
|
instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?;
|
||||||
|
let mask2 = (&mask2_i).as_ref();
|
||||||
|
|
||||||
|
grin_wallet_controller::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
|
||||||
|
api.set_active_account(m, "account_1")?;
|
||||||
|
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||||
|
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
||||||
|
assert_eq!(wallet1_info.amount_currently_spendable, 10_000_000_000);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Self-send to same account, using smallest strategy
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"send",
|
||||||
|
"-m",
|
||||||
|
"file",
|
||||||
|
"-d",
|
||||||
|
&file_name,
|
||||||
|
"-g",
|
||||||
|
"Love, Yeast, Smallest",
|
||||||
|
"-s",
|
||||||
|
"smallest",
|
||||||
|
"10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"receive",
|
||||||
|
"-i",
|
||||||
|
&file_name,
|
||||||
|
"-g",
|
||||||
|
"Thanks, Yeast!",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?;
|
||||||
|
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"finalize",
|
||||||
|
"-i",
|
||||||
|
&response_file_name,
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
bh += 1;
|
||||||
|
|
||||||
|
// Check our transaction log, should have bh entries + one for the self receive
|
||||||
|
let (wallet1, mask1_i) =
|
||||||
|
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||||
|
let mask1 = (&mask1_i).as_ref();
|
||||||
|
|
||||||
|
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||||
|
api.set_active_account(m, "mining")?;
|
||||||
|
let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?;
|
||||||
|
assert!(refreshed);
|
||||||
|
assert_eq!(txs.len(), bh as usize + 1);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Try using the self-send method, splitting up outputs for the fun of it
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"send",
|
||||||
|
"-m",
|
||||||
|
"self",
|
||||||
|
"-d",
|
||||||
|
"mining",
|
||||||
|
"-g",
|
||||||
|
"Self love",
|
||||||
|
"-o",
|
||||||
|
"3",
|
||||||
|
"-s",
|
||||||
|
"smallest",
|
||||||
|
"10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
bh += 1;
|
||||||
|
|
||||||
|
// Check our transaction log, should have bh entries + 2 for the self receives
|
||||||
|
let (wallet1, mask1_i) =
|
||||||
|
instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||||
|
let mask1 = (&mask1_i).as_ref();
|
||||||
|
|
||||||
|
grin_wallet_controller::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||||
|
api.set_active_account(m, "mining")?;
|
||||||
|
let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?;
|
||||||
|
assert!(refreshed);
|
||||||
|
assert_eq!(txs.len(), bh as usize + 2);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Another file exchange, don't send, but unlock with repair command
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"send",
|
||||||
|
"-m",
|
||||||
|
"file",
|
||||||
|
"-d",
|
||||||
|
&file_name,
|
||||||
|
"-g",
|
||||||
|
"Ain't sending",
|
||||||
|
"10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "check", "-d"];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// Another file exchange, cancel this time
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"send",
|
||||||
|
"-m",
|
||||||
|
"file",
|
||||||
|
"-d",
|
||||||
|
&file_name,
|
||||||
|
"-g",
|
||||||
|
"Ain't sending 2",
|
||||||
|
"10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"cancel",
|
||||||
|
"-i",
|
||||||
|
"26",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// issue an invoice tx, wallet 2
|
||||||
|
let file_name = format!("{}/invoice.slate", test_dir);
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"invoice",
|
||||||
|
"-d",
|
||||||
|
&file_name,
|
||||||
|
"-g",
|
||||||
|
"Please give me your precious grins. Love, Yeast",
|
||||||
|
"65",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
let output_file_name = format!("{}/invoice.slate.paid", test_dir);
|
||||||
|
|
||||||
|
// now pay the invoice tx, wallet 1
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"pay",
|
||||||
|
"-i",
|
||||||
|
&file_name,
|
||||||
|
"-d",
|
||||||
|
&output_file_name,
|
||||||
|
"-g",
|
||||||
|
"Here you go",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// and finalize, wallet 2
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"finalize",
|
||||||
|
"-i",
|
||||||
|
&output_file_name,
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
|
||||||
|
// bit more mining
|
||||||
|
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
|
||||||
|
//bh += 5;
|
||||||
|
|
||||||
|
// txs and outputs (mostly spit out for a visual in test logs)
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "txs"];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// message output (mostly spit out for a visual in test logs)
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin-wallet",
|
||||||
|
"-p",
|
||||||
|
"password",
|
||||||
|
"-a",
|
||||||
|
"mining",
|
||||||
|
"txs",
|
||||||
|
"-i",
|
||||||
|
"10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// txs and outputs (mostly spit out for a visual in test logs)
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "outputs"];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "txs"];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "outputs"];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
|
||||||
|
// get tx output via -tx parameter
|
||||||
|
let mut tx_id = "".to_string();
|
||||||
|
grin_wallet_controller::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
|
||||||
|
api.set_active_account(m, "default")?;
|
||||||
|
let (_, txs) = api.retrieve_txs(m, true, None, None)?;
|
||||||
|
let some_tx_id = txs[0].tx_slate_id.clone();
|
||||||
|
assert!(some_tx_id.is_some());
|
||||||
|
tx_id = some_tx_id.unwrap().to_hyphenated().to_string().clone();
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "txs", "-t", &tx_id[..]];
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||||
|
|
||||||
|
// let logging finish
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wallet_command_line() {
|
||||||
|
let test_dir = "target/test_output/command_line";
|
||||||
|
if let Err(e) = command_line_test_impl(test_dir) {
|
||||||
|
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
||||||
|
}
|
||||||
|
}
|
297
tests/common/mod.rs
Normal file
297
tests/common/mod.rs
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
// Copyright 2019 The Grin Developers
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Common functions for wallet integration tests
|
||||||
|
extern crate grin_wallet;
|
||||||
|
|
||||||
|
use grin_wallet_impls::test_framework::LocalWalletClient;
|
||||||
|
use grin_wallet_util::grin_util as util;
|
||||||
|
|
||||||
|
use clap::{App, ArgMatches};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{env, fs};
|
||||||
|
use util::{Mutex, ZeroingString};
|
||||||
|
|
||||||
|
use grin_wallet_config::{GlobalWalletConfig, WalletConfig, GRIN_WALLET_DIR};
|
||||||
|
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl};
|
||||||
|
use grin_wallet_libwallet::{WalletInfo, WalletInst};
|
||||||
|
use grin_wallet_util::grin_core::global::{self, ChainTypes};
|
||||||
|
use grin_wallet_util::grin_keychain::ExtKeychain;
|
||||||
|
use util::secp::key::SecretKey;
|
||||||
|
|
||||||
|
use grin_wallet::cmd::wallet_args;
|
||||||
|
use grin_wallet_util::grin_api as api;
|
||||||
|
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
// Set up 2 wallets and launch the test proxy behind them
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! setup_proxy {
|
||||||
|
($test_dir: expr, $chain: ident, $wallet1: ident, $client1: ident, $mask1: ident, $wallet2: ident, $client2: ident, $mask2: ident) => {
|
||||||
|
// Create a new proxy to simulate server and wallet responses
|
||||||
|
let mut wallet_proxy: WalletProxy<
|
||||||
|
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
|
||||||
|
LocalWalletClient,
|
||||||
|
ExtKeychain,
|
||||||
|
> = WalletProxy::new($test_dir);
|
||||||
|
let $chain = wallet_proxy.chain.clone();
|
||||||
|
|
||||||
|
// load app yaml. If it don't exist, just say so and exit
|
||||||
|
let yml = load_yaml!("../src/bin/grin-wallet.yml");
|
||||||
|
let app = App::from_yaml(yml);
|
||||||
|
|
||||||
|
// wallet init
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "init", "-h"];
|
||||||
|
// should create new wallet file
|
||||||
|
let $client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
||||||
|
execute_command(&app, $test_dir, "wallet1", &$client1, arg_vec.clone())?;
|
||||||
|
|
||||||
|
// add wallet to proxy
|
||||||
|
let config1 = initial_setup_wallet($test_dir, "wallet1");
|
||||||
|
//config1.owner_api_listen_port = Some(13420);
|
||||||
|
let ($wallet1, mask1_i) =
|
||||||
|
instantiate_wallet(config1.clone(), $client1.clone(), "password", "default")?;
|
||||||
|
let $mask1 = (&mask1_i).as_ref();
|
||||||
|
wallet_proxy.add_wallet(
|
||||||
|
"wallet1",
|
||||||
|
$client1.get_send_instance(),
|
||||||
|
$wallet1.clone(),
|
||||||
|
mask1_i.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create wallet 2, which will run a listener
|
||||||
|
let $client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
|
||||||
|
execute_command(&app, $test_dir, "wallet2", &$client2, arg_vec.clone())?;
|
||||||
|
|
||||||
|
let config2 = initial_setup_wallet($test_dir, "wallet2");
|
||||||
|
//config2.api_listen_port = 23415;
|
||||||
|
let ($wallet2, mask2_i) =
|
||||||
|
instantiate_wallet(config2.clone(), $client2.clone(), "password", "default")?;
|
||||||
|
let $mask2 = (&mask2_i).as_ref();
|
||||||
|
wallet_proxy.add_wallet(
|
||||||
|
"wallet2",
|
||||||
|
$client2.get_send_instance(),
|
||||||
|
$wallet2.clone(),
|
||||||
|
mask2_i.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the wallet proxy listener running
|
||||||
|
thread::spawn(move || {
|
||||||
|
if let Err(e) = wallet_proxy.run() {
|
||||||
|
error!("Wallet Proxy error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clean_output_dir(test_dir: &str) {
|
||||||
|
let _ = fs::remove_dir_all(test_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(test_dir: &str) {
|
||||||
|
util::init_test_logger();
|
||||||
|
clean_output_dir(test_dir);
|
||||||
|
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a wallet config file in the given current directory
|
||||||
|
pub fn config_command_wallet(
|
||||||
|
dir_name: &str,
|
||||||
|
wallet_name: &str,
|
||||||
|
) -> Result<(), grin_wallet_controller::Error> {
|
||||||
|
let mut current_dir;
|
||||||
|
let mut default_config = GlobalWalletConfig::default();
|
||||||
|
current_dir = env::current_dir().unwrap_or_else(|e| {
|
||||||
|
panic!("Error creating config file: {}", e);
|
||||||
|
});
|
||||||
|
current_dir.push(dir_name);
|
||||||
|
current_dir.push(wallet_name);
|
||||||
|
let _ = fs::create_dir_all(current_dir.clone());
|
||||||
|
let mut config_file_name = current_dir.clone();
|
||||||
|
config_file_name.push("grin-wallet.toml");
|
||||||
|
if config_file_name.exists() {
|
||||||
|
return Err(grin_wallet_controller::ErrorKind::ArgumentError(
|
||||||
|
"grin-wallet.toml already exists in the target directory. Please remove it first"
|
||||||
|
.to_owned(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
default_config.update_paths(¤t_dir);
|
||||||
|
default_config
|
||||||
|
.write_to_file(config_file_name.to_str().unwrap())
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("Error creating config file: {}", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"File {} configured and created",
|
||||||
|
config_file_name.to_str().unwrap(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles setup and detection of paths for wallet
|
||||||
|
pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> WalletConfig {
|
||||||
|
let mut current_dir;
|
||||||
|
current_dir = env::current_dir().unwrap_or_else(|e| {
|
||||||
|
panic!("Error creating config file: {}", e);
|
||||||
|
});
|
||||||
|
current_dir.push(dir_name);
|
||||||
|
current_dir.push(wallet_name);
|
||||||
|
let _ = fs::create_dir_all(current_dir.clone());
|
||||||
|
let mut config_file_name = current_dir.clone();
|
||||||
|
config_file_name.push("grin-wallet.toml");
|
||||||
|
GlobalWalletConfig::new(config_file_name.to_str().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.members
|
||||||
|
.unwrap()
|
||||||
|
.wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wallet_subcommand<'a>(
|
||||||
|
wallet_dir: &str,
|
||||||
|
wallet_name: &str,
|
||||||
|
args: ArgMatches<'a>,
|
||||||
|
) -> ArgMatches<'a> {
|
||||||
|
match args.subcommand() {
|
||||||
|
("init", Some(init_args)) => {
|
||||||
|
// wallet init command should spit out its config file then continue
|
||||||
|
// (if desired)
|
||||||
|
if init_args.is_present("here") {
|
||||||
|
let _ = config_command_wallet(wallet_dir, wallet_name);
|
||||||
|
}
|
||||||
|
init_args.to_owned()
|
||||||
|
}
|
||||||
|
_ => ArgMatches::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Helper to create an instance of the LMDB wallet
|
||||||
|
pub fn instantiate_wallet(
|
||||||
|
mut wallet_config: WalletConfig,
|
||||||
|
node_client: LocalWalletClient,
|
||||||
|
passphrase: &str,
|
||||||
|
account: &str,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
Arc<
|
||||||
|
Mutex<
|
||||||
|
Box<
|
||||||
|
WalletInst<
|
||||||
|
'static,
|
||||||
|
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
||||||
|
LocalWalletClient,
|
||||||
|
ExtKeychain,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
Option<SecretKey>,
|
||||||
|
),
|
||||||
|
grin_wallet_controller::Error,
|
||||||
|
> {
|
||||||
|
wallet_config.chain_type = None;
|
||||||
|
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(node_client).unwrap())
|
||||||
|
as Box<
|
||||||
|
WalletInst<
|
||||||
|
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
||||||
|
LocalWalletClient,
|
||||||
|
ExtKeychain,
|
||||||
|
>,
|
||||||
|
>;
|
||||||
|
let lc = wallet.lc_provider().unwrap();
|
||||||
|
// legacy hack to avoid the need for changes in existing grin-wallet.toml files
|
||||||
|
// remove `wallet_data` from end of path as
|
||||||
|
// new lifecycle provider assumes grin_wallet.toml is in root of data directory
|
||||||
|
let mut top_level_wallet_dir = PathBuf::from(wallet_config.clone().data_file_dir);
|
||||||
|
if top_level_wallet_dir.ends_with(GRIN_WALLET_DIR) {
|
||||||
|
top_level_wallet_dir.pop();
|
||||||
|
wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into();
|
||||||
|
}
|
||||||
|
lc.set_wallet_directory(&wallet_config.data_file_dir);
|
||||||
|
let keychain_mask = lc
|
||||||
|
.open_wallet(None, ZeroingString::from(passphrase), true, false)
|
||||||
|
.unwrap();
|
||||||
|
let wallet_inst = lc.wallet_inst()?;
|
||||||
|
wallet_inst.set_parent_key_id_by_name(account)?;
|
||||||
|
Ok((Arc::new(Mutex::new(wallet)), keychain_mask))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_command(
|
||||||
|
app: &App,
|
||||||
|
test_dir: &str,
|
||||||
|
wallet_name: &str,
|
||||||
|
client: &LocalWalletClient,
|
||||||
|
arg_vec: Vec<&str>,
|
||||||
|
) -> Result<String, grin_wallet_controller::Error> {
|
||||||
|
let args = app.clone().get_matches_from(arg_vec);
|
||||||
|
let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone());
|
||||||
|
let mut config = initial_setup_wallet(test_dir, wallet_name);
|
||||||
|
//unset chain type so it doesn't get reset
|
||||||
|
config.chain_type = None;
|
||||||
|
wallet_args::wallet_command(&args, config.clone(), client.clone(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post<IN>(url: &Url, api_secret: Option<String>, input: &IN) -> Result<String, api::Error>
|
||||||
|
where
|
||||||
|
IN: Serialize,
|
||||||
|
{
|
||||||
|
// TODO: change create_post_request to accept a url instead of a &str
|
||||||
|
let req = api::client::create_post_request(url.as_str(), api_secret, input)?;
|
||||||
|
let res = api::client::send_request(req)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_request<OUT>(
|
||||||
|
id: u64,
|
||||||
|
dest: &str,
|
||||||
|
req: &str,
|
||||||
|
) -> Result<Result<OUT, WalletAPIReturnError>, api::Error>
|
||||||
|
where
|
||||||
|
OUT: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let url = Url::parse(dest).unwrap();
|
||||||
|
let req: Value = serde_json::from_str(req).unwrap();
|
||||||
|
let res: String = post(&url, None, &req).map_err(|e| {
|
||||||
|
let err_string = format!("{}", e);
|
||||||
|
println!("{}", err_string);
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
let res = serde_json::from_str(&res).unwrap();
|
||||||
|
let res = easy_jsonrpc::Response::from_json_response(res).unwrap();
|
||||||
|
let res = res.outputs.get(&id).unwrap().clone().unwrap();
|
||||||
|
if res["Err"] != json!(null) {
|
||||||
|
Ok(Err(WalletAPIReturnError {
|
||||||
|
message: res["Err"].as_str().unwrap().to_owned(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
// deserialize result into expected type
|
||||||
|
let value: OUT = serde_json::from_value(res["Ok"].clone()).unwrap();
|
||||||
|
Ok(Ok(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types to make working with json responses easier
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct WalletAPIReturnError {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct RetrieveSummaryInfoResp(pub bool, pub WalletInfo);
|
0
tests/data/v3_reqs/open_wallet.req.json
Normal file
0
tests/data/v3_reqs/open_wallet.req.json
Normal file
10
tests/data/v3_reqs/retrieve_info.req.json
Normal file
10
tests/data/v3_reqs/retrieve_info.req.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "retrieve_summary_info",
|
||||||
|
"params": {
|
||||||
|
"token": null,
|
||||||
|
"refresh_from_node": true,
|
||||||
|
"minimum_confirmations": 1
|
||||||
|
},
|
||||||
|
"id": 1
|
||||||
|
}
|
76
tests/owner_v3.rs
Normal file
76
tests/owner_v3.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2019 The Grin Developers
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate clap;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
extern crate grin_wallet;
|
||||||
|
|
||||||
|
use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy};
|
||||||
|
|
||||||
|
use clap::App;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use grin_wallet_impls::DefaultLCProvider;
|
||||||
|
use grin_wallet_util::grin_keychain::ExtKeychain;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod common;
|
||||||
|
use common::RetrieveSummaryInfoResp;
|
||||||
|
use common::{execute_command, initial_setup_wallet, instantiate_wallet, send_request, setup};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn owner_v3() -> Result<(), grin_wallet_controller::Error> {
|
||||||
|
let test_dir = "target/test_output/owner_v3";
|
||||||
|
setup(test_dir);
|
||||||
|
|
||||||
|
setup_proxy!(test_dir, chain, wallet1, client1, mask1, wallet2, client2, _mask2);
|
||||||
|
|
||||||
|
// add some blocks manually
|
||||||
|
let bh = 10u64;
|
||||||
|
let _ =
|
||||||
|
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||||
|
|
||||||
|
// run the owner listener on wallet 1
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "owner_api"];
|
||||||
|
// Set running
|
||||||
|
thread::spawn(move || {
|
||||||
|
let yml = load_yaml!("../src/bin/grin-wallet.yml");
|
||||||
|
let app = App::from_yaml(yml);
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// run the foreign listener for wallet 2
|
||||||
|
let arg_vec = vec!["grin-wallet", "-p", "password", "listen"];
|
||||||
|
// Set owner listener running
|
||||||
|
thread::spawn(move || {
|
||||||
|
let yml = load_yaml!("../src/bin/grin-wallet.yml");
|
||||||
|
let app = App::from_yaml(yml);
|
||||||
|
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
|
// Send simple retrieve_info request to owner listener
|
||||||
|
let req = include_str!("data/v3_reqs/retrieve_info.req.json");
|
||||||
|
let res = send_request(1, "http://127.0.0.1:3420/v3/owner", req)?;
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let value: RetrieveSummaryInfoResp = res.unwrap();
|
||||||
|
assert_eq!(value.1.amount_currently_spendable, 420000000000);
|
||||||
|
println!("Response: {:?}", value);
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue