use super::*;
#[test]
fn test_ffi_plugin_registration_validation_and_cleanup() {
let _guard = TEST_MUTEX.lock().unwrap();
reset_globals();
let _ = nemo_flow_clear_plugin_configuration();
let plugin_kind = unique_name("ffi_plugin");
let plugin_kind_c = cstring(&plugin_kind);
let config = cstring(
&json!({
"version": 1,
"components": [{
"kind": plugin_kind,
"enabled": true,
"config": {}
}]
})
.to_string(),
);
let user_data = Box::into_raw(Box::new(7usize)) as *mut libc::c_void;
unsafe {
assert_eq!(
nemo_flow_register_plugin(
plugin_kind_c.as_ptr(),
Some(plugin_validate_warn),
plugin_register_subscriber,
user_data,
Some(plugin_free),
),
NemoFlowStatus::Ok
);
let mut report_json = ptr::null_mut();
assert_eq!(
nemo_flow_validate_plugin_config(config.as_ptr(), &mut report_json),
NemoFlowStatus::Ok
);
let report = returned_json(report_json);
assert!(
report["diagnostics"]
.as_array()
.unwrap()
.iter()
.any(|diag| diag["code"] == "plugin.warning")
);
let mut init_json = ptr::null_mut();
assert_eq!(
nemo_flow_initialize_plugins(config.as_ptr(), &mut init_json),
NemoFlowStatus::Ok
);
let initialized = returned_json(init_json);
assert!(
initialized["diagnostics"]
.as_array()
.unwrap()
.iter()
.any(|diag| diag["code"] == "plugin.warning")
);
let mut active_json = ptr::null_mut();
assert_eq!(
nemo_flow_active_plugin_report_json(&mut active_json),
NemoFlowStatus::Ok
);
let active = returned_json(active_json);
assert_eq!(active["diagnostics"], initialized["diagnostics"]);
assert_eq!(nemo_flow_clear_plugin_configuration(), NemoFlowStatus::Ok);
assert_eq!(
nemo_flow_deregister_plugin(plugin_kind_c.as_ptr()),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_deregister_plugin(plugin_kind_c.as_ptr()),
NemoFlowStatus::NotFound
);
}
assert_eq!(*lock_unpoisoned(plugin_frees()), 1);
}
#[test]
fn test_ffi_plugin_validation_failure_modes_are_reported() {
let _guard = TEST_MUTEX.lock().unwrap();
reset_globals();
let _ = nemo_flow_clear_plugin_configuration();
for (suffix, validate_cb, expected_fragment) in [
(
"invalid",
Some(plugin_validate_invalid as callable::NemoFlowPluginValidateCb),
"invalid diagnostics JSON",
),
(
"null",
Some(plugin_validate_null as callable::NemoFlowPluginValidateCb),
"returned null",
),
] {
let plugin_kind = unique_name(&format!("ffi_plugin_{suffix}"));
let plugin_kind_c = cstring(&plugin_kind);
let config = cstring(
&json!({
"version": 1,
"components": [{
"kind": plugin_kind,
"enabled": true,
"config": {}
}]
})
.to_string(),
);
let user_data = Box::into_raw(Box::new(9usize)) as *mut libc::c_void;
unsafe {
assert_eq!(
nemo_flow_register_plugin(
plugin_kind_c.as_ptr(),
validate_cb,
plugin_register_fail,
user_data,
Some(plugin_free),
),
NemoFlowStatus::Ok
);
let mut report_json = ptr::null_mut();
assert_eq!(
nemo_flow_validate_plugin_config(config.as_ptr(), &mut report_json),
NemoFlowStatus::Ok
);
let report = returned_json(report_json);
let diag = report["diagnostics"].as_array().unwrap();
assert!(
diag.iter().any(|value| {
value["code"] == "plugin.validate_failed"
&& value["message"]
.as_str()
.is_some_and(|message| message.contains(expected_fragment))
}),
"missing expected plugin validation diagnostic: {expected_fragment}"
);
assert_eq!(
nemo_flow_deregister_plugin(plugin_kind_c.as_ptr()),
NemoFlowStatus::Ok
);
}
}
assert_eq!(*lock_unpoisoned(plugin_frees()), 2);
}
#[test]
fn test_ffi_plugin_without_validate_callback_uses_registration_fallback_error() {
let _guard = TEST_MUTEX.lock().unwrap();
reset_globals();
let _ = nemo_flow_clear_plugin_configuration();
let plugin_kind = unique_name("ffi_plugin_no_validate");
let plugin_kind_c = cstring(&plugin_kind);
let config = cstring(
&json!({
"version": 1,
"components": [{
"kind": plugin_kind,
"enabled": true,
"config": {}
}]
})
.to_string(),
);
let user_data = Box::into_raw(Box::new(11usize)) as *mut libc::c_void;
unsafe {
assert_eq!(
nemo_flow_register_plugin(
plugin_kind_c.as_ptr(),
None,
plugin_register_fail,
user_data,
Some(plugin_free),
),
NemoFlowStatus::Ok
);
let mut report_json = ptr::null_mut();
assert_eq!(
nemo_flow_validate_plugin_config(config.as_ptr(), &mut report_json),
NemoFlowStatus::Ok
);
let report = returned_json(report_json);
assert_eq!(report["diagnostics"], json!([]));
let mut init_json = ptr::null_mut();
assert_eq!(
nemo_flow_initialize_plugins(config.as_ptr(), &mut init_json),
NemoFlowStatus::Internal
);
let err = read_last_error().expect("expected plugin registration failure message");
assert!(err.contains("register callback failed with status Internal"));
let mut active_json = ptr::null_mut();
assert_eq!(
nemo_flow_active_plugin_report_json(&mut active_json),
NemoFlowStatus::Ok
);
assert_eq!(returned_json(active_json), Json::Null);
assert_eq!(
nemo_flow_deregister_plugin(plugin_kind_c.as_ptr()),
NemoFlowStatus::Ok
);
}
assert_eq!(*lock_unpoisoned(plugin_frees()), 1);
}
#[test]
fn test_ffi_plugin_registration_failure_prefers_last_error_message() {
let _guard = TEST_MUTEX.lock().unwrap();
reset_globals();
let _ = nemo_flow_clear_plugin_configuration();
let plugin_kind = unique_name("ffi_plugin_last_error");
let plugin_kind_c = cstring(&plugin_kind);
let config = cstring(
&json!({
"version": 1,
"components": [{
"kind": plugin_kind,
"enabled": true,
"config": {}
}]
})
.to_string(),
);
let user_data = Box::into_raw(Box::new(13usize)) as *mut libc::c_void;
unsafe {
assert_eq!(
nemo_flow_register_plugin(
plugin_kind_c.as_ptr(),
None,
plugin_register_fail_with_last_error,
user_data,
Some(plugin_free),
),
NemoFlowStatus::Ok
);
let mut init_json = ptr::null_mut();
assert_eq!(
nemo_flow_initialize_plugins(config.as_ptr(), &mut init_json),
NemoFlowStatus::Internal
);
assert!(
read_last_error()
.unwrap_or_default()
.contains("plugin register callback set last error explicitly")
);
assert_eq!(
nemo_flow_deregister_plugin(plugin_kind_c.as_ptr()),
NemoFlowStatus::Ok
);
}
assert_eq!(*lock_unpoisoned(plugin_frees()), 1);
}
#[test]
fn test_ffi_plugin_context_helpers_cover_null_and_success_paths() {
let _guard = TEST_MUTEX.lock().unwrap();
reset_globals();
let name = cstring("registered");
let llm_name = cstring("llm");
let tool_name = cstring("tool");
unsafe {
assert_eq!(
nemo_flow_plugin_context_register_subscriber(
ptr::null_mut(),
name.as_ptr(),
subscriber_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
ptr::null_mut(),
tool_name.as_ptr(),
1,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_response_guardrail(
ptr::null_mut(),
tool_name.as_ptr(),
1,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_tool_conditional_execution_guardrail(
ptr::null_mut(),
tool_name.as_ptr(),
1,
tool_allow_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_llm_sanitize_request_guardrail(
ptr::null_mut(),
llm_name.as_ptr(),
1,
llm_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_llm_sanitize_response_guardrail(
ptr::null_mut(),
llm_name.as_ptr(),
1,
llm_response_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_llm_conditional_execution_guardrail(
ptr::null_mut(),
llm_name.as_ptr(),
1,
llm_allow_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_llm_request_intercept(
ptr::null_mut(),
llm_name.as_ptr(),
1,
false,
llm_request_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_tool_request_intercept(
ptr::null_mut(),
tool_name.as_ptr(),
1,
false,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_llm_execution_intercept(
ptr::null_mut(),
llm_name.as_ptr(),
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_llm_stream_execution_intercept(
ptr::null_mut(),
llm_name.as_ptr(),
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_plugin_context_register_tool_execution_intercept(
ptr::null_mut(),
tool_name.as_ptr(),
1,
tool_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::NullPointer
);
}
let mut inner = PluginRegistrationContext::with_namespace("ffi::");
let mut ctx = FfiPluginContext(&mut inner as *mut _);
unsafe {
assert_eq!(
nemo_flow_plugin_context_register_subscriber(
&mut ctx,
name.as_ptr(),
subscriber_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
&mut ctx,
tool_name.as_ptr(),
1,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_response_guardrail(
&mut ctx,
tool_name.as_ptr(),
1,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_tool_conditional_execution_guardrail(
&mut ctx,
tool_name.as_ptr(),
1,
tool_allow_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_llm_sanitize_request_guardrail(
&mut ctx,
llm_name.as_ptr(),
1,
llm_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_llm_sanitize_response_guardrail(
&mut ctx,
llm_name.as_ptr(),
1,
llm_response_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_llm_conditional_execution_guardrail(
&mut ctx,
llm_name.as_ptr(),
1,
llm_allow_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_llm_request_intercept(
&mut ctx,
llm_name.as_ptr(),
1,
false,
llm_request_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_tool_request_intercept(
&mut ctx,
tool_name.as_ptr(),
1,
false,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_llm_execution_intercept(
&mut ctx,
llm_name.as_ptr(),
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_llm_stream_execution_intercept(
&mut ctx,
llm_name.as_ptr(),
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_tool_execution_intercept(
&mut ctx,
tool_name.as_ptr(),
1,
tool_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
}
let mut registrations = inner.into_registrations();
let registered_names = registrations
.iter()
.map(|registration| registration.name.clone())
.collect::<Vec<_>>();
assert_eq!(
registered_names,
vec![
"ffi::registered",
"ffi::tool",
"ffi::tool",
"ffi::tool",
"ffi::llm",
"ffi::llm",
"ffi::llm",
"ffi::llm",
"ffi::tool",
"ffi::llm",
"ffi::llm",
"ffi::tool",
]
);
nemo_flow::plugin::rollback_registrations(&mut registrations);
assert!(registrations.is_empty());
}
#[test]
fn test_ffi_plugin_context_helpers_reject_duplicate_names() {
let _guard = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
reset_globals();
let subscriber_name = cstring("duplicate-subscriber");
let llm_name = cstring("duplicate-llm");
let tool_name = cstring("duplicate-tool");
let mut inner = PluginRegistrationContext::with_namespace("ffi::");
let mut ctx = FfiPluginContext(&mut inner as *mut _);
unsafe {
assert_eq!(
nemo_flow_plugin_context_register_subscriber(
&mut ctx,
subscriber_name.as_ptr(),
subscriber_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_subscriber(
&mut ctx,
subscriber_name.as_ptr(),
subscriber_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Internal
);
assert!(
read_last_error()
.unwrap_or_default()
.contains("already exists")
);
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
&mut ctx,
tool_name.as_ptr(),
1,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
&mut ctx,
tool_name.as_ptr(),
2,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Internal
);
assert!(
read_last_error()
.unwrap_or_default()
.contains("already exists")
);
assert_eq!(
nemo_flow_plugin_context_register_llm_request_intercept(
&mut ctx,
llm_name.as_ptr(),
1,
false,
llm_request_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_llm_request_intercept(
&mut ctx,
llm_name.as_ptr(),
2,
true,
llm_request_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Internal
);
assert!(
read_last_error()
.unwrap_or_default()
.contains("already exists")
);
assert_eq!(
nemo_flow_plugin_context_register_tool_execution_intercept(
&mut ctx,
tool_name.as_ptr(),
1,
tool_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_plugin_context_register_tool_execution_intercept(
&mut ctx,
tool_name.as_ptr(),
2,
tool_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Internal
);
assert!(
read_last_error()
.unwrap_or_default()
.contains("already exists")
);
}
}
#[test]
fn test_ffi_plugin_context_helpers_reject_invalid_utf8_names_in_bulk() {
let _guard = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
reset_globals();
let invalid_utf8 = [0xffu8, 0];
let invalid_name = invalid_utf8.as_ptr() as *const c_char;
let mut inner = PluginRegistrationContext::with_namespace("ffi::");
let mut ctx = FfiPluginContext(&mut inner as *mut _);
macro_rules! assert_invalid_name_status {
($call:expr) => {{
assert_eq!($call, NemoFlowStatus::InvalidUtf8);
assert!(read_last_error().unwrap_or_default().contains("utf-8"));
}};
}
unsafe {
assert_invalid_name_status!(nemo_flow_plugin_context_register_subscriber(
&mut ctx,
invalid_name,
subscriber_cb,
ptr::null_mut(),
None,
));
assert_invalid_name_status!(
nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
&mut ctx,
invalid_name,
1,
tool_request_cb,
ptr::null_mut(),
None,
)
);
assert_invalid_name_status!(
nemo_flow_plugin_context_register_tool_sanitize_response_guardrail(
&mut ctx,
invalid_name,
1,
tool_request_cb,
ptr::null_mut(),
None,
)
);
assert_invalid_name_status!(
nemo_flow_plugin_context_register_tool_conditional_execution_guardrail(
&mut ctx,
invalid_name,
1,
tool_allow_cb,
ptr::null_mut(),
None,
)
);
assert_invalid_name_status!(
nemo_flow_plugin_context_register_llm_sanitize_request_guardrail(
&mut ctx,
invalid_name,
1,
llm_request_cb,
ptr::null_mut(),
None,
)
);
assert_invalid_name_status!(
nemo_flow_plugin_context_register_llm_sanitize_response_guardrail(
&mut ctx,
invalid_name,
1,
llm_response_cb,
ptr::null_mut(),
None,
)
);
assert_invalid_name_status!(
nemo_flow_plugin_context_register_llm_conditional_execution_guardrail(
&mut ctx,
invalid_name,
1,
llm_allow_cb,
ptr::null_mut(),
None,
)
);
assert_invalid_name_status!(nemo_flow_plugin_context_register_llm_request_intercept(
&mut ctx,
invalid_name,
1,
false,
llm_request_intercept_cb,
ptr::null_mut(),
None,
));
assert_invalid_name_status!(nemo_flow_plugin_context_register_tool_request_intercept(
&mut ctx,
invalid_name,
1,
false,
tool_request_cb,
ptr::null_mut(),
None,
));
assert_invalid_name_status!(nemo_flow_plugin_context_register_llm_execution_intercept(
&mut ctx,
invalid_name,
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
));
assert_invalid_name_status!(
nemo_flow_plugin_context_register_llm_stream_execution_intercept(
&mut ctx,
invalid_name,
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
)
);
assert_invalid_name_status!(nemo_flow_plugin_context_register_tool_execution_intercept(
&mut ctx,
invalid_name,
1,
tool_exec_intercept_cb,
ptr::null_mut(),
None,
));
}
let registrations = inner.into_registrations();
assert!(registrations.is_empty());
}
#[test]
fn test_ffi_plugin_context_helpers_reject_duplicate_names_in_bulk() {
let _guard = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
reset_globals();
let mut inner = PluginRegistrationContext::with_namespace("ffi::");
let mut ctx = FfiPluginContext(&mut inner as *mut _);
macro_rules! assert_duplicate {
($call:expr) => {{
assert_eq!($call, NemoFlowStatus::Internal);
assert!(
read_last_error()
.unwrap_or_default()
.contains("already exists")
);
}};
}
unsafe {
let subscriber_name = cstring("duplicate-subscriber-bulk");
assert_eq!(
nemo_flow_plugin_context_register_subscriber(
&mut ctx,
subscriber_name.as_ptr(),
subscriber_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(nemo_flow_plugin_context_register_subscriber(
&mut ctx,
subscriber_name.as_ptr(),
subscriber_cb,
ptr::null_mut(),
None,
));
let tool_sanitize_req = cstring("duplicate-tool-sanitize-req-bulk");
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
&mut ctx,
tool_sanitize_req.as_ptr(),
1,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(
nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
&mut ctx,
tool_sanitize_req.as_ptr(),
2,
tool_request_cb,
ptr::null_mut(),
None,
)
);
let tool_sanitize_resp = cstring("duplicate-tool-sanitize-resp-bulk");
assert_eq!(
nemo_flow_plugin_context_register_tool_sanitize_response_guardrail(
&mut ctx,
tool_sanitize_resp.as_ptr(),
1,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(
nemo_flow_plugin_context_register_tool_sanitize_response_guardrail(
&mut ctx,
tool_sanitize_resp.as_ptr(),
2,
tool_request_cb,
ptr::null_mut(),
None,
)
);
let tool_conditional = cstring("duplicate-tool-conditional-bulk");
assert_eq!(
nemo_flow_plugin_context_register_tool_conditional_execution_guardrail(
&mut ctx,
tool_conditional.as_ptr(),
1,
tool_allow_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(
nemo_flow_plugin_context_register_tool_conditional_execution_guardrail(
&mut ctx,
tool_conditional.as_ptr(),
2,
tool_allow_cb,
ptr::null_mut(),
None,
)
);
let llm_sanitize_req = cstring("duplicate-llm-sanitize-req-bulk");
assert_eq!(
nemo_flow_plugin_context_register_llm_sanitize_request_guardrail(
&mut ctx,
llm_sanitize_req.as_ptr(),
1,
llm_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(
nemo_flow_plugin_context_register_llm_sanitize_request_guardrail(
&mut ctx,
llm_sanitize_req.as_ptr(),
2,
llm_request_cb,
ptr::null_mut(),
None,
)
);
let llm_sanitize_resp = cstring("duplicate-llm-sanitize-resp-bulk");
assert_eq!(
nemo_flow_plugin_context_register_llm_sanitize_response_guardrail(
&mut ctx,
llm_sanitize_resp.as_ptr(),
1,
llm_response_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(
nemo_flow_plugin_context_register_llm_sanitize_response_guardrail(
&mut ctx,
llm_sanitize_resp.as_ptr(),
2,
llm_response_cb,
ptr::null_mut(),
None,
)
);
let llm_conditional = cstring("duplicate-llm-conditional-bulk");
assert_eq!(
nemo_flow_plugin_context_register_llm_conditional_execution_guardrail(
&mut ctx,
llm_conditional.as_ptr(),
1,
llm_allow_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(
nemo_flow_plugin_context_register_llm_conditional_execution_guardrail(
&mut ctx,
llm_conditional.as_ptr(),
2,
llm_allow_cb,
ptr::null_mut(),
None,
)
);
let llm_request = cstring("duplicate-llm-request-bulk");
assert_eq!(
nemo_flow_plugin_context_register_llm_request_intercept(
&mut ctx,
llm_request.as_ptr(),
1,
false,
llm_request_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(nemo_flow_plugin_context_register_llm_request_intercept(
&mut ctx,
llm_request.as_ptr(),
2,
true,
llm_request_intercept_cb,
ptr::null_mut(),
None,
));
let tool_request = cstring("duplicate-tool-request-bulk");
assert_eq!(
nemo_flow_plugin_context_register_tool_request_intercept(
&mut ctx,
tool_request.as_ptr(),
1,
false,
tool_request_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(nemo_flow_plugin_context_register_tool_request_intercept(
&mut ctx,
tool_request.as_ptr(),
2,
true,
tool_request_cb,
ptr::null_mut(),
None,
));
let llm_exec = cstring("duplicate-llm-exec-bulk");
assert_eq!(
nemo_flow_plugin_context_register_llm_execution_intercept(
&mut ctx,
llm_exec.as_ptr(),
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(nemo_flow_plugin_context_register_llm_execution_intercept(
&mut ctx,
llm_exec.as_ptr(),
2,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
));
let llm_stream_exec = cstring("duplicate-llm-stream-exec-bulk");
assert_eq!(
nemo_flow_plugin_context_register_llm_stream_execution_intercept(
&mut ctx,
llm_stream_exec.as_ptr(),
1,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(
nemo_flow_plugin_context_register_llm_stream_execution_intercept(
&mut ctx,
llm_stream_exec.as_ptr(),
2,
llm_exec_intercept_cb,
ptr::null_mut(),
None,
)
);
let tool_exec = cstring("duplicate-tool-exec-bulk");
assert_eq!(
nemo_flow_plugin_context_register_tool_execution_intercept(
&mut ctx,
tool_exec.as_ptr(),
1,
tool_exec_intercept_cb,
ptr::null_mut(),
None,
),
NemoFlowStatus::Ok
);
assert_duplicate!(nemo_flow_plugin_context_register_tool_execution_intercept(
&mut ctx,
tool_exec.as_ptr(),
2,
tool_exec_intercept_cb,
ptr::null_mut(),
None,
));
}
let mut registrations = inner.into_registrations();
nemo_flow::plugin::rollback_registrations(&mut registrations);
assert!(registrations.is_empty());
}
#[test]
fn test_ffi_specialized_subscriber_and_exporter_default_and_invalid_name_paths() {
let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
reset_globals();
unsafe {
let invalid_utf8 = [0xffu8, 0];
let invalid_name = invalid_utf8.as_ptr() as *const c_char;
let mut otel_subscriber: *mut FfiOpenTelemetrySubscriber = ptr::null_mut();
assert_eq!(
nemo_flow_otel_subscriber_create(
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
0,
&mut otel_subscriber,
),
NemoFlowStatus::Ok
);
let otel_name = cstring(&unique_name("ffi_otel_defaults"));
assert_eq!(
nemo_flow_otel_subscriber_register(otel_subscriber, invalid_name),
NemoFlowStatus::InvalidUtf8
);
assert_eq!(
nemo_flow_otel_subscriber_register(otel_subscriber, otel_name.as_ptr()),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_otel_subscriber_register(otel_subscriber, otel_name.as_ptr()),
NemoFlowStatus::Internal
);
assert_eq!(
nemo_flow_otel_subscriber_deregister(ptr::null()),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_otel_subscriber_deregister(invalid_name),
NemoFlowStatus::InvalidUtf8
);
assert_eq!(
nemo_flow_otel_subscriber_deregister(otel_name.as_ptr()),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_otel_subscriber_force_flush(otel_subscriber),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_otel_subscriber_shutdown(otel_subscriber),
NemoFlowStatus::Ok
);
nemo_flow_otel_subscriber_free(otel_subscriber);
let mut oi_subscriber: *mut FfiOpenInferenceSubscriber = ptr::null_mut();
assert_eq!(
nemo_flow_openinference_subscriber_create(
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
ptr::null(),
0,
&mut oi_subscriber,
),
NemoFlowStatus::Ok
);
let oi_name = cstring(&unique_name("ffi_oi_defaults"));
assert_eq!(
nemo_flow_openinference_subscriber_register(oi_subscriber, invalid_name),
NemoFlowStatus::InvalidUtf8
);
assert_eq!(
nemo_flow_openinference_subscriber_register(oi_subscriber, oi_name.as_ptr()),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_openinference_subscriber_register(oi_subscriber, oi_name.as_ptr()),
NemoFlowStatus::Internal
);
assert_eq!(
nemo_flow_openinference_subscriber_deregister(ptr::null()),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_openinference_subscriber_deregister(invalid_name),
NemoFlowStatus::InvalidUtf8
);
assert_eq!(
nemo_flow_openinference_subscriber_deregister(oi_name.as_ptr()),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_openinference_subscriber_force_flush(oi_subscriber),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_openinference_subscriber_shutdown(oi_subscriber),
NemoFlowStatus::Ok
);
nemo_flow_openinference_subscriber_free(oi_subscriber);
let session = cstring("specialized-session");
let agent = cstring("specialized-agent");
let version = cstring("1.0.0");
let mut exporter = ptr::null_mut();
assert_eq!(
nemo_flow_atif_exporter_create(
session.as_ptr(),
agent.as_ptr(),
version.as_ptr(),
ptr::null(),
&mut exporter,
),
NemoFlowStatus::Ok
);
let exporter_name = cstring(&unique_name("ffi_exporter_defaults"));
assert_eq!(
nemo_flow_atif_exporter_register(exporter, invalid_name),
NemoFlowStatus::InvalidUtf8
);
assert_eq!(
nemo_flow_atif_exporter_register(exporter, exporter_name.as_ptr()),
NemoFlowStatus::Ok
);
assert_eq!(
nemo_flow_atif_exporter_register(exporter, exporter_name.as_ptr()),
NemoFlowStatus::AlreadyExists
);
assert_eq!(
nemo_flow_atif_exporter_deregister(ptr::null()),
NemoFlowStatus::NullPointer
);
assert_eq!(
nemo_flow_atif_exporter_deregister(invalid_name),
NemoFlowStatus::InvalidUtf8
);
assert_eq!(
nemo_flow_atif_exporter_deregister(exporter_name.as_ptr()),
NemoFlowStatus::Ok
);
nemo_flow_atif_exporter_free(exporter);
}
}
#[test]
fn test_ffi_specialized_constructor_invalid_utf8_and_malformed_json_sweep() {
let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
reset_globals();
unsafe {
let invalid_utf8 = [0xffu8, 0];
let invalid = invalid_utf8.as_ptr() as *const c_char;
let malformed_json = cstring("{");
let valid_headers = cstring(r#"{"authorization":"Bearer token"}"#);
let valid_attrs = cstring(r#"{"deployment.environment":"test"}"#);
let endpoint = cstring("http://localhost:4318/v1/traces");
let service = cstring("ffi-agent");
let namespace = cstring("agents");
let version = cstring("1.0.0");
let scope = cstring("ffi-tests");
let grpc = cstring("grpc");
let mut otel = ptr::null_mut();
assert_eq!(
nemo_flow_otel_subscriber_create(
ptr::null(),
endpoint.as_ptr(),
malformed_json.as_ptr(),
valid_attrs.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
1,
&mut otel,
),
NemoFlowStatus::InvalidJson
);
assert_eq!(
nemo_flow_otel_subscriber_create(
ptr::null(),
endpoint.as_ptr(),
valid_headers.as_ptr(),
malformed_json.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
1,
&mut otel,
),
NemoFlowStatus::InvalidJson
);
for (transport, endpoint_ptr, service_ptr, namespace_ptr, version_ptr, scope_ptr) in [
(
invalid,
endpoint.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
invalid,
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
invalid,
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
service.as_ptr(),
invalid,
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
invalid,
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
invalid,
),
] {
assert_eq!(
nemo_flow_otel_subscriber_create(
transport,
endpoint_ptr,
valid_headers.as_ptr(),
valid_attrs.as_ptr(),
service_ptr,
namespace_ptr,
version_ptr,
scope_ptr,
1,
&mut otel,
),
NemoFlowStatus::InvalidUtf8
);
}
let mut openinference = ptr::null_mut();
assert_eq!(
nemo_flow_openinference_subscriber_create(
ptr::null(),
endpoint.as_ptr(),
malformed_json.as_ptr(),
valid_attrs.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
1,
&mut openinference,
),
NemoFlowStatus::InvalidJson
);
assert_eq!(
nemo_flow_openinference_subscriber_create(
ptr::null(),
endpoint.as_ptr(),
valid_headers.as_ptr(),
malformed_json.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
1,
&mut openinference,
),
NemoFlowStatus::InvalidJson
);
for (transport, endpoint_ptr, service_ptr, namespace_ptr, version_ptr, scope_ptr) in [
(
invalid,
endpoint.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
invalid,
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
invalid,
namespace.as_ptr(),
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
service.as_ptr(),
invalid,
version.as_ptr(),
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
invalid,
scope.as_ptr(),
),
(
grpc.as_ptr(),
endpoint.as_ptr(),
service.as_ptr(),
namespace.as_ptr(),
version.as_ptr(),
invalid,
),
] {
assert_eq!(
nemo_flow_openinference_subscriber_create(
transport,
endpoint_ptr,
valid_headers.as_ptr(),
valid_attrs.as_ptr(),
service_ptr,
namespace_ptr,
version_ptr,
scope_ptr,
1,
&mut openinference,
),
NemoFlowStatus::InvalidUtf8
);
}
let session = cstring("specialized-session-invalid-utf8");
let agent = cstring("specialized-agent-invalid-utf8");
let agent_version = cstring("1.0.0");
let mut exporter = ptr::null_mut();
for (session_ptr, agent_ptr, version_ptr, model_ptr) in [
(invalid, agent.as_ptr(), agent_version.as_ptr(), ptr::null()),
(
session.as_ptr(),
invalid,
agent_version.as_ptr(),
ptr::null(),
),
(session.as_ptr(), agent.as_ptr(), invalid, ptr::null()),
(
session.as_ptr(),
agent.as_ptr(),
agent_version.as_ptr(),
invalid,
),
] {
assert_eq!(
nemo_flow_atif_exporter_create(
session_ptr,
agent_ptr,
version_ptr,
model_ptr,
&mut exporter,
),
NemoFlowStatus::InvalidUtf8
);
}
let plugin_kind = invalid;
assert_eq!(
nemo_flow_register_plugin(
plugin_kind,
None,
plugin_register_fail,
ptr::null_mut(),
None,
),
NemoFlowStatus::InvalidUtf8
);
assert_eq!(
nemo_flow_deregister_plugin(plugin_kind),
NemoFlowStatus::InvalidUtf8
);
}
}