use crate::{conductor::error::ConductorError, sweettest::*};
use holochain_types::prelude::*;
use holochain_wasm_test_utils::TestWasm;
#[cfg(feature = "unstable-migration")]
use maplit::btreeset;
use matches::assert_matches;
#[cfg(feature = "unstable-migration")]
use std::collections::BTreeSet;
use std::collections::HashMap;
#[tokio::test(flavor = "multi_thread")]
async fn clone_only_provisioning_creates_no_cell_and_allows_cloning() {
holochain_trace::test_run();
let mut conductor = SweetConductor::from_standard_config().await;
async fn make_payload(clone_limit: u32) -> InstallAppPayload {
let (dna, _, _) = SweetDnaFile::unique_from_test_wasms(vec![
TestWasm::GenesisSelfCheckRequiresProperties,
])
.await;
let path = format!("{}", dna.dna_hash());
let modifiers = DnaModifiersOpt::none();
let roles = vec![AppRoleManifest {
name: "name".into(),
dna: AppRoleDnaManifest {
path: Some(path.clone()),
modifiers: modifiers.clone(),
installed_hash: None,
clone_limit,
},
provisioning: Some(CellProvisioning::CloneOnly),
}];
let manifest = AppManifestCurrentBuilder::default()
.name("test_app".into())
.description(None)
.roles(roles)
.build()
.unwrap();
let dna_bundle = DnaBundle::from_dna_file(dna.clone()).unwrap();
let resources = vec![(path.clone(), dna_bundle)];
let bundle = AppBundle::new(manifest.clone().into(), resources).unwrap();
let bundle_bytes = bundle.pack().unwrap();
InstallAppPayload {
agent_key: None,
source: AppBundleSource::Bytes(bundle_bytes),
installed_app_id: Some("app_1".into()),
network_seed: None,
roles_settings: Default::default(),
ignore_genesis_failure: false,
}
}
assert_matches!(
conductor
.clone()
.install_app_bundle(make_payload(0).await)
.await
.unwrap_err(),
ConductorError::AppBundleError(AppBundleError::AppManifestError(
AppManifestError::InvalidStrategyCloneOnly(_)
))
);
{
let app = conductor
.clone()
.install_app_bundle(make_payload(1).await)
.await
.unwrap();
assert_eq!(app.all_cells().count(), 0);
assert_eq!(app.role_assignments().len(), 1);
}
{
let clone_cell = conductor
.create_clone_cell(
&"app_1".into(),
CreateCloneCellPayload {
role_name: "name".into(),
modifiers: DnaModifiersOpt::none()
.with_network_seed("1".into())
.with_properties(YamlProperties::new(serde_yaml::Value::String(
"foo".into(),
))),
membrane_proof: None,
name: Some("Johnny".into()),
},
)
.await
.unwrap();
let state = conductor.get_state().await.unwrap();
let app = state.get_app(&"app_1".to_string()).unwrap();
assert_eq!(clone_cell.name, "Johnny".to_string());
assert_eq!(app.role_assignments().len(), 1);
assert_eq!(app.clone_cells().count(), 1);
}
{
let err = conductor
.create_clone_cell(
&"app_1".into(),
CreateCloneCellPayload {
role_name: "name".into(),
modifiers: DnaModifiersOpt::none()
.with_network_seed("1".into())
.with_properties(YamlProperties::new(serde_yaml::Value::String(
"foo".into(),
))),
membrane_proof: None,
name: None,
},
)
.await
.unwrap_err();
assert_matches!(
err,
ConductorError::AppError(AppError::CloneLimitExceeded(1, _))
);
let state = conductor.get_state().await.unwrap();
let app = state.get_app(&"app_1".to_string()).unwrap();
assert_eq!(app.all_cells().count(), 1);
}
}
#[tokio::test(flavor = "multi_thread")]
async fn reject_duplicate_app_for_same_agent() {
let conductor = SweetConductor::from_standard_config().await;
let (dna, _, _) = SweetDnaFile::unique_from_test_wasms(vec![TestWasm::Create]).await;
let path = format!("{}", dna.dna_hash());
let modifiers = DnaModifiersOpt::none();
let roles = vec![AppRoleManifest {
name: "name".into(),
dna: AppRoleDnaManifest {
path: Some(path.clone()),
modifiers: modifiers.clone(),
installed_hash: None,
clone_limit: 0,
},
provisioning: Some(CellProvisioning::Create { deferred: false }),
}];
let manifest = AppManifestCurrentBuilder::default()
.name("test_app".into())
.description(None)
.roles(roles)
.build()
.unwrap();
let resources = vec![(path.clone(), DnaBundle::from_dna_file(dna.clone()).unwrap())];
let bundle = AppBundle::new(manifest.clone().into(), resources).unwrap();
let bundle_bytes = bundle.pack().unwrap();
let app = conductor
.clone()
.install_app_bundle(InstallAppPayload {
agent_key: None,
source: AppBundleSource::Bytes(bundle_bytes),
installed_app_id: Some("app_1".into()),
network_seed: None,
roles_settings: Default::default(),
ignore_genesis_failure: false,
})
.await
.unwrap();
let alice = app.agent_key().clone();
let cell_id = CellId::new(dna.dna_hash().to_owned(), app.agent_key().clone());
let resources = vec![(path.clone(), DnaBundle::from_dna_file(dna.clone()).unwrap())];
let bundle = AppBundle::new(manifest.clone().into(), resources).unwrap();
let bundle_bytes = bundle.pack().unwrap();
let duplicate_install_with_app_disabled = conductor
.clone()
.install_app_bundle(InstallAppPayload {
source: AppBundleSource::Bytes(bundle_bytes),
agent_key: Some(alice.clone()),
installed_app_id: Some("app_2".into()),
roles_settings: Default::default(),
ignore_genesis_failure: false,
network_seed: None,
})
.await;
assert_matches!(
duplicate_install_with_app_disabled.unwrap_err(),
ConductorError::CellAlreadyExists(id) if id == cell_id
);
conductor.enable_app("app_1".into()).await.unwrap();
let resources = vec![(path.clone(), DnaBundle::from_dna_file(dna.clone()).unwrap())];
let bundle = AppBundle::new(manifest.clone().into(), resources).unwrap();
let bundle_bytes = bundle.pack().unwrap();
let duplicate_install_with_app_enabled = conductor
.clone()
.install_app_bundle(InstallAppPayload {
source: AppBundleSource::Bytes(bundle_bytes),
agent_key: Some(alice.clone()),
installed_app_id: Some("app_2".into()),
roles_settings: Default::default(),
ignore_genesis_failure: false,
network_seed: None,
})
.await;
assert_matches!(
duplicate_install_with_app_enabled.unwrap_err(),
ConductorError::CellAlreadyExists(id) if id == cell_id
);
let resources = vec![(path, DnaBundle::from_dna_file(dna.clone()).unwrap())];
let bundle = AppBundle::new(manifest.into(), resources).unwrap();
let bundle_bytes = bundle.pack().unwrap();
let valid_install_of_second_app = conductor
.clone()
.install_app_bundle(InstallAppPayload {
source: AppBundleSource::Bytes(bundle_bytes),
agent_key: Some(alice.clone()),
installed_app_id: Some("app_2".into()),
roles_settings: Default::default(),
ignore_genesis_failure: false,
network_seed: Some("network".into()),
})
.await;
assert!(valid_install_of_second_app.is_ok());
}
#[cfg(feature = "unstable-migration")]
#[tokio::test(flavor = "multi_thread")]
async fn cells_by_dna_lineage() {
let mut conductor = SweetConductor::from_standard_config().await;
async fn mk_dna(lineage: &[&DnaHash]) -> DnaFile {
let (dna, _, _) = SweetDnaFile::unique_from_test_wasms(vec![TestWasm::Create]).await;
let (def, code) = dna.into_parts();
let mut def = def.into_content();
def.lineage = lineage.iter().map(|h| (**h).to_owned()).collect();
DnaFile::from_parts(def.into_hashed(), code)
}
let dna1 = mk_dna(&[]).await;
let dna2 = mk_dna(&[dna1.dna_hash()]).await;
let dna3 = mk_dna(&[dna1.dna_hash(), dna2.dna_hash()]).await;
let dna4 = mk_dna(&[dna2.dna_hash(), dna3.dna_hash()]).await;
let dnax = mk_dna(&[]).await;
let app1 = conductor.setup_app("app1", [&dna1, &dnax]).await.unwrap();
let app2 = conductor.setup_app("app2", [&dna2]).await.unwrap();
let app3 = conductor.setup_app("app3", [&dna3]).await.unwrap();
let app4 = conductor.setup_app("app4", [&dna4]).await.unwrap();
let lin1 = conductor
.cells_by_dna_lineage(dna1.dna_hash())
.await
.unwrap();
let lin2 = conductor
.cells_by_dna_lineage(dna2.dna_hash())
.await
.unwrap();
let lin3 = conductor
.cells_by_dna_lineage(dna3.dna_hash())
.await
.unwrap();
let lin4 = conductor
.cells_by_dna_lineage(dna4.dna_hash())
.await
.unwrap();
let linx = conductor
.cells_by_dna_lineage(dnax.dna_hash())
.await
.unwrap();
fn app_cells(app: &SweetApp, indices: &[usize]) -> (String, BTreeSet<CellId>) {
(
app.installed_app_id().clone(),
indices
.iter()
.map(|i| app.cells()[*i].cell_id().clone())
.collect(),
)
}
pretty_assertions::assert_eq!(
lin1,
btreeset![
app_cells(&app1, &[0]),
app_cells(&app2, &[0]),
app_cells(&app3, &[0]),
]
);
pretty_assertions::assert_eq!(
lin2,
btreeset![
app_cells(&app2, &[0]),
app_cells(&app3, &[0]),
app_cells(&app4, &[0]),
]
);
pretty_assertions::assert_eq!(
lin3,
btreeset![
app_cells(&app3, &[0]),
app_cells(&app4, &[0]),
]
);
pretty_assertions::assert_eq!(
lin4,
btreeset![
app_cells(&app4, &[0]),
]
);
pretty_assertions::assert_eq!(linx, btreeset![app_cells(&app1, &[1]),]);
}
#[tokio::test(flavor = "multi_thread")]
async fn use_existing_integration() {
let conductor = SweetConductor::from_standard_config().await;
let (dna1, _, _) = SweetDnaFile::unique_from_test_wasms(vec![TestWasm::WhoAmI]).await;
let (dna2, _, _) = SweetDnaFile::unique_from_test_wasms(vec![TestWasm::WhoAmI]).await;
let bundle1 = {
let path = format!("{}", dna1.dna_hash());
let roles = vec![AppRoleManifest {
name: "created".into(),
dna: AppRoleDnaManifest {
path: Some(path.clone()),
modifiers: DnaModifiersOpt::none(),
installed_hash: None,
clone_limit: 0,
},
provisioning: Some(CellProvisioning::Create { deferred: false }),
}];
let manifest = AppManifestCurrentBuilder::default()
.name("test_app".into())
.description(None)
.roles(roles)
.build()
.unwrap();
let resources = vec![(
path.clone(),
DnaBundle::from_dna_file(dna1.clone()).unwrap(),
)];
AppBundle::new(manifest.clone().into(), resources)
.unwrap()
.pack()
.unwrap()
};
let bundle2 = |correct: bool| {
let dna2 = dna2.clone();
async move {
let path = format!("{}", dna2.dna_hash());
let installed_hash = if correct {
Some(dna2.dna_hash().clone().into())
} else {
None
};
let roles = vec![
AppRoleManifest {
name: "created".into(),
dna: AppRoleDnaManifest {
path: Some(path.clone()),
modifiers: DnaModifiersOpt::none(),
installed_hash: None,
clone_limit: 0,
},
provisioning: Some(CellProvisioning::Create { deferred: false }),
},
AppRoleManifest {
name: "extant".into(),
dna: AppRoleDnaManifest {
path: None,
modifiers: DnaModifiersOpt::none(),
installed_hash,
clone_limit: 0,
},
#[allow(deprecated)]
provisioning: Some(CellProvisioning::UseExisting { protected: true }),
},
];
let manifest = AppManifestCurrentBuilder::default()
.name("test_app".into())
.description(None)
.roles(roles)
.build()
.unwrap();
let resources = vec![(
path.clone(),
DnaBundle::from_dna_file(dna2.clone()).unwrap(),
)];
AppBundle::new(manifest.clone().into(), resources)
.unwrap()
.pack()
.unwrap()
}
};
let app_1 = conductor
.clone()
.install_app_bundle(InstallAppPayload {
agent_key: None,
source: AppBundleSource::Bytes(bundle1),
installed_app_id: Some("app_1".into()),
network_seed: None,
roles_settings: Default::default(),
ignore_genesis_failure: false,
})
.await
.unwrap();
{
let err = conductor
.clone()
.install_app_bundle(InstallAppPayload {
agent_key: None,
source: AppBundleSource::Bytes(bundle2(false).await),
installed_app_id: Some("app_2".into()),
network_seed: None,
roles_settings: Default::default(),
ignore_genesis_failure: false,
})
.await
.unwrap_err();
assert!(matches!(
err,
ConductorError::AppBundleError(AppBundleError::AppManifestError(_))
));
}
{
let err = conductor
.clone()
.install_app_bundle(InstallAppPayload {
agent_key: None,
source: AppBundleSource::Bytes(bundle2(true).await),
installed_app_id: Some("app_2".into()),
network_seed: None,
roles_settings: Default::default(),
ignore_genesis_failure: false,
})
.await
.unwrap_err();
assert!(matches!(
err,
ConductorError::AppBundleError(AppBundleError::CellResolutionFailure(_, _))
));
}
let cell_id = app_1
.all_cells()
.collect::<Vec<CellId>>()
.first()
.unwrap()
.to_owned();
#[allow(deprecated)]
let role_settings = ("extant".into(), RoleSettings::UseExisting { cell_id });
let app_2 = conductor
.clone()
.install_app_bundle(InstallAppPayload {
agent_key: None,
source: AppBundleSource::Bytes(bundle2(true).await),
installed_app_id: Some("app_2".into()),
network_seed: None,
roles_settings: Some(HashMap::from([role_settings])),
ignore_genesis_failure: false,
})
.await
.unwrap();
let cell_id_1 = app_1.all_cells().next().unwrap().clone();
let cell_id_2 = app_2.all_cells().next().unwrap().clone();
let zome2 = SweetZome::new(cell_id_2.clone(), "whoami".into());
conductor.enable_app("app_1".into()).await.unwrap();
conductor.enable_app("app_2".into()).await.unwrap();
{
let r: Result<AgentInfo, _> = conductor
.call_fallible(&zome2, "who_are_they_role", "extant".to_string())
.await;
assert!(r.is_err());
}
{
let secret = CapSecret::from([1; 64]);
conductor
.grant_zome_call_capability(GrantZomeCallCapabilityPayload {
cell_id: cell_id_1.clone(),
cap_grant: ZomeCallCapGrant {
tag: "tag".into(),
access: CapAccess::Transferable { secret },
functions: GrantedFunctions::All,
},
})
.await
.unwrap();
let r: AgentInfo = conductor
.call_from_fallible(
cell_id_2.agent_pubkey(),
None,
&zome2,
"who_are_they_role_secret",
("extant".to_string(), Some(secret)),
)
.await
.unwrap();
assert_eq!(r.agent_initial_pubkey, *cell_id_1.agent_pubkey());
}
conductor
.disable_app("app_1".into(), DisabledAppReason::User)
.await
.unwrap();
conductor
.disable_app("app_2".into(), DisabledAppReason::User)
.await
.unwrap();
conductor
.disable_app("app_1".into(), DisabledAppReason::User)
.await
.unwrap();
let err = conductor
.clone()
.uninstall_app(&"app_1".to_string(), false)
.await
.unwrap_err();
assert_matches!(
err,
ConductorError::AppHasDependents(a, b) if a == *"app_1" && b == vec!["app_2".to_string()]
);
conductor
.clone()
.uninstall_app(&"app_1".to_string(), true)
.await
.unwrap();
}