use holochain::prelude::{DnaModifiersOpt, RoleSettings, Timestamp, YamlProperties};
use holochain::test_utils::itertools::Itertools;
use holochain::{prelude::AppBundleSource, sweettest::SweetConductor};
use holochain_client::{
AdminWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, ClientAgentSigner,
InstallAppPayload, InstalledAppId,
};
use holochain_conductor_api::{CellInfo, StorageBlob};
use holochain_types::websocket::AllowedOrigins;
use holochain_zome_types::prelude::ExternIO;
use kitsune_p2p_types::fixt::AgentInfoSignedFixturator;
use std::collections::BTreeSet;
use std::net::{Ipv4Addr, SocketAddr};
use std::{collections::HashMap, path::PathBuf};
const ROLE_NAME: &str = "foo";
#[tokio::test(flavor = "multi_thread")]
async fn app_interfaces() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect((Ipv4Addr::LOCALHOST, admin_port))
.await
.unwrap();
let app_interfaces = admin_ws.list_app_interfaces().await.unwrap();
assert_eq!(app_interfaces.len(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn signed_zome_call() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect((Ipv4Addr::LOCALHOST, admin_port))
.await
.unwrap();
let app_id: InstalledAppId = "test-app".into();
let installed_app = admin_ws
.install_app(InstallAppPayload {
agent_key: None,
installed_app_id: Some(app_id.clone()),
network_seed: None,
roles_settings: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let app_ws_port = admin_ws
.attach_app_interface(0, AllowedOrigins::Any, None)
.await
.unwrap();
let issued_token = admin_ws
.issue_app_auth_token(app_id.clone().into())
.await
.unwrap();
let signer = ClientAgentSigner::default();
let app_ws = AppWebsocket::connect(
(Ipv4Addr::LOCALHOST, app_ws_port),
issued_token.token,
signer.clone().into(),
)
.await
.unwrap();
let cells = installed_app.cell_info.into_values().next().unwrap();
let cell_id = match cells[0].clone() {
CellInfo::Provisioned(c) => c.cell_id,
_ => panic!("Invalid cell type"),
};
const TEST_ZOME_NAME: &str = "foo";
const TEST_FN_NAME: &str = "foo";
let credentials = admin_ws
.authorize_signing_credentials(AuthorizeSigningCredentialsPayload {
cell_id: cell_id.clone(),
functions: None,
})
.await
.unwrap();
signer.add_credentials(cell_id.clone(), credentials);
let response = app_ws
.call_zome(
cell_id.into(),
TEST_ZOME_NAME.into(),
TEST_FN_NAME.into(),
ExternIO::encode(()).unwrap(),
)
.await
.unwrap();
assert_eq!(
ExternIO::decode::<String>(&response).unwrap(),
TEST_FN_NAME.to_string()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn storage_info() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{}", admin_port))
.await
.unwrap();
let app_id: InstalledAppId = "test-app".into();
let agent_key = admin_ws.generate_agent_pub_key().await.unwrap();
admin_ws
.install_app(InstallAppPayload {
agent_key: Some(agent_key.clone()),
installed_app_id: Some(app_id.clone()),
network_seed: None,
roles_settings: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let storage_info = admin_ws.storage_info().await.unwrap();
let matched_storage_info = storage_info
.blobs
.iter()
.filter(|b| match b {
StorageBlob::Dna(dna_storage_info) => dna_storage_info.used_by.contains(&app_id),
})
.collect_vec();
assert_eq!(1, matched_storage_info.len());
}
#[tokio::test(flavor = "multi_thread")]
async fn dump_network_stats() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{}", admin_port))
.await
.unwrap();
let app_id: InstalledAppId = "test-app".into();
let agent_key = admin_ws.generate_agent_pub_key().await.unwrap();
admin_ws
.install_app(InstallAppPayload {
agent_key: Some(agent_key.clone()),
installed_app_id: Some(app_id.clone()),
network_seed: None,
roles_settings: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let network_stats = admin_ws.dump_network_stats().await.unwrap();
assert!(network_stats.contains("\"backend\": \"backendMem\""));
}
#[tokio::test(flavor = "multi_thread")]
async fn get_compatible_cells() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{}", admin_port))
.await
.unwrap();
let app_id: InstalledAppId = "test-app".into();
let agent_key = admin_ws.generate_agent_pub_key().await.unwrap();
let app_info = admin_ws
.install_app(InstallAppPayload {
agent_key: Some(agent_key.clone()),
installed_app_id: Some(app_id.clone()),
network_seed: None,
roles_settings: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
let cell_id = if let CellInfo::Provisioned(provisioned_cell) =
&app_info.cell_info.get(ROLE_NAME).unwrap()[0]
{
provisioned_cell.cell_id.clone()
} else {
panic!("expected provisioned cell")
};
let dna_hash = cell_id.dna_hash().clone();
let mut compatible_cells = admin_ws.get_compatible_cells(dna_hash).await.unwrap();
assert_eq!(
compatible_cells.len(),
1,
"compatible cells set should have 1 element"
);
let cell_1 = compatible_cells.pop_first().unwrap();
let mut expected_cells = BTreeSet::new();
expected_cells.insert(cell_id);
assert_eq!(
cell_1,
(app_id, expected_cells),
"only cell should be expected app cell"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn revoke_agent_key() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{}", admin_port))
.await
.unwrap();
let app_info = admin_ws
.install_app(InstallAppPayload {
agent_key: None,
installed_app_id: None,
network_seed: None,
roles_settings: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
let app_id = app_info.installed_app_id.clone();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let agent_key = app_info.agent_pub_key.clone();
let response = admin_ws
.revoke_agent_key(app_id.clone(), agent_key.clone())
.await
.unwrap();
assert_eq!(response, vec![]);
let response = admin_ws
.revoke_agent_key(app_id, agent_key.clone())
.await
.unwrap();
let cell_id = if let CellInfo::Provisioned(provisioned_cell) =
&app_info.cell_info.get(ROLE_NAME).unwrap()[0]
{
provisioned_cell.cell_id.clone()
} else {
panic!("expected provisioned cell")
};
assert!(matches!(&response[0], (cell, error) if *cell == cell_id && error.contains("invalid")));
}
#[tokio::test(flavor = "multi_thread")]
async fn agent_info() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{}", admin_port))
.await
.unwrap();
let app_id: InstalledAppId = "test-app".into();
let agent_key = admin_ws.generate_agent_pub_key().await.unwrap();
admin_ws
.install_app(InstallAppPayload {
agent_key: Some(agent_key.clone()),
installed_app_id: Some(app_id.clone()),
roles_settings: None,
network_seed: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let agent_infos = admin_ws.agent_info(None).await.unwrap();
assert_eq!(agent_infos.len(), 1);
let other_agent = fixt::fixt!(AgentInfoSigned);
admin_ws
.add_agent_info(vec![other_agent.clone()])
.await
.unwrap();
let agent_infos = admin_ws.agent_info(None).await.unwrap();
assert_eq!(agent_infos.len(), 2);
assert!(agent_infos.contains(&other_agent));
}
#[tokio::test(flavor = "multi_thread")]
async fn list_cell_ids() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{}", admin_port))
.await
.unwrap();
let app_id: InstalledAppId = "test-app".into();
let agent_key = admin_ws.generate_agent_pub_key().await.unwrap();
let app_info = admin_ws
.install_app(InstallAppPayload {
agent_key: Some(agent_key),
installed_app_id: Some(app_id.clone()),
roles_settings: None,
network_seed: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id).await.unwrap();
let cell_id =
if let CellInfo::Provisioned(cell) = &app_info.cell_info.get(ROLE_NAME).unwrap()[0] {
cell.cell_id.clone()
} else {
panic!("expected provisioned cell");
};
let cell_ids = admin_ws.list_cell_ids().await.unwrap();
assert!(cell_ids.contains(&cell_id));
}
#[tokio::test(flavor = "multi_thread")]
async fn install_app_with_roles_settings() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{}", admin_port))
.await
.unwrap();
let app_id: InstalledAppId = "test-app".into();
let agent_key = admin_ws.generate_agent_pub_key().await.unwrap();
let custom_network_seed = String::from("modified seed");
let custom_properties = YamlProperties::new(serde_yaml::Value::String(String::from(
"some properties provided at install time",
)));
let custom_origin_time = Timestamp::now();
let custom_quantum_time = std::time::Duration::from_secs(5 * 60);
let custom_modifiers = DnaModifiersOpt::default()
.with_network_seed(custom_network_seed.clone())
.with_origin_time(custom_origin_time)
.with_quantum_time(custom_quantum_time)
.with_properties(custom_properties.clone());
let role_settings = (
String::from("foo"),
RoleSettings::Provisioned {
membrane_proof: Default::default(),
modifiers: Some(custom_modifiers),
},
);
admin_ws
.install_app(InstallAppPayload {
agent_key: Some(agent_key.clone()),
installed_app_id: Some(app_id.clone()),
roles_settings: Some(HashMap::from([role_settings])),
network_seed: None,
source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")),
ignore_genesis_failure: false,
allow_throwaway_random_agent_key: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let app_info = admin_ws
.list_apps(None)
.await
.unwrap()
.first()
.unwrap()
.clone();
let manifest = app_info.manifest;
let app_role = manifest
.app_roles()
.into_iter()
.find(|r| r.name == "foo")
.unwrap();
assert_eq!(
app_role.dna.modifiers.network_seed,
Some(custom_network_seed)
);
assert_eq!(app_role.dna.modifiers.origin_time, Some(custom_origin_time));
assert_eq!(
app_role.dna.modifiers.quantum_time,
Some(custom_quantum_time)
);
assert_eq!(app_role.dna.modifiers.properties, Some(custom_properties));
}
#[tokio::test(flavor = "multi_thread")]
async fn connect_multiple_addresses() {
let conductor = SweetConductor::from_standard_config().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(
&[
SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 5000),
SocketAddr::new(Ipv4Addr::LOCALHOST.into(), admin_port),
][..],
)
.await
.unwrap();
let apps = admin_ws.list_apps(None).await.unwrap();
assert!(apps.is_empty());
}