use common::make_agent;
use holochain::prelude::{DnaModifiersOpt, RoleSettings, 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 kitsune2_api::Url;
use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration;
mod common;
mod fixture;
const ROLE_NAME: &str = "foo";
#[tokio::test(flavor = "multi_thread")]
async fn app_interfaces() {
let conductor = SweetConductor::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect((Ipv4Addr::LOCALHOST, admin_port), None)
.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::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect((Ipv4Addr::LOCALHOST, admin_port), None)
.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::Bytes(fixture::get_fixture_app_bundle()),
ignore_genesis_failure: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let app_ws_port = admin_ws
.attach_app_interface(0, None, 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(),
None,
)
.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::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{admin_port}"), None)
.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::Bytes(fixture::get_fixture_app_bundle()),
ignore_genesis_failure: 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::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{admin_port}"), None)
.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::Bytes(fixture::get_fixture_app_bundle()),
ignore_genesis_failure: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let network_stats = admin_ws.dump_network_stats().await.unwrap();
#[cfg(feature = "transport-tx5-backend-go-pion")]
assert_eq!("BackendGoPion", network_stats.transport_stats.backend);
#[cfg(feature = "transport-iroh")]
assert_eq!("iroh", network_stats.transport_stats.backend);
}
#[tokio::test(flavor = "multi_thread")]
async fn agent_info() {
let conductor = SweetConductor::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{admin_port}"), None)
.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::Bytes(fixture::get_fixture_app_bundle()),
ignore_genesis_failure: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).await.unwrap();
let agent_infos = tokio::time::timeout(Duration::from_secs(5), async {
loop {
let agent_infos = admin_ws.agent_info(None).await.unwrap();
if agent_infos.len() == 1 {
return agent_infos;
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
})
.await
.expect("agent info didn't make it to the peer store");
let space = kitsune2_api::AgentInfoSigned::decode(
&kitsune2_core::Ed25519Verifier,
agent_infos[0].as_bytes(),
)
.unwrap()
.space
.clone();
let other_agent = make_agent(&space);
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 peer_meta_info() {
let conductor = SweetConductor::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{admin_port}"), None)
.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::Bytes(fixture::get_fixture_app_bundle()),
ignore_genesis_failure: 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 dna_hash = match app_info.cell_info.first().unwrap().1.first().unwrap() {
CellInfo::Provisioned(c) => c.cell_id.dna_hash().clone(),
_ => panic!("Wrong CellInfo type."),
};
let url = Url::from_str("ws://test.com:80/test-url").unwrap();
let response = admin_ws.peer_meta_info(url.clone(), None).await.unwrap();
assert_eq!(response.len(), 1);
let meta_infos = response
.get(&dna_hash)
.expect("No meta infos found for dna hash.");
assert_eq!(meta_infos.len(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn install_app_then_list_apps_and_list_cell_ids() {
let conductor = SweetConductor::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{admin_port}"), None)
.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::Bytes(fixture::get_fixture_app_bundle()),
ignore_genesis_failure: false,
})
.await
.unwrap();
admin_ws.enable_app(app_id.clone()).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_eq!(cell_ids.len(), 1);
assert!(cell_ids.contains(&cell_id));
let app_infos = admin_ws.list_apps(None).await.unwrap();
assert_eq!(app_infos.len(), 1);
assert!(app_infos.iter().any(|a| a.installed_app_id == app_id));
}
#[tokio::test(flavor = "multi_thread")]
async fn install_app_with_roles_settings() {
let conductor = SweetConductor::standard().await;
let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap();
let admin_ws = AdminWebsocket::connect(format!("127.0.0.1:{admin_port}"), None)
.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(yaml_serde::Value::String(String::from(
"some properties provided at install time",
)));
let custom_modifiers = DnaModifiersOpt::default()
.with_network_seed(custom_network_seed.clone())
.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::Bytes(fixture::get_fixture_app_bundle()),
ignore_genesis_failure: 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.properties, Some(custom_properties));
}
#[tokio::test(flavor = "multi_thread")]
async fn connect_multiple_addresses() {
let conductor = SweetConductor::standard().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),
][..],
None,
)
.await
.unwrap();
let apps = admin_ws.list_apps(None).await.unwrap();
assert!(apps.is_empty());
}