Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ git2 = { version = "0.18", default-features = true, features = ["vendored-openss
glob = { version = "0.3.1", default-features = false }
log = { version = "0.4.20", default-features = false }
mockito = { version = "1.4.0", default-features = false }
wiremock = { version = "0.6.0", default-features = false }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried axum or mockito crate but it was super hard to assert on the payload send to the mockserver. So ended up using wiremock.

tar = { version = "0.4.40", default-features = false }
tempfile = { version = "3.10", default-features = false }
thiserror = { version = "1.0.58", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions crates/pop-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ contract-extrinsics.workspace = true
mockito.workspace = true
subxt.workspace = true
subxt-signer.workspace = true
wiremock.workspace = true

[features]
default = ["chain", "telemetry", "wasm-contracts"]
Expand Down
7 changes: 6 additions & 1 deletion crates/pop-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ fn cache() -> Result<PathBuf> {
fn init() -> Result<Option<Telemetry>> {
let maybe_config_path = config_file_path();

let maybe_tel = maybe_config_path.ok().map(|path| Telemetry::new(&path));
let maybe_tel = if let Ok(endpoint) = std::env::var("POP_TELEMETRY_ENDPOINT") {
// This is used in tests to set a mock endpoint.
maybe_config_path.ok().map(|path| Telemetry::init(endpoint, &path))
} else {
maybe_config_path.ok().map(|path| Telemetry::new(&path))
};

// Handle for await not used here as telemetry should complete before any of the commands do.
// Sends a generic ping saying the CLI was used.
Expand Down
39 changes: 21 additions & 18 deletions crates/pop-cli/tests/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#![cfg(feature = "chain")]

use anyhow::Result;
use assert_cmd::cargo::cargo_bin;
use common::{cleanup_telemetry_env, pop, MockTelemetry};
use pop_chains::{
up::{Binary, Source::GitHub},
ChainTemplate,
Expand All @@ -19,18 +19,22 @@ use pop_common::{
templates::Template,
};
use std::{
ffi::OsStr,
fs,
fs::write,
path::{Path, PathBuf},
process::Command,
time::Duration,
};
use strum::VariantArray;
use strum::{EnumMessage, VariantArray};

mod common;

// Test that all templates are generated correctly
#[test]
fn generate_all_the_templates() -> Result<()> {
#[tokio::test]
async fn generate_all_the_templates() -> Result<()> {
// Setup wiremock server and telemetry environment
let telemetry = MockTelemetry::new().await?;

let temp = tempfile::tempdir()?;
let temp_dir = temp.path();

Expand All @@ -51,22 +55,23 @@ fn generate_all_the_templates() -> Result<()> {
],
);
assert!(command.spawn()?.wait()?.success());
telemetry
.assert_latest_payload_structure("new chain", template.get_message().unwrap_or(""))
.await?;
assert!(temp_dir.join(parachain_name).exists());
}
cleanup_telemetry_env();
Ok(())
}

/// Test the parachain lifecycle: new, build, up, call.
#[tokio::test]
async fn parachain_lifecycle() -> Result<()> {
// For testing locally: set to `true`
const LOCAL_TESTING: bool = false;
// Setup wiremock server and telemetry environment
let telemetry = MockTelemetry::new().await?;

let temp = tempfile::tempdir()?;
let temp_dir = match LOCAL_TESTING {
true => Path::new("./"),
false => temp.path(),
};
let temp_dir = temp.path();

// pop new chain test_parachain --verify (default)
let working_dir = temp_dir.join("test_parachain");
Expand All @@ -87,6 +92,7 @@ async fn parachain_lifecycle() -> Result<()> {
],
);
assert!(command.spawn()?.wait()?.success());
telemetry.assert_latest_payload_structure("new chain", "Standard").await?;
assert!(working_dir.exists());
}

Expand Down Expand Up @@ -124,6 +130,7 @@ async fn parachain_lifecycle() -> Result<()> {
],
);
assert!(command.spawn()?.wait()?.success());
telemetry.assert_latest_payload_structure("build spec", "").await?;

// Assert build files have been generated
assert!(working_dir.join("target").exists());
Expand Down Expand Up @@ -205,6 +212,7 @@ rpc_port = {random_port}
],
);
assert!(command.spawn()?.wait()?.success());
telemetry.assert_latest_payload_structure("call chain", "").await?;

// pop call chain --call 0x00000411 --url ws://127.0.0.1:random_port --suri //Alice
// --skip-confirm
Expand All @@ -223,23 +231,18 @@ rpc_port = {random_port}
],
);
assert!(command.spawn()?.wait()?.success());
telemetry.assert_latest_payload_structure("call chain", "").await?;

assert!(up.try_wait()?.is_none(), "the process should still be running");
// Stop the process
up.kill()?;
up.wait()?;
Command::new("kill").args(["-s", "SIGINT", &up.id().to_string()]).spawn()?;

cleanup_telemetry_env();
Ok(())
}

fn pop(dir: &Path, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Command {
let mut command = Command::new(cargo_bin("pop"));
command.current_dir(dir).args(args);
println!("{command:?}");
command
}

// Function that mocks the build process generating the target dir and release.
fn mock_build_process(temp_dir: &Path) -> Result<()> {
// Create a target directory
Expand Down
97 changes: 97 additions & 0 deletions crates/pop-cli/tests/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use anyhow::Result;
use assert_cmd::cargo::cargo_bin;
use std::{ffi::OsStr, path::Path, process::Command as StdCommand};
use wiremock::{
matchers::{method, path},
Mock, MockGuard, MockServer, ResponseTemplate,
};

pub struct MockTelemetry {
pub telemetry_mock: MockGuard,
}

impl MockTelemetry {
pub async fn new() -> Result<Self> {
// Create a wiremock server
let mock_server = MockServer::start().await;
let server_url = mock_server.uri();

// Set up mock for /api/send endpoint
let telemetry_mock = Mock::given(method("POST"))
.and(path("/api/send"))
.respond_with(
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({}))
.append_header("content-type", "application/json"),
)
.mount_as_scoped(&mock_server)
.await;

let endpoint = server_url.clone() + "/api/send";
std::env::set_var("POP_TELEMETRY_ENDPOINT", endpoint);
std::env::remove_var("DO_NOT_TRACK");
std::env::remove_var("CI");

Ok(Self { telemetry_mock })
}

async fn parse_payload_from_request(
&self,
request_index: Option<usize>,
) -> Result<(String, String)> {
// Get the received requests from wiremock
let requests = self.telemetry_mock.received_requests().await;

let request = match request_index {
Some(index) => {
assert!(
index < requests.len(),
"Request index {} out of range (got {} requests)",
index,
requests.len()
);
&requests[index]
},
None => {
assert!(!requests.is_empty(), "No requests received");
requests.last().unwrap()
},
};

let body = String::from_utf8(request.body.clone()).unwrap_or_default();

// Parse the JSON body
let payload: serde_json::Value = serde_json::from_str(&body)
.map_err(|e| anyhow::anyhow!("Failed to parse request body as JSON: {}", e))?;

let actual_name = payload["payload"]["name"].as_str().unwrap_or("").to_string();
let actual_data = payload["payload"]["data"].as_str().unwrap_or("").to_string();

Ok((actual_name, actual_data))
}

pub async fn assert_latest_payload_structure(
&self,
expected_name: &str,
expected_data: &str,
) -> Result<()> {
let (actual_name, actual_data) = self.parse_payload_from_request(None).await?;

assert_eq!(actual_name, expected_name);
assert_eq!(actual_data, expected_data);

Ok(())
}
}

pub fn cleanup_telemetry_env() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we get back the variables removed in new?

std::env::remove_var("DO_NOT_TRACK");
std::env::remove_var("CI");

std::env::remove_var("POP_TELEMETRY_ENDPOINT");
}

/// Create a `pop` command configured for `dir` with given `args`.
pub fn pop(dir: &Path, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> StdCommand {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did this refactor in the tests, once you merge all changes you can take it from: https://github.com/r0gue-io/pop-cli/blob/main/crates/pop-common/src/lib.rs#L94

let mut command = StdCommand::new(cargo_bin("pop"));
command.current_dir(dir).args(args);
println!("{command:?}");
command
}
Loading
Loading