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:
Yeastplume 2019-08-13 09:36:21 +01:00 committed by GitHub
parent f8c316a351
commit 62d976f9ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1011 additions and 671 deletions

1
.gitignore vendored
View file

@ -10,5 +10,4 @@ target
grin.log grin.log
wallet.seed wallet.seed
test_output test_output
wallet*
.idea/ .idea/

5
Cargo.lock generated
View file

@ -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]]

View file

@ -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"

View file

@ -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

View file

@ -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)
}
} }
} }

View file

@ -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(&current_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());
}
}
}

View file

@ -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 {

View file

@ -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;

View file

@ -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));

View file

@ -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
View 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
View 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
View 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(&current_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);

View file

View 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
View 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(())
}