use std::sync::Arc;
use aion::{Engine, EngineBuilder};
use aion_store::visibility::{ListWorkflowsFilter, VisibilityStore};
use aion_store::{EventStore, InMemoryStore};
use super::super::document::EMBEDDED_UPDATE_CHECK_DOCUMENT;
use super::super::document::EmbeddedUpdateCheck;
use super::*;
type TestResult = Result<(), Box<dyn std::error::Error>>;
async fn engine() -> Result<(Arc<Engine>, Arc<dyn VisibilityStore>), Box<dyn std::error::Error>> {
let backing = Arc::new(InMemoryStore::default());
let store: Arc<dyn EventStore> = backing.clone();
let visibility: Arc<dyn VisibilityStore> = backing;
let engine = Arc::new(
EngineBuilder::new()
.store_arc(store)
.visibility_store_arc(Arc::clone(&visibility))
.scheduler_threads(1)
.build()
.await?,
);
Ok((engine, visibility))
}
#[tokio::test]
async fn a_fresh_catalog_gets_the_document_installed_routed_and_nothing_started() -> TestResult {
let (engine, visibility) = engine().await?;
let embedded = EmbeddedUpdateCheck::load()?;
let executions_before = visibility
.count_workflows(ListWorkflowsFilter::default())
.await?;
let outcome = install_embedded_update_check(engine.as_ref()).await;
assert_eq!(
outcome,
UpdateCheckInstall::Installed {
workflow_type: embedded.workflow_type().to_owned(),
content_hash: embedded.content_hash().to_string(),
},
"a fresh catalog must be claimed"
);
let routed: Vec<_> = engine
.list_workflow_versions()?
.into_iter()
.filter(|version| version.workflow_type == embedded.workflow_type() && version.route_active)
.collect();
assert_eq!(routed.len(), 1, "exactly one version holds the route");
assert_eq!(
routed[0].content_hash.to_string(),
embedded.content_hash().to_string()
);
let executions_after = visibility
.count_workflows(ListWorkflowsFilter::default())
.await?;
assert_eq!(
executions_after, executions_before,
"a boot install must start nothing — every check is an explicit operator act"
);
let check_runs = visibility
.count_workflows(ListWorkflowsFilter {
workflow_type: Some(embedded.workflow_type().to_owned()),
..ListWorkflowsFilter::default()
})
.await?;
assert_eq!(check_runs, 0, "no update-check execution may exist at boot");
Ok(())
}
#[tokio::test]
async fn a_second_install_reports_already_current_and_changes_nothing() -> TestResult {
let (engine, _visibility) = engine().await?;
let embedded = EmbeddedUpdateCheck::load()?;
let first = install_embedded_update_check(engine.as_ref()).await;
assert!(matches!(first, UpdateCheckInstall::Installed { .. }));
let before = engine.list_workflow_versions()?;
let second = install_embedded_update_check(engine.as_ref()).await;
assert_eq!(
second,
UpdateCheckInstall::AlreadyCurrent {
workflow_type: embedded.workflow_type().to_owned(),
content_hash: embedded.content_hash().to_string(),
}
);
let after = engine.list_workflow_versions()?;
assert_eq!(
before.len(),
after.len(),
"an already-current install must load nothing"
);
Ok(())
}
#[tokio::test]
async fn an_install_never_repoints_a_route_it_did_not_place() -> TestResult {
let (engine, _visibility) = engine().await?;
let embedded = EmbeddedUpdateCheck::load()?;
let other_source = EMBEDDED_UPDATE_CHECK_DOCUMENT.replace("step fetch", "step fetch_again");
let other = EmbeddedUpdateCheck::from_source(&other_source)?;
assert_eq!(
other.workflow_type(),
embedded.workflow_type(),
"the stand-in must be the same workflow type"
);
assert_ne!(
other.content_hash(),
embedded.content_hash(),
"the stand-in must be a different version, or this test cannot distinguish anything"
);
engine.load_package(other.package().clone()).await?;
let outcome = install_embedded_update_check(engine.as_ref()).await;
assert_eq!(
outcome,
UpdateCheckInstall::Deferred {
workflow_type: embedded.workflow_type().to_owned(),
embedded_hash: embedded.content_hash().to_string(),
routed_hash: Some(other.content_hash().to_string()),
}
);
let versions = engine.list_workflow_versions()?;
let routed: Vec<_> = versions
.iter()
.filter(|version| version.workflow_type == embedded.workflow_type() && version.route_active)
.collect();
assert_eq!(routed.len(), 1);
assert_eq!(
routed[0].content_hash.to_string(),
other.content_hash().to_string(),
"the operator's routed version must survive the boot install"
);
assert!(
!versions.iter().any(|version| {
version.workflow_type == embedded.workflow_type()
&& version.content_hash.to_string() == embedded.content_hash().to_string()
}),
"a deferred install must not load the embedded version either — loading is what \
re-points the route"
);
Ok(())
}
#[test]
fn outcome_labels_are_distinct() {
let installed = UpdateCheckInstall::Installed {
workflow_type: String::from("update_check"),
content_hash: String::from("hash"),
};
let current = UpdateCheckInstall::AlreadyCurrent {
workflow_type: String::from("update_check"),
content_hash: String::from("hash"),
};
let deferred = UpdateCheckInstall::Deferred {
workflow_type: String::from("update_check"),
embedded_hash: String::from("hash"),
routed_hash: None,
};
let failed = UpdateCheckInstall::Failed {
reason: String::from("why"),
};
let labels = [
installed.outcome(),
current.outcome(),
deferred.outcome(),
failed.outcome(),
];
let unique: std::collections::BTreeSet<&str> = labels.iter().copied().collect();
assert_eq!(unique.len(), labels.len(), "labels must be distinguishable");
}