Skip to content

Commit

Permalink
First iteration of e2e tests (#183)
Browse files Browse the repository at this point in the history
* e2e tests wip

* more wip

* first version of a few e2e tests

* slight refactor to tidy up tests from helper logic

* rename tests

* added readme

* added a message to assertion, use json! macro and regroup uses
  • Loading branch information
jloleysens authored Jun 2, 2022
1 parent d368746 commit 9cb5c97
Show file tree
Hide file tree
Showing 8 changed files with 490 additions and 97 deletions.
346 changes: 251 additions & 95 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ documentation = "https://docs.agent-cli.animo.id"
repository = "https://github.com/animo/agent-cli"
categories = ["command-line-utilities"]
license = "Apache-2.0"
exclude = ["pkg", "Makefile", "example"]
exclude = ["pkg", "Makefile", "example", "tests"]
edition = "2021"

[workspace]
Expand All @@ -39,3 +39,7 @@ serde_yaml = "0.8.23"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
lazy_static = "1.4.0"

[dev-dependencies]
speculoos = "0.9.0"
regex = "1.5.6"
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM rust:1.61.0

RUN apt-get update -y
RUN apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev -y

COPY ./crates ./crates
COPY ./Cargo.toml ./Cargo.lock ./

RUN cargo build --release --locked

# Default command, overridden in other places
CMD ./target/release/agent-cli --version

6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ lint:
build:
cargo build --release

test:
test:
bash ./tests/run.sh

# It is important that e2e tests are run serially on a single thread
e2e-test:
cargo test -- --test-threads=1

install:
cargo install --path .
40 changes: 40 additions & 0 deletions docker/docker-compose.acapy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# docker-compose to start a local aca-py in multi tenant mode.
# Runs with default SQLite database and --no-ledger to optimize for quick startup time.
# To shut down the services run `docker-compose rm` - this will retain the postgres database, so you can change aca-py startup parameters
# and restart the docker containers without losing your wallet data.
# If you want to delete your wallet data just run `docker volume ls -q | xargs docker volume rm`.
version: "3"
services:
vcr-agent:
image: bcgovimages/aries-cloudagent:py36-1.16-1_0.7.3
ports:
- 8010:8010
entrypoint: /bin/bash
command: [
"-c",
"sleep 5; \
aca-py start \
--auto-provision \
--inbound-transport http '0.0.0.0' 8001 \
--endpoint 'http://host.docker.internal:8001' \
--outbound-transport http \
--auto-accept-invites \
--auto-accept-requests \
--auto-ping-connection \
--auto-respond-messages \
--auto-respond-credential-proposal \
--auto-respond-credential-offer \
--auto-respond-credential-request \
--auto-verify-presentation \
--multitenant \
--multitenant-admin \
--jwt-secret 'Something very secret' \
--wallet-type 'indy' \
--no-ledger \
--wallet-name 'acapy_agent_wallet' \
--wallet-key 'key' \
--admin '0.0.0.0' 8010 \
--admin-insecure-mode \
--label 'tester_agent' \
--log-level 'info' ",
]
46 changes: 46 additions & 0 deletions tests/e2e.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#![cfg(test)]

use agent::modules::connection::Connection;
use regex::Regex;
use speculoos::prelude::*;
mod helpers;
use helpers::run_test;

#[tokio::test]
async fn smoke() -> () {
run_test(|agent_cli| {
let re = Regex::new(r"^agent-cli \d\.\d.\d").unwrap();
assert_that(&agent_cli.exec(&["--version"])).matches(|v| re.is_match(v))
})
.await
}

#[tokio::test]
async fn create_a_connection_and_list_connections() -> () {
run_test(|agent_cli| {
let connections = agent_cli.exec(&["connection", "list"]);
assert_that(&connections).is_equal_to(&String::from("[]"));
let _ = agent_cli.exec(&["connection", "invite", "--alias", "test"]);
let connections_str = agent_cli.exec(&["connection", "list"]);
let connections: Vec<Connection> = serde_json::from_str(&connections_str).unwrap();
assert_that(&connections).has_length(1);
})
.await;
}

#[tokio::test]
async fn create_a_connection_and_send_a_message() -> () {
run_test(|agent_cli| {
let _ = agent_cli.exec(&["connection", "invite", "--alias", "foo"]);
let connections_str = agent_cli.exec(&["connection", "list"]);
let connection: Vec<Connection> = serde_json::from_str(&connections_str).unwrap();
let result = agent_cli.exec(&[
"message",
"--connection-id",
&connection[0].connection_id,
"--message=BAR",
]);
assert_that(&result).is_equal_to(String::from(""))
})
.await
}
9 changes: 9 additions & 0 deletions tests/e2e_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
In order to run the e2e tests you need to start a local aca-py instance. This
can be done by executing the following in the repo root:

```sh
docker-compose -f ./docker/docker-compose.acapy.yml up
```

As a future enhancement to e2e we can have the test harness set up the entire
environment for us.
121 changes: 121 additions & 0 deletions tests/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::env;
use std::panic;
use std::process::Command;

/// Helper function which does test set up and teardown
pub async fn run_test<T>(test: T) -> ()
where
T: FnOnce(TestAgentCli) -> () + panic::UnwindSafe,
{
let (agent_cli, wallet_id) = setup().await;
let result = panic::catch_unwind(|| test(agent_cli));
teardown(wallet_id).await;
assert!(result.is_ok(), "Test execution failed")
}

fn get_agent_url() -> String {
match env::var("AGENT_URL") {
Ok(v) => v,
Err(_) => String::from("http://localhost:8010"),
}
}

/// TODO: This struct should be moved because this functionality should be
/// provided by the CLI
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CreateWalletResponse {
token: String,
wallet_id: String,
}

/// TODO: Rework this once we have the ability to create a sub-wallet from the CLI.
/// We should just be using the CLI directly.
async fn setup() -> (TestAgentCli, String) {
let body: Value = json!(
{
"image_url": "https://aries.ca/images/sample.png",
"key_management_mode": "managed",
"label": "Alice",
"wallet_dispatch_type": "default",
"wallet_key": "MySecretKey123",
"wallet_name": "MyCoolName",
"wallet_type": "indy",
"wallet_webhook_urls": [
"http://localhost:8022/webhooks"
]
});
let url = format!("{}/multitenancy/wallet", get_agent_url());
let client = Client::new()
.post(url)
.header("content-type", "application/json")
.json(&body);
let json = match client.send().await {
Ok(res) => res.json::<CreateWalletResponse>().await.unwrap(),
Err(e) => panic!("Setup failed {}", e),
};
(TestAgentCli::new(json.token), json.wallet_id)
}

/// TODO: Rework this once we have the ability to create a sub-wallet from the CLI.
/// We should just be using the CLI directly.
async fn teardown(wallet_id: String) {
let url = format!(
"{}/multitenancy/wallet/{}/remove",
get_agent_url(),
wallet_id
);
let client = Client::new()
.post(url)
.header("content-type", "application/json");
let res = match client.send().await {
Ok(res) => res,
Err(e) => panic!("Setup failed {}", e),
};

assert!(res.status().as_u16() < 399, "bad status for wallet removal")
}

/// A test utility that wraps common args we want to pass to every command
/// we give to the agent as well as handling of process stdout and stderr.
pub struct TestAgentCli {
token: String,
}

impl TestAgentCli {
pub fn new(token: String) -> Self {
TestAgentCli { token }
}

pub fn exec(&self, args: &[&str]) -> String {
let agent_url = get_agent_url();
let mut all_args = vec![
"run",
"--quiet",
"--",
"--agent-url",
&agent_url,
"--token",
&self.token,
];
all_args.extend(args.to_vec());
let result = Command::new("cargo").args(&all_args).output();
let output = match result {
Ok(o) => o,
Err(e) => panic!("Command failed \"{:?}\" with \"{}\"", &all_args, e),
};
if !output.status.success() {
println!();
println!("=============================");
println!("Command failed: {:?}", &all_args);
println!("{}", String::from_utf8_lossy(&output.stderr));
println!("=============================");
println!();
panic!("Test failed!");
}
let string_output = String::from_utf8(output.stdout).unwrap();
String::from(string_output.trim())
}
}

0 comments on commit 9cb5c97

Please sign in to comment.