From a0c494c7301b4ef5994df293b42ba58d3736b802 Mon Sep 17 00:00:00 2001 From: librelois <c@elo.tf> Date: Sat, 5 Dec 2020 02:28:41 +0100 Subject: [PATCH] ref+tests: make sub-commands testable and test it --- Cargo.lock | 141 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 + src/client.rs | 37 ++++++++++-- src/commands.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 69 +++++----------------- 5 files changed, 336 insertions(+), 62 deletions(-) create mode 100644 src/commands.rs diff --git a/Cargo.lock b/Cargo.lock index bbfff64..4c30a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -169,12 +178,24 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "downcast" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" + [[package]] name = "either" version = "1.6.1" @@ -212,6 +233,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -243,6 +273,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -394,6 +430,7 @@ version = "0.1.0" dependencies = [ "anyhow", "graphql_client", + "mockall", "reqwest", "serde", "structopt", @@ -664,6 +701,33 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "mockall" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cabea45a7fc0e37093f4f30a5e2b62602253f91791c057d5f0470c63260c3d" +dependencies = [ + "cfg-if 0.1.10", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c461918bf7f59eefb1459252756bf2351a995d6bd510d0b2061bd86bcdabfa6" +dependencies = [ + "cfg-if 0.1.10", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "native-tls" version = "0.2.6" @@ -693,6 +757,21 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -824,6 +903,35 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "predicates" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" +dependencies = [ + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" + +[[package]] +name = "predicates-tree" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -913,6 +1021,24 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1147,6 +1273,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "tinyvec" version = "1.1.0" @@ -1241,6 +1376,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "try-lock" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index fb731be..8b7635f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,6 @@ graphql_client = "0.9.0" reqwest = { version = "0.10.9", features = ["blocking", "json"] } serde = { version = "1.0.105", features = ["derive"] } structopt = "0.3.18" + +[dev-dependencies] +mockall = "0.8.0" diff --git a/src/client.rs b/src/client.rs index 8aede5c..c6926f7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,17 +1,42 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + use crate::*; -pub(crate) struct Client(reqwest::blocking::Client); +#[cfg_attr(test, allow(dead_code))] +pub(crate) struct Client { + inner: reqwest::blocking::Client, + server_url: String, +} +#[cfg_attr(test, mockall::automock, allow(dead_code))] impl Client { - pub(crate) fn new() -> Self { - Client(reqwest::blocking::Client::new()) + pub(crate) fn new(server_url: String) -> Self { + Client { + inner: reqwest::blocking::Client::new(), + server_url, + } } - pub(crate) fn send_gql_query<Req: serde::Serialize, ResData: serde::de::DeserializeOwned>( + pub(crate) fn send_gql_query< + Req: 'static + serde::Serialize, + ResData: 'static + serde::de::DeserializeOwned, + >( &self, request_body: &Req, - server_url: &str, ) -> anyhow::Result<ResData> { - let request = self.0.post(server_url).json(request_body); + let request = self.inner.post(&self.server_url).json(request_body); let start_time = Instant::now(); let response = request.send()?; diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..d03ff59 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,148 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; + +#[derive(StructOpt)] +pub(crate) enum Command { + /// Get account balance + Balance { + pubkey_or_script: String, + #[structopt(short, long)] + ud_unit: bool, + }, + /// Get current UD value + CurrentUd, +} + +pub(crate) fn balance<W: Write>( + client: &Client, + out: &mut W, + pubkey_or_script: &str, + ud_unit: bool, +) -> anyhow::Result<()> { + let request_body = BalanceQuery::build_query(balance_query::Variables { + script: pubkey_or_script.to_owned(), + with_ud: ud_unit, + }); + + let balance_query::ResponseData { + balance: balance_query::BalanceQueryBalance { amount }, + current_ud: current_ud_opt, + } = client.send_gql_query(&request_body)?; + + if let Some(balance_query::BalanceQueryCurrentUd { amount: ud_amount }) = current_ud_opt { + writeln!( + out, + "The balance of account '{}' is {:.2} UDĞ1 !", + pubkey_or_script, + amount as f64 / ud_amount as f64, + )?; + } else { + writeln!( + out, + "The balance of account '{}' is {}.{:02} Ğ1 !", + pubkey_or_script, + amount / 100, + amount % 100 + )?; + } + Ok(()) +} + +pub(crate) fn current_ud<W: Write>(client: &Client, out: &mut W) -> anyhow::Result<()> { + let request_body = CurrentUdQuery::build_query(current_ud_query::Variables); + + if let current_ud_query::ResponseData { + current_ud: Some(current_ud_query::CurrentUdQueryCurrentUd { amount }), + } = client.send_gql_query(&request_body)? + { + let int_part = amount / 100; + let dec_part = amount % 100; + writeln!( + out, + "The current UD value is {}.{:02} Ğ1 !", + int_part, dec_part + )?; + } else { + writeln!(out, "server with empty blockchain")?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_balance() -> anyhow::Result<()> { + let mut client = Client::default(); + client + .expect_send_gql_query::<graphql_client::QueryBody<balance_query::Variables>, _>() + .returning(|_| { + Ok(balance_query::ResponseData { + balance: balance_query::BalanceQueryBalance { amount: 2_046 }, + current_ud: None, + }) + }); + let mut out = Vec::new(); + balance(&client, &mut out, "toto", false)?; + let output = std::str::from_utf8(&out)?; + + assert_eq!(output, "The balance of account 'toto' is 20.46 Ğ1 !\n"); + + Ok(()) + } + + #[test] + fn test_balance_with_ud_unit() -> anyhow::Result<()> { + let mut client = Client::default(); + client + .expect_send_gql_query::<graphql_client::QueryBody<balance_query::Variables>, _>() + .returning(|_| { + Ok(balance_query::ResponseData { + balance: balance_query::BalanceQueryBalance { amount: 2_046 }, + current_ud: Some(balance_query::BalanceQueryCurrentUd { amount: 1_023 }), + }) + }); + let mut out = Vec::new(); + balance(&client, &mut out, "toto", true)?; + let output = std::str::from_utf8(&out)?; + + assert_eq!(output, "The balance of account 'toto' is 2.00 UDĞ1 !\n"); + + Ok(()) + } + + #[test] + fn test_current_ud() -> anyhow::Result<()> { + let mut client = Client::default(); + client + .expect_send_gql_query::<graphql_client::QueryBody<current_ud_query::Variables>, _>() + .returning(|_| { + Ok(current_ud_query::ResponseData { + current_ud: Some(current_ud_query::CurrentUdQueryCurrentUd { amount: 1_023 }), + }) + }); + let mut out = Vec::new(); + current_ud(&client, &mut out)?; + let output = std::str::from_utf8(&out)?; + + assert_eq!(output, "The current UD value is 10.23 Ğ1 !\n"); + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 49380cc..b664c1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,10 +25,17 @@ )] mod client; +mod commands; +#[cfg(not(test))] use crate::client::Client; +#[cfg(test)] +use crate::client::MockClient as Client; +use crate::commands::Command; +use commands::{balance, current_ud}; use graphql_client::GraphQLQuery; use graphql_client::Response; +use std::io::Write; use std::time::Instant; use structopt::StructOpt; @@ -49,71 +56,21 @@ struct CliArgs { #[structopt(short, long, default_value = DEFAULT_GVA_SERVER)] server: String, #[structopt(subcommand)] - command: SubCommand, -} - -#[derive(StructOpt)] -enum SubCommand { - /// Get account balance - Balance { - pubkey_or_script: String, - #[structopt(short, long)] - ud_unit: bool, - }, - /// Get current UD value - CurrentUd, + command: Command, } fn main() -> anyhow::Result<()> { let cli_args = CliArgs::from_args(); - let client = Client::new(); + let client = Client::new(cli_args.server); + let mut out = std::io::stdout(); match cli_args.command { - SubCommand::Balance { + Command::Balance { pubkey_or_script, ud_unit, - } => { - let request_body = BalanceQuery::build_query(balance_query::Variables { - script: pubkey_or_script.clone(), - with_ud: ud_unit, - }); - - let balance_query::ResponseData { - balance: balance_query::BalanceQueryBalance { amount }, - current_ud: current_ud_opt, - } = client.send_gql_query(&request_body, &cli_args.server)?; - - if let Some(balance_query::BalanceQueryCurrentUd { amount: ud_amount }) = current_ud_opt - { - println!( - "The balance of account '{}' is {:.2} UDĞ1 !", - pubkey_or_script, - amount as f64 / ud_amount as f64, - ); - } else { - println!( - "The balance of account '{}' is {}.{} Ğ1 !", - pubkey_or_script, - amount / 100, - amount % 100 - ); - } - } - SubCommand::CurrentUd => { - let request_body = CurrentUdQuery::build_query(current_ud_query::Variables); - - if let current_ud_query::ResponseData { - current_ud: Some(current_ud_query::CurrentUdQueryCurrentUd { amount }), - } = client.send_gql_query(&request_body, &cli_args.server)? - { - let int_part = amount / 100; - let dec_part = amount % 100; - println!("The current UD value is {}.{} Ğ1 !", int_part, dec_part); - } else { - println!("server with empty blockchain"); - } - } + } => balance(&client, &mut out, &pubkey_or_script, ud_unit)?, + Command::CurrentUd => current_ud(&client, &mut out)?, } Ok(()) } -- GitLab