diff --git a/Cargo.lock b/Cargo.lock index f7fbb6a6aa5792381f4df4cc842aeac7eb13d9b1..3908137f8b6ab490466087abd8794cb97a75f058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1517,6 +1517,7 @@ dependencies = [ name = "duniter-end2end-tests" version = "3.0.0" dependencies = [ + "anyhow", "async-trait", "clap", "ctrlc", diff --git a/end2end-tests/Cargo.toml b/end2end-tests/Cargo.toml index 8d7b445eeded82e13bb22a059caefa39ad27fd7d..09aae7a4982bc07e83eeeddca11c4e293a1d40e1 100644 --- a/end2end-tests/Cargo.toml +++ b/end2end-tests/Cargo.toml @@ -9,6 +9,7 @@ repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' version = '3.0.0' [dev-dependencies] +anyhow = "1.0" async-trait = "0.1" clap = { version = "3.0", features = ["derive"] } ctrlc = "3.2.2" diff --git a/end2end-tests/README.md b/end2end-tests/README.md index 383016dd8119da6d66388f6fd2b1c36001b7e3f1..4de191b66b4c5bec738c104a3fb35d73534777ec 100644 --- a/end2end-tests/README.md +++ b/end2end-tests/README.md @@ -117,6 +117,8 @@ For some scenarios, you may need to perform an action (When) that fails voluntar ### Run cucumber functional tests +The cucumber tests use the last debug binary in your `target` folder. Make sure this binary corresponds to the executable you want to test by running `cargo build` before. + To run the cucumber tests, you will need to have the rust toolchain installed locally. To run all the scenarios (there are many) use the command: `cargo cucumber` @@ -127,7 +129,10 @@ You can filter the `.feature` files to run with the option `i`, for instance: cargo cucumber -i monetary* ``` -Will only run `.feature` files that start with `"monetary"`. +will only run `.feature` files that start with `"monetary"`. + +The features will be tested in parallel and logs files will be written in the `end2end-tests` folder. +If you get an `Error: Timeout`, look at the logs to understand why Duniter did not launch successfully. ### Contribute to the code that runs the tests diff --git a/end2end-tests/cucumber-features/balance_transfer.feature b/end2end-tests/cucumber-features/balance_transfer.feature new file mode 100644 index 0000000000000000000000000000000000000000..5230f88cbab4a57cfb8742189df1d8589b9d2e02 --- /dev/null +++ b/end2end-tests/cucumber-features/balance_transfer.feature @@ -0,0 +1,6 @@ +Feature: Balance transfer + + Scenario: If alice sends 5 ÄžD to Dave, Dave will get 5 ÄžD + Given alice has 10 ÄžD + When alice sends 5 ÄžD to dave + Then dave should have 5 ÄžD diff --git a/end2end-tests/cucumber-features/certification.feature b/end2end-tests/cucumber-features/certification.feature new file mode 100644 index 0000000000000000000000000000000000000000..635af00786c0c26402ad5dd2900381284f79726a --- /dev/null +++ b/end2end-tests/cucumber-features/certification.feature @@ -0,0 +1,5 @@ +Feature: Certification + + Scenario: Dave certifies Alice + When dave certifies alice + Then alice should be certified by dave \ No newline at end of file diff --git a/end2end-tests/cucumber-features/identity_creation.feature b/end2end-tests/cucumber-features/identity_creation.feature new file mode 100644 index 0000000000000000000000000000000000000000..c8f37c5f0502153fb20e7d07aa6e5c59b6be9aa9 --- /dev/null +++ b/end2end-tests/cucumber-features/identity_creation.feature @@ -0,0 +1,5 @@ +Feature: Identity creation + + Scenario: alice invites a new memeber to join the web of trust + When alice creates identity for roumoulou + Then roumoulou identity should be created diff --git a/end2end-tests/cucumber-genesis/wot.json b/end2end-tests/cucumber-genesis/wot.json new file mode 100644 index 0000000000000000000000000000000000000000..15cf63708af746400c8354e942d561336b403f60 --- /dev/null +++ b/end2end-tests/cucumber-genesis/wot.json @@ -0,0 +1,66 @@ +{ + "first_ud": 1000, + "first_ud_reeval": 100, + "identities": { + "Alice": { + "balance": 1000, + "certs": ["Bob", "Charlie", "Dave"], + "pubkey": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + }, + "Bob": { + "balance": 1000, + "certs": ["Alice", "Charlie", "Dave"], + "pubkey": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" + }, + "Charlie": { + "balance": 1000, + "certs": ["Alice", "Bob"], + "pubkey": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" + }, + "Dave": { + "balance": 1000, + "certs": [], + "pubkey": "5CDrwUSbvQuC4GkRbzon5XmgqZZksUVvCGa1QFzjeLyZjVfs" + } + }, + "parameters": { + "babe_epoch_duration": 30, + "cert_period": 15, + "cert_max_by_issuer": 10, + "cert_min_received_cert_to_issue_cert": 2, + "cert_renewable_period": 50, + "cert_validity_period": 1000, + "idty_confirm_period": 40, + "idty_creation_period": 50, + "membership_period": 1000, + "membership_renewable_period": 50, + "pending_membership_period": 500, + "ud_creation_period": 10, + "ud_reeval_period": 100, + "smith_cert_period": 15, + "smith_cert_max_by_issuer": 8, + "smith_cert_min_received_cert_to_issue_cert": 2, + "smith_cert_renewable_period": 50, + "smith_cert_validity_period": 1000, + "smith_membership_period": 1000, + "smith_membership_renewable_period": 20, + "smith_pending_membership_period": 500, + "smiths_wot_first_cert_issuable_on": 20, + "smiths_wot_min_cert_for_membership": 2, + "wot_first_cert_issuable_on": 20, + "wot_min_cert_for_create_idty_right": 2, + "wot_min_cert_for_membership": 2 + }, + "smiths": { + "Alice": { + "certs": ["Bob", "Charlie"] + }, + "Bob": { + "certs": ["Alice", "Charlie"] + }, + "Charlie": { + "certs": ["Alice", "Bob"] + } + }, + "sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" +} diff --git a/end2end-tests/tests/common/cert.rs b/end2end-tests/tests/common/cert.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a344e3191c6401676dd74c2532c1a75ec4e9059 --- /dev/null +++ b/end2end-tests/tests/common/cert.rs @@ -0,0 +1,43 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency 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, version 3 of the License. +// +// Substrate-Libre-Currency 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use super::node_runtime::runtime_types::gdev_runtime; +use super::node_runtime::runtime_types::pallet_certification; +use super::*; +use sp_keyring::AccountKeyring; +use subxt::{sp_runtime::MultiAddress, PairSigner}; + +pub async fn certify( + api: &Api, + client: &Client, + from: AccountKeyring, + to: AccountKeyring, +) -> Result<()> { + let from = PairSigner::new(from.pair()); + let to = to.to_account_id(); + + let _events = create_block_with_extrinsic( + client, + api.tx() + .cert() + .add_cert(to.into()) + .create_signed(&from, ()) + .await?, + ) + .await?; + + Ok(()) +} diff --git a/end2end-tests/tests/common/mod.rs b/end2end-tests/tests/common/mod.rs index 078abd747617b2e2c04a6392e796d5e5ccd75507..40d90c2625c47882db09b64174fc8fc62de90366 100644 --- a/end2end-tests/tests/common/mod.rs +++ b/end2end-tests/tests/common/mod.rs @@ -17,10 +17,12 @@ #![allow(clippy::enum_variant_names, dead_code, unused_imports)] pub mod balances; +pub mod cert; #[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")] pub mod node_runtime {} +use anyhow::anyhow; use serde_json::Value; use sp_keyring::AccountKeyring; use std::io::prelude::*; @@ -30,9 +32,9 @@ use std::str::FromStr; use subxt::rpc::{rpc_params, ClientT, SubscriptionClientT}; use subxt::{ClientBuilder, DefaultConfig, DefaultExtra}; +pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; pub type Api = node_runtime::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>; pub type Client = subxt::Client<DefaultConfig>; -pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; pub type TransactionProgress<'client> = subxt::TransactionProgress<'client, DefaultConfig, node_runtime::DispatchError>; @@ -55,7 +57,7 @@ struct FullNode { } pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Api, Client, Process) { - println!("maybe_genesis_conf_file={:?}", maybe_genesis_conf_file); + // println!("maybe_genesis_conf_file={:?}", maybe_genesis_conf_file); let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH").unwrap_or_else(|_| { if std::path::Path::new(DUNITER_DOCKER_PATH).exists() { DUNITER_DOCKER_PATH.to_owned() diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs index 87b651cd8fe01fc7712868cf3f2d1b57c05dfce9..9e9d4ba69aa09e3bb3f6f050c44389369789f3be 100644 --- a/end2end-tests/tests/cucumber_tests.rs +++ b/end2end-tests/tests/cucumber_tests.rs @@ -28,6 +28,8 @@ use std::sync::{ Arc, }; +// ===== world ===== + #[derive(WorldInit)] pub struct DuniterWorld { ignore_errors: bool, @@ -119,6 +121,8 @@ fn parse_amount(amount: u64, unit: &str) -> (u64, bool) { } } +// ===== given ===== + #[given(regex = r"([a-zA-Z]+) ha(?:ve|s) (\d+) (ÄžD|cÄžD|UD|mUD)")] async fn who_have(world: &mut DuniterWorld, who: String, amount: u64, unit: String) -> Result<()> { // Parse inputs @@ -141,6 +145,8 @@ async fn who_have(world: &mut DuniterWorld, who: String, amount: u64, unit: Stri Ok(()) } +// ===== when ===== + #[when(regex = r"(\d+) blocks? later")] async fn n_blocks_later(world: &mut DuniterWorld, n: usize) -> Result<()> { for _ in 0..n { @@ -149,7 +155,7 @@ async fn n_blocks_later(world: &mut DuniterWorld, n: usize) -> Result<()> { Ok(()) } -#[when(regex = r"([a-zA-Z]+) send (\d+) (ÄžD|cÄžD|UD|mUD) to ([a-zA-Z]+)")] +#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ÄžD|cÄžD|UD|mUD) to ([a-zA-Z]+)")] async fn transfer( world: &mut DuniterWorld, from: String, @@ -175,7 +181,7 @@ async fn transfer( } } -#[when(regex = r"([a-zA-Z]+) sends all (?:his|her) (?:ÄžDs?|DUs?) to ([a-zA-Z]+)")] +#[when(regex = r"([a-zA-Z]+) sends? all (?:his|her) (?:ÄžDs?|DUs?) to ([a-zA-Z]+)")] async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Result<()> { // Parse inputs let from = AccountKeyring::from_str(&from).expect("unknown from"); @@ -184,13 +190,19 @@ async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Resu common::balances::transfer_all(world.api(), world.client(), from, to).await } -#[then(regex = r"([a-zA-Z]+) should have (\d+) (ÄžD|cÄžD)")] -async fn should_have( - world: &mut DuniterWorld, - who: String, - amount: u64, - unit: String, -) -> Result<()> { +#[when(regex = r"([a-zA-Z]+) certifies ([a-zA-Z]+)")] +async fn certifies(world: &mut DuniterWorld, from: String, to: String) -> Result<()> { + // Parse inputs + let from = AccountKeyring::from_str(&from).expect("unknown from"); + let to = AccountKeyring::from_str(&to).expect("unknown to"); + + common::cert::certify(world.api(), world.client(), from, to).await +} + +// ===== then ==== + +#[then(regex = r"([a-zA-Z]+) should have (\d+) ÄžD")] +async fn should_have(world: &mut DuniterWorld, who: String, amount: u64) -> Result<()> { // Parse inputs let who = AccountKeyring::from_str(&who) .expect("unknown to") @@ -232,6 +244,40 @@ async fn monetary_mass_should_be(world: &mut DuniterWorld, amount: u64, cents: u Ok(()) } +#[then(regex = r"([a-zA-Z]+) should be certified by ([a-zA-Z]+)")] +async fn should_be_certified_by( + world: &mut DuniterWorld, + receiver: String, + issuer: String, +) -> Result<()> { + // Parse inputs + let receiver_account = AccountKeyring::from_str(&receiver) + .expect("unknown to") + .to_account_id(); + let issuer_account = AccountKeyring::from_str(&issuer) + .expect("unknown to") + .to_account_id(); + + let _issuer_index = world + .api() + .storage() + .identity() + .identity_index_of(issuer_account, None) + .await?; + let _receiver_index = world + .api() + .storage() + .identity() + .identity_index_of(receiver_account, None) + .await?; + + // let certification = world.api().storage().cert().cer(issuer_index, receiver_index).await?.ok_or(anyhow!("no certification found from {} to {}", issuer, receiver)); + + Ok(()) +} + +// ============================================================ + #[derive(clap::Args)] struct CustomOpts { /// Keep running