use persona_wire_core::application::bundle_install::install_bundle;
use persona_wire_core::application::bundle_registry::BundleRegistry;
use persona_wire_core::application::projection_registry::ProjectionRegistry;
use persona_wire_core::application::spec_registry::SpecRegistry;
use persona_wire_core::domain::entity::bundle::{
BundleName, BundleRef, BundleVersion, ConflictMode,
};
use persona_wire_core::infrastructure::storage::SqliteStorage;
const QUICKSTART_BODY: &str = r#"
[bundle]
name = "quickstart"
version = "0.1.0"
description = "E2E sample"
[[nodes]]
name = "alice"
node_type = "persona"
metadata = { owner = "owner_a" }
[[specs]]
name = "active_personas"
spec = { TypeIs = "persona" }
[[projections]]
name = "personas_overview"
spec_ref = "active_personas"
template = "Personas: {{count}}"
target_form = "prompt"
"#;
fn setup() -> SqliteStorage {
let s = SqliteStorage::open_in_memory().unwrap();
s.migrate().unwrap();
s.seed_default_types().unwrap();
s
}
#[test]
fn bundle_register_install_roundtrip() {
let s = setup();
let reg = BundleRegistry::new(&s);
let bundle_id = reg
.register(
&BundleName::new("quickstart").unwrap(),
&BundleVersion::new("0.1.0").unwrap(),
Some("E2E sample"),
QUICKSTART_BODY,
)
.expect("register");
let by_id = reg
.resolve(&BundleRef::parse(&bundle_id.to_string()).unwrap())
.unwrap()
.expect("by id");
let by_name = reg
.resolve(&BundleRef::parse("quickstart").unwrap())
.unwrap()
.expect("by name");
assert_eq!(by_id, by_name);
assert_eq!(by_id.description.as_deref(), Some("E2E sample"));
assert!(by_id.body.contains("[[specs]]"));
let report = install_bundle(&by_id, ConflictMode::Increment, &s).expect("install");
assert_eq!(report.installed.len(), 3, "report: {:?}", report);
assert!(report.errors.is_empty(), "errors: {:?}", report.errors);
assert!(report.skipped.is_empty());
let spec_names = SpecRegistry::new(&s).list().unwrap();
assert!(
spec_names.iter().any(|n| n == "active_personas"),
"spec list: {:?}",
spec_names
);
let proj_names = ProjectionRegistry::new(&s).list().unwrap();
assert!(
proj_names.iter().any(|n| n == "personas_overview"),
"projection list: {:?}",
proj_names
);
let proj = ProjectionRegistry::new(&s)
.get("personas_overview")
.unwrap()
.expect("projection row");
assert_eq!(proj.spec_ref().as_str(), "active_personas");
let node_id = s
.lookup_node_id_by_name("alice")
.unwrap()
.expect("node row");
let node = s.get_node(&node_id).unwrap().expect("get_node");
assert_eq!(node.r#type, "persona");
assert_eq!(
node.metadata.get("owner").and_then(|v| v.as_str()),
Some("owner_a")
);
}
#[test]
fn bundle_reinstall_increments_names_without_duplicating_originals() {
let s = setup();
let reg = BundleRegistry::new(&s);
let _ = reg
.register(
&BundleName::new("quickstart").unwrap(),
&BundleVersion::new("0.1.0").unwrap(),
None,
QUICKSTART_BODY,
)
.unwrap();
let bundle = reg
.resolve(&BundleRef::parse("quickstart").unwrap())
.unwrap()
.unwrap();
let r1 = install_bundle(&bundle, ConflictMode::Increment, &s).unwrap();
assert_eq!(r1.installed.len(), 3);
let r2 = install_bundle(&bundle, ConflictMode::Increment, &s).unwrap();
let final_names: Vec<_> = r2.installed.iter().map(|i| i.final_name.clone()).collect();
assert!(final_names.contains(&"active_personas-1".to_string()));
assert!(final_names.contains(&"personas_overview-1".to_string()));
assert!(final_names.contains(&"alice-1".to_string()));
assert!(SpecRegistry::new(&s)
.get("active_personas")
.unwrap()
.is_some());
assert!(ProjectionRegistry::new(&s)
.get("personas_overview")
.unwrap()
.is_some());
assert!(s.lookup_node_id_by_name("alice").unwrap().is_some());
let p_suffix = ProjectionRegistry::new(&s)
.get("personas_overview-1")
.unwrap()
.expect("suffixed projection");
assert_eq!(p_suffix.spec_ref().as_str(), "active_personas-1");
}
#[test]
fn bundle_delete_after_install_succeeds_and_preserves_install_log() {
let s = setup();
let reg = BundleRegistry::new(&s);
reg.register(
&BundleName::new("delete-me").unwrap(),
&BundleVersion::new("0.1.0").unwrap(),
None,
QUICKSTART_BODY,
)
.unwrap();
let bundle = reg
.resolve(&BundleRef::parse("delete-me").unwrap())
.unwrap()
.unwrap();
let bundle_id = bundle.id;
let report = install_bundle(&bundle, ConflictMode::Increment, &s).unwrap();
assert!(report.errors.is_empty());
let deleted = reg
.delete(&BundleName::new("delete-me").unwrap())
.expect("delete after install");
assert!(deleted);
assert!(reg
.get(&BundleName::new("delete-me").unwrap())
.unwrap()
.is_none());
let (count, bundle_id_after): (i64, Option<String>) = s
.conn_for_test()
.query_row(
"SELECT COUNT(*), MAX(bundle_id) FROM bundle_installs WHERE install_id = ?1",
rusqlite::params![report.install_id],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap();
assert_eq!(count, 1, "install log row should survive bundle delete");
assert!(
bundle_id_after.is_none(),
"bundle_id should be SET NULL after parent delete, got {:?} (was bundle_id={:?})",
bundle_id_after,
bundle_id
);
}
#[test]
fn bundle_skip_mode_is_idempotent_for_fixed_names() {
let s = setup();
let reg = BundleRegistry::new(&s);
reg.register(
&BundleName::new("quickstart").unwrap(),
&BundleVersion::new("0.1.0").unwrap(),
None,
QUICKSTART_BODY,
)
.unwrap();
let bundle = reg
.resolve(&BundleRef::parse("quickstart").unwrap())
.unwrap()
.unwrap();
install_bundle(&bundle, ConflictMode::Increment, &s).unwrap();
let r = install_bundle(&bundle, ConflictMode::Skip, &s).unwrap();
assert!(r.installed.is_empty(), "installed: {:?}", r.installed);
assert!(r.errors.is_empty(), "errors: {:?}", r.errors);
assert_eq!(r.skipped.len(), 3);
}
#[tokio::test]
async fn bundle_wiring_with_explicit_projection_ref_renders_after_install() {
use persona_wire_core::application::plugin_registry::PluginRegistry;
use persona_wire_core::application::use_cases::{wire_prompt_context, WirePromptContextInput};
let dir = std::env::temp_dir().join(format!("bundle-projref-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("notes.md");
std::fs::write(&file, "bundle body").unwrap();
let body = format!(
r#"
[bundle]
name = "projref"
version = "0.1.0"
[[specs]]
name = "any_wiring"
spec = {{ TypeIs = "outline_node" }}
[[projections]]
name = "shared_overview"
spec_ref = "any_wiring"
template = "SHARED: {{{{#each entries}}}}{{{{this.fetched_data.body}}}}{{{{/each}}}}"
target_form = "markdown"
[[wirings]]
persona_id = "gamma"
slot = "notes"
source_uri = "file:{}"
projection_ref = "shared_overview"
"#,
file.display()
);
let s = setup();
let reg = BundleRegistry::new(&s);
reg.register(
&BundleName::new("projref").unwrap(),
&BundleVersion::new("0.1.0").unwrap(),
None,
&body,
)
.unwrap();
let bundle = reg
.resolve(&BundleRef::parse("projref").unwrap())
.unwrap()
.unwrap();
let report = install_bundle(&bundle, ConflictMode::Increment, &s).unwrap();
assert!(report.errors.is_empty(), "errors: {:?}", report.errors);
let storage = std::sync::Arc::new(std::sync::Mutex::new(s));
let registry = PluginRegistry::default_for_wire().unwrap();
let out = wire_prompt_context(
WirePromptContextInput {
persona_id: "gamma".into(),
projection_names: None,
projection_exclude_names: None,
},
storage,
®istry,
)
.await
.unwrap();
assert!(
out.prompt_context.contains("SHARED: bundle body"),
"rendered: {} / warnings: {:?}",
out.prompt_context,
out.warnings
);
assert_eq!(out.projections[0].name, "shared_overview");
std::fs::remove_dir_all(&dir).ok();
}