use jacs_binding_core::AgentWrapper;
use serde_json::{Value, json};
use serial_test::serial;
use std::path::{Path, PathBuf};
struct CwdGuard {
original: PathBuf,
}
impl CwdGuard {
fn change_to(path: &Path) -> Self {
let original = std::env::current_dir().expect("current dir should be available");
std::env::set_current_dir(path).expect("should change current dir");
Self { original }
}
}
impl Drop for CwdGuard {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.original);
}
}
fn canonical_display(path: &Path) -> String {
std::fs::canonicalize(path)
.unwrap_or_else(|_| path.to_path_buf())
.to_string_lossy()
.to_string()
}
fn assert_same_path(actual: &Value, expected: &Path) {
let actual_path = actual.as_str().expect("path value should be a string");
assert_eq!(
canonical_display(Path::new(actual_path)),
canonical_display(expected)
);
}
fn create_ephemeral_wrapper() -> AgentWrapper {
let wrapper = AgentWrapper::new();
wrapper
.ephemeral(Some("ed25519"))
.expect("ephemeral(ed25519) should succeed");
wrapper
}
#[test]
#[serial]
fn test_load_with_info_returns_canonical_metadata() {
let tmp = tempfile::TempDir::new().unwrap();
let tmp_path = tmp.path().canonicalize().unwrap();
let config_dir = tmp_path.join("nested");
let data_dir = config_dir.join("jacs_data");
let key_dir = config_dir.join("jacs_keys");
let config_path = config_dir.join("jacs.config.json");
let params = jacs::simple::CreateAgentParams::builder()
.name("binding-agent-wrapper")
.password("TestP@ss123!#")
.algorithm("ring-Ed25519")
.data_directory(data_dir.to_str().unwrap())
.key_directory(key_dir.to_str().unwrap())
.config_path(config_path.to_str().unwrap())
.domain("binding-wrapper.example.com")
.build();
let (_agent, created_info) =
jacs::simple::SimpleAgent::create_with_params(params).expect("create should succeed");
let _cwd_guard = CwdGuard::change_to(&tmp_path);
unsafe {
std::env::set_var("JACS_PRIVATE_KEY_PASSWORD", "TestP@ss123!#");
}
let wrapper = AgentWrapper::new();
let info_json = wrapper
.load_with_info("./nested/jacs.config.json".to_string())
.expect("load_with_info should succeed");
let info: Value = serde_json::from_str(&info_json).expect("info should be valid JSON");
assert_eq!(info["agent_id"], created_info.agent_id);
assert_eq!(info["version"], created_info.version);
assert_eq!(info["algorithm"], created_info.algorithm);
assert_same_path(&info["config_path"], &config_path);
assert_same_path(&info["data_directory"], &data_dir);
assert_same_path(&info["key_directory"], &key_dir);
unsafe {
std::env::remove_var("JACS_PRIVATE_KEY_PASSWORD");
}
}
#[test]
#[serial]
fn test_load_with_info_prefers_wrapper_password_and_restores_process_env() {
let tmp = tempfile::TempDir::new().unwrap();
let tmp_path = tmp.path().canonicalize().unwrap();
let config_path = tmp_path.join("jacs.config.json");
let data_dir = tmp_path.join("jacs_data");
let key_dir = tmp_path.join("jacs_keys");
let params = jacs::simple::CreateAgentParams::builder()
.name("binding-password-store")
.password("CorrectP@ss123!#")
.algorithm("ring-Ed25519")
.data_directory(data_dir.to_str().unwrap())
.key_directory(key_dir.to_str().unwrap())
.config_path(config_path.to_str().unwrap())
.domain("binding-password.example.com")
.build();
let (_agent, created_info) =
jacs::simple::SimpleAgent::create_with_params(params).expect("create should succeed");
unsafe {
std::env::set_var("JACS_PRIVATE_KEY_PASSWORD", "WrongEnvP@ss123!#");
}
let wrapper = AgentWrapper::new();
wrapper
.set_private_key_password(Some("CorrectP@ss123!#".to_string()))
.expect("setting wrapper password should succeed");
let info_json = wrapper
.load_with_info(config_path.to_string_lossy().to_string())
.expect("load_with_info should succeed with wrapper password");
let info: Value = serde_json::from_str(&info_json).expect("info should be valid JSON");
assert_eq!(info["agent_id"], created_info.agent_id);
assert_eq!(
std::env::var("JACS_PRIVATE_KEY_PASSWORD").ok().as_deref(),
Some("WrongEnvP@ss123!#")
);
unsafe {
std::env::remove_var("JACS_PRIVATE_KEY_PASSWORD");
}
}
#[cfg(feature = "pq-tests")]
fn create_ephemeral_wrapper_pq() -> AgentWrapper {
let wrapper = AgentWrapper::new();
wrapper
.ephemeral(None) .expect("ephemeral(pq2025) should succeed");
wrapper
}
#[test]
fn test_create_agent_via_wrapper_valid_json() {
let wrapper = AgentWrapper::new();
let info_json = wrapper
.ephemeral(Some("ed25519"))
.expect("ephemeral should succeed");
let info: Value = serde_json::from_str(&info_json).expect("ephemeral should return valid JSON");
assert!(
info.get("agent_id").is_some(),
"agent info should have agent_id"
);
let agent_id = info["agent_id"].as_str().unwrap_or("");
assert!(!agent_id.is_empty(), "agent_id should be non-empty");
assert!(
info.get("algorithm").is_some(),
"agent info should have algorithm"
);
}
#[cfg(feature = "pq-tests")]
#[test]
fn test_create_agent_pq2025() {
let wrapper = AgentWrapper::new();
let info_json = wrapper
.ephemeral(None)
.expect("ephemeral(pq2025) should succeed");
let info: Value = serde_json::from_str(&info_json).unwrap();
let algo = info["algorithm"].as_str().unwrap_or("");
assert!(
algo.contains("pq2025"),
"default algorithm should be pq2025, got: {}",
algo
);
}
#[test]
fn test_create_agent_ed25519_alias_succeeds() {
let wrapper = AgentWrapper::new();
let info_json = wrapper
.ephemeral(Some("ed25519"))
.expect("ephemeral(ed25519) should succeed");
let info: Value = serde_json::from_str(&info_json).unwrap();
assert!(
info["algorithm"].as_str().unwrap_or("").contains("Ed25519"),
"ed25519 alias should select ring-Ed25519, got: {}",
info["algorithm"]
);
}
#[test]
fn test_sign_message_output_has_signature_fields() {
let wrapper = create_ephemeral_wrapper();
let content = json!({
"jacsType": "document",
"jacsLevel": "raw",
"content": {"action": "test", "value": 42}
});
let signed = wrapper
.create_document(&content.to_string(), None, None, true, None, None)
.expect("create_document should succeed");
assert!(!signed.is_empty(), "signed document should not be empty");
let parsed: Value =
serde_json::from_str(&signed).expect("signed document should be valid JSON");
assert!(
parsed.get("jacsSignature").is_some(),
"signed document should have jacsSignature"
);
assert!(
parsed.get("jacsId").is_some() || parsed.get("id").is_some(),
"signed document should have an ID field"
);
}
#[test]
fn test_verify_valid_returns_success() {
let wrapper = create_ephemeral_wrapper();
let content = json!({
"jacsType": "document",
"jacsLevel": "raw",
"content": {"hello": "verify-test"}
});
let signed = wrapper
.create_document(&content.to_string(), None, None, true, None, None)
.expect("create_document should succeed");
let valid = wrapper
.verify_signature(&signed, None)
.expect("verify_signature should succeed");
assert!(valid, "valid document should verify successfully");
}
#[test]
fn test_verify_rejects_tampered() {
let wrapper = create_ephemeral_wrapper();
let content = json!({
"jacsType": "document",
"jacsLevel": "raw",
"content": {"original": true}
});
let signed = wrapper
.create_document(&content.to_string(), None, None, true, None, None)
.expect("create_document should succeed");
let mut parsed: Value = serde_json::from_str(&signed).unwrap();
if let Some(content_field) = parsed.get_mut("content") {
*content_field = json!({"original": false, "tampered": true});
}
let tampered = serde_json::to_string(&parsed).unwrap();
let result = wrapper.verify_document(&tampered);
assert!(
result.is_err(),
"tampered document should fail verification"
);
}
#[test]
fn test_verify_rejects_garbage() {
let wrapper = create_ephemeral_wrapper();
let result = wrapper.verify_document("not-valid-json");
assert!(result.is_err(), "garbage input should fail verification");
}
#[test]
fn test_export_agent_json_valid() {
let wrapper = create_ephemeral_wrapper();
let agent_json = wrapper
.get_agent_json()
.expect("get_agent_json should succeed");
assert!(!agent_json.is_empty(), "agent JSON should not be empty");
let parsed: Value = serde_json::from_str(&agent_json).expect("agent JSON should be valid JSON");
assert!(
parsed.get("jacsId").is_some(),
"agent JSON should have jacsId"
);
assert!(
parsed.get("jacsSignature").is_some(),
"agent JSON should have jacsSignature (it's a signed agent document)"
);
}
#[test]
fn test_get_agent_id_non_empty() {
let wrapper = create_ephemeral_wrapper();
let agent_id = wrapper.get_agent_id().expect("get_agent_id should succeed");
assert!(!agent_id.is_empty(), "agent_id should be non-empty");
}
#[test]
fn test_get_agent_id_consistent_with_ephemeral_info() {
let wrapper = AgentWrapper::new();
let info_json = wrapper
.ephemeral(Some("ed25519"))
.expect("ephemeral should succeed");
let info: Value = serde_json::from_str(&info_json).unwrap();
let info_agent_id = info["agent_id"].as_str().unwrap_or("");
let wrapper_agent_id = wrapper.get_agent_id().expect("get_agent_id should succeed");
assert_eq!(
info_agent_id, wrapper_agent_id,
"agent_id from ephemeral info should match get_agent_id()"
);
}
#[test]
fn test_diagnostics_returns_json_with_expected_keys() {
let wrapper = create_ephemeral_wrapper();
let diag_str = wrapper.diagnostics();
assert!(!diag_str.is_empty(), "diagnostics should not be empty");
let diag: Value = serde_json::from_str(&diag_str).expect("diagnostics should be valid JSON");
assert!(
diag.get("jacs_version").is_some(),
"diagnostics should have jacs_version"
);
assert!(diag.get("os").is_some(), "diagnostics should have os");
assert!(diag.get("arch").is_some(), "diagnostics should have arch");
assert!(
diag.get("agent_loaded").is_some(),
"diagnostics should have agent_loaded"
);
assert_eq!(
diag["agent_loaded"].as_bool(),
Some(true),
"agent_loaded should be true for loaded agent"
);
}
#[test]
fn test_diagnostics_standalone_returns_valid_json() {
let diag_str = jacs_binding_core::diagnostics_standalone();
assert!(!diag_str.is_empty());
let diag: Value =
serde_json::from_str(&diag_str).expect("standalone diagnostics should be valid JSON");
assert!(diag.get("jacs_version").is_some());
assert_eq!(
diag["agent_loaded"].as_bool(),
Some(false),
"standalone diagnostics should show agent_loaded=false"
);
}
#[test]
fn test_full_roundtrip_create_sign_verify_ed25519_alias() {
let wrapper = create_ephemeral_wrapper();
let content = json!({
"jacsType": "document",
"jacsLevel": "raw",
"content": {"roundtrip": "ed25519", "step": 1}
});
let signed = wrapper
.create_document(&content.to_string(), None, None, true, None, None)
.expect("create_document should succeed");
let valid = wrapper
.verify_signature(&signed, None)
.expect("verify_signature should succeed");
assert!(valid, "roundtrip document should verify successfully");
}
#[test]
fn test_full_roundtrip_create_sign_verify_ed25519() {
let wrapper = AgentWrapper::new();
wrapper
.ephemeral(Some("ed25519"))
.expect("ephemeral(ed25519) should succeed");
let signed = wrapper
.create_document(
&serde_json::json!({"curve": "ed25519"}).to_string(),
None,
None,
true,
None,
None,
)
.expect("create_document should succeed");
let valid = wrapper
.verify_signature(&signed, None)
.expect("verify_signature should succeed");
assert!(valid, "ed25519 roundtrip document should verify");
}
#[cfg(feature = "pq-tests")]
#[test]
fn test_full_roundtrip_create_sign_verify_pq2025() {
let wrapper = create_ephemeral_wrapper_pq();
let content = json!({
"jacsType": "document",
"jacsLevel": "raw",
"content": {"roundtrip": "pq2025", "step": 1}
});
let signed = wrapper
.create_document(&content.to_string(), None, None, true, None, None)
.expect("create_document should succeed");
let valid = wrapper
.verify_signature(&signed, None)
.expect("verify_signature should succeed");
assert!(valid, "pq2025 roundtrip should verify successfully");
}
#[test]
fn test_sign_string_returns_non_empty() {
let wrapper = create_ephemeral_wrapper();
let sig = wrapper
.sign_string("hello world")
.expect("sign_string should succeed");
assert!(!sig.is_empty(), "signature should be non-empty");
}
#[test]
fn test_sign_string_different_data_different_sigs() {
let wrapper = create_ephemeral_wrapper();
let sig1 = wrapper.sign_string("message one").unwrap();
let sig2 = wrapper.sign_string("message two").unwrap();
assert_ne!(
sig1, sig2,
"different messages should produce different signatures"
);
}
#[test]
fn test_sign_batch_returns_correct_count() {
let wrapper = create_ephemeral_wrapper();
let messages = vec![
"batch-msg-1".to_string(),
"batch-msg-2".to_string(),
"batch-msg-3".to_string(),
];
let sigs = wrapper
.sign_batch(messages.clone())
.expect("sign_batch should succeed");
assert_eq!(
sigs.len(),
messages.len(),
"should return one signature per message"
);
for sig in &sigs {
assert!(!sig.is_empty(), "each signature should be non-empty");
}
}
#[test]
fn test_sign_batch_empty_input() {
let wrapper = create_ephemeral_wrapper();
let sigs = wrapper
.sign_batch(vec![])
.expect("sign_batch with empty input should succeed");
assert!(sigs.is_empty(), "empty input should return empty output");
}
#[test]
fn test_get_agent_id_before_load_fails() {
let wrapper = AgentWrapper::new();
let result = wrapper.get_agent_id();
assert!(
result.is_err(),
"get_agent_id should fail when agent is not loaded"
);
}
#[test]
fn test_get_agent_json_before_load_fails() {
let wrapper = AgentWrapper::new();
let result = wrapper.get_agent_json();
assert!(
result.is_err(),
"get_agent_json should fail when agent is not loaded"
);
}