#![allow(unsafe_code)]
use crate::server::MockServer;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::sync::Arc;
use tokio::runtime::Runtime;
use tokio::sync::Mutex;
pub struct MockServerHandle {
server: Arc<Mutex<MockServer>>,
runtime: Runtime,
}
#[no_mangle]
pub unsafe extern "C" fn mockforge_server_new(port: u16) -> *mut MockServerHandle {
let Ok(runtime) = Runtime::new() else {
return ptr::null_mut();
};
let server = runtime.block_on(async { Box::pin(MockServer::new().port(port).start()).await });
let Ok(server) = server else {
return ptr::null_mut();
};
let handle = MockServerHandle {
server: Arc::new(Mutex::new(server)),
runtime,
};
Box::into_raw(Box::new(handle))
}
#[no_mangle]
pub unsafe extern "C" fn mockforge_server_destroy(handle: *mut MockServerHandle) {
if handle.is_null() {
return;
}
let handle = Box::from_raw(handle);
let server = handle.server.clone();
handle.runtime.block_on(async move {
let taken = std::mem::take(&mut *server.lock().await);
let _ = Box::pin(taken.stop()).await;
});
}
#[no_mangle]
pub unsafe extern "C" fn mockforge_server_stub(
handle: *mut MockServerHandle,
method: *const c_char,
path: *const c_char,
_status: u16,
body: *const c_char,
) -> i32 {
if handle.is_null() || method.is_null() || path.is_null() || body.is_null() {
return -1;
}
let handle = &*handle;
let Ok(method) = CStr::from_ptr(method).to_str() else {
return -1;
};
let Ok(path) = CStr::from_ptr(path).to_str() else {
return -1;
};
let Ok(body) = CStr::from_ptr(body).to_str() else {
return -1;
};
let body_value: serde_json::Value = match serde_json::from_str(body) {
Ok(v) => v,
Err(_) => return -1,
};
let server = handle.server.clone();
let result = handle.runtime.block_on(async move {
let mut server = server.lock().await;
server.stub_response(method, path, body_value).await
});
match result {
Ok(()) => 0,
Err(_) => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn mockforge_server_url(handle: *const MockServerHandle) -> *mut c_char {
if handle.is_null() {
return ptr::null_mut();
}
let handle = &*handle;
let server = handle.server.clone();
let url = handle.runtime.block_on(async move {
let server = server.lock().await;
server.url()
});
CString::new(url).map_or(ptr::null_mut(), CString::into_raw)
}
#[no_mangle]
pub unsafe extern "C" fn mockforge_free_string(s: *mut c_char) {
if !s.is_null() {
let _ = CString::from_raw(s);
}
}
#[no_mangle]
pub const unsafe extern "C" fn mockforge_last_error() -> *mut c_char {
ptr::null_mut()
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn test_mock_server_handle_size() {
assert!(size_of::<MockServerHandle>() > 0);
}
#[test]
fn test_mockforge_server_new_null_on_invalid_port() {
unsafe {
let handle = mockforge_server_new(0);
if !handle.is_null() {
mockforge_server_destroy(handle);
}
}
}
#[test]
fn test_mockforge_server_destroy_null_handle() {
unsafe {
mockforge_server_destroy(ptr::null_mut());
}
}
#[test]
fn test_mockforge_server_stub_null_handle() {
unsafe {
let method = CString::new("GET").unwrap();
let path = CString::new("/test").unwrap();
let body = CString::new("{}").unwrap();
let result = mockforge_server_stub(
ptr::null_mut(),
method.as_ptr(),
path.as_ptr(),
200,
body.as_ptr(),
);
assert_eq!(result, -1);
}
}
#[test]
fn test_mockforge_server_stub_null_method() {
unsafe {
let path = CString::new("/test").unwrap();
let body = CString::new("{}").unwrap();
let result = mockforge_server_stub(
ptr::null_mut(),
ptr::null(),
path.as_ptr(),
200,
body.as_ptr(),
);
assert_eq!(result, -1);
}
}
#[test]
fn test_mockforge_server_stub_null_path() {
unsafe {
let method = CString::new("GET").unwrap();
let body = CString::new("{}").unwrap();
let result = mockforge_server_stub(
ptr::null_mut(),
method.as_ptr(),
ptr::null(),
200,
body.as_ptr(),
);
assert_eq!(result, -1);
}
}
#[test]
fn test_mockforge_server_stub_null_body() {
unsafe {
let method = CString::new("GET").unwrap();
let path = CString::new("/test").unwrap();
let result = mockforge_server_stub(
ptr::null_mut(),
method.as_ptr(),
path.as_ptr(),
200,
ptr::null(),
);
assert_eq!(result, -1);
}
}
#[test]
fn test_mockforge_server_stub_invalid_json() {
unsafe {
let method = CString::new("GET").unwrap();
let path = CString::new("/test").unwrap();
let body = CString::new("{invalid json").unwrap();
let result = mockforge_server_stub(
ptr::null_mut(),
method.as_ptr(),
path.as_ptr(),
200,
body.as_ptr(),
);
assert_eq!(result, -1);
}
}
#[test]
fn test_mockforge_server_url_null_handle() {
unsafe {
let url = mockforge_server_url(ptr::null());
assert!(url.is_null());
}
}
#[test]
fn test_mockforge_free_string_null() {
unsafe {
mockforge_free_string(ptr::null_mut());
}
}
#[test]
fn test_mockforge_free_string_valid() {
unsafe {
let test_str = CString::new("test").unwrap();
let raw_ptr = test_str.into_raw();
mockforge_free_string(raw_ptr);
}
}
#[test]
fn test_mockforge_last_error_returns_null() {
unsafe {
let error = mockforge_last_error();
assert!(error.is_null());
}
}
#[test]
fn test_cstring_conversion_utf8() {
unsafe {
let method = CString::new("GET").unwrap();
let method_ptr = method.as_ptr();
let converted = CStr::from_ptr(method_ptr);
assert_eq!(converted.to_str().unwrap(), "GET");
}
}
#[test]
fn test_cstring_conversion_special_chars() {
unsafe {
let path = CString::new("/api/users/{id}").unwrap();
let path_ptr = path.as_ptr();
let converted = CStr::from_ptr(path_ptr);
assert_eq!(converted.to_str().unwrap(), "/api/users/{id}");
}
}
#[test]
fn test_json_value_parsing() {
unsafe {
let valid_json = CString::new(r#"{"key":"value"}"#).unwrap();
let json_raw_ptr = valid_json.as_ptr();
let json_content = CStr::from_ptr(json_raw_ptr).to_str().unwrap();
let result: Result<serde_json::Value, _> = serde_json::from_str(json_content);
assert!(result.is_ok());
}
}
#[test]
fn test_status_code_range() {
let status_codes = [200, 201, 400, 404, 500, 503];
for &status in &status_codes {
assert!(status > 0);
assert!(status < 600);
}
}
#[test]
fn test_mock_server_handle_runtime_creation() {
let runtime = Runtime::new();
assert!(runtime.is_ok());
}
#[test]
fn test_arc_mutex_server_creation() {
let server = MockServer::default();
let _arc_server = Arc::new(Mutex::new(server));
}
#[tokio::test]
async fn test_server_in_ffi_context() {
let result = Box::pin(MockServer::new().port(0).start()).await;
if let Ok(server) = result {
let _ = Box::pin(server.stop()).await;
}
}
#[test]
fn test_multiple_cstring_allocations() {
unsafe {
let strings = vec![
CString::new("GET").unwrap(),
CString::new("POST").unwrap(),
CString::new("/api/test").unwrap(),
CString::new(r#"{"test":true}"#).unwrap(),
];
for s in strings {
let ptr = s.into_raw();
mockforge_free_string(ptr);
}
}
}
#[test]
fn test_error_code_conventions() {
let success = 0;
let error = -1;
assert_eq!(success, 0);
assert_eq!(error, -1);
assert!(error < success);
}
}