1#![allow(unsafe_code)]
6
7use crate::server::MockServer;
8use std::ffi::{CStr, CString};
9use std::os::raw::c_char;
10use std::ptr;
11use std::sync::Arc;
12use tokio::runtime::Runtime;
13use tokio::sync::Mutex;
14
15pub struct MockServerHandle {
17 server: Arc<Mutex<MockServer>>,
18 runtime: Runtime,
19}
20
21#[no_mangle]
26pub unsafe extern "C" fn mockforge_server_new(port: u16) -> *mut MockServerHandle {
27 let runtime = match Runtime::new() {
28 Ok(rt) => rt,
29 Err(_) => return ptr::null_mut(),
30 };
31
32 let server = runtime.block_on(async { MockServer::new().port(port).start().await });
34
35 let server = match server {
36 Ok(s) => s,
37 Err(_) => return ptr::null_mut(),
38 };
39
40 let handle = MockServerHandle {
41 server: Arc::new(Mutex::new(server)),
42 runtime,
43 };
44
45 Box::into_raw(Box::new(handle))
46}
47
48#[no_mangle]
53pub unsafe extern "C" fn mockforge_server_destroy(handle: *mut MockServerHandle) {
54 if handle.is_null() {
55 return;
56 }
57
58 let handle = Box::from_raw(handle);
59 let server = handle.server.clone();
60
61 handle.runtime.block_on(async move {
62 let mut server = server.lock().await;
63 let _ = std::mem::take(&mut *server).stop().await;
64 });
65}
66
67#[no_mangle]
74pub unsafe extern "C" fn mockforge_server_stub(
75 handle: *mut MockServerHandle,
76 method: *const c_char,
77 path: *const c_char,
78 status: u16,
79 body: *const c_char,
80) -> i32 {
81 if handle.is_null() || method.is_null() || path.is_null() || body.is_null() {
82 return -1;
83 }
84
85 let handle = &*handle;
86
87 let method = match CStr::from_ptr(method).to_str() {
88 Ok(s) => s,
89 Err(_) => return -1,
90 };
91
92 let path = match CStr::from_ptr(path).to_str() {
93 Ok(s) => s,
94 Err(_) => return -1,
95 };
96
97 let body = match CStr::from_ptr(body).to_str() {
98 Ok(s) => s,
99 Err(_) => return -1,
100 };
101
102 let body_value: serde_json::Value = match serde_json::from_str(body) {
103 Ok(v) => v,
104 Err(_) => return -1,
105 };
106
107 let server = handle.server.clone();
108 let result = handle.runtime.block_on(async move {
109 let mut server = server.lock().await;
110 server.stub_response(method, path, body_value).await
111 });
112
113 match result {
114 Ok(()) => 0,
115 Err(_) => -1,
116 }
117}
118
119#[no_mangle]
125pub unsafe extern "C" fn mockforge_server_url(handle: *const MockServerHandle) -> *mut c_char {
126 if handle.is_null() {
127 return ptr::null_mut();
128 }
129
130 let handle = &*handle;
131 let server = handle.server.clone();
132
133 let url = handle.runtime.block_on(async move {
134 let server = server.lock().await;
135 server.url()
136 });
137
138 match CString::new(url) {
139 Ok(s) => s.into_raw(),
140 Err(_) => ptr::null_mut(),
141 }
142}
143
144#[no_mangle]
149pub unsafe extern "C" fn mockforge_free_string(s: *mut c_char) {
150 if !s.is_null() {
151 let _ = CString::from_raw(s);
152 }
153}
154
155#[no_mangle]
160pub const unsafe extern "C" fn mockforge_last_error() -> *mut c_char {
161 ptr::null_mut()
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use std::ffi::CStr;
169
170 #[test]
171 fn test_mock_server_handle_size() {
172 assert!(std::mem::size_of::<MockServerHandle>() > 0);
174 }
175
176 #[test]
177 fn test_mockforge_server_new_null_on_invalid_port() {
178 unsafe {
179 let handle = mockforge_server_new(0);
182
183 if !handle.is_null() {
186 mockforge_server_destroy(handle);
187 }
188 }
189 }
190
191 #[test]
192 fn test_mockforge_server_destroy_null_handle() {
193 unsafe {
194 mockforge_server_destroy(ptr::null_mut());
196 }
197 }
198
199 #[test]
200 fn test_mockforge_server_stub_null_handle() {
201 unsafe {
202 let method = CString::new("GET").unwrap();
203 let path = CString::new("/test").unwrap();
204 let body = CString::new("{}").unwrap();
205
206 let result = mockforge_server_stub(
207 ptr::null_mut(),
208 method.as_ptr(),
209 path.as_ptr(),
210 200,
211 body.as_ptr(),
212 );
213
214 assert_eq!(result, -1);
215 }
216 }
217
218 #[test]
219 fn test_mockforge_server_stub_null_method() {
220 unsafe {
221 let path = CString::new("/test").unwrap();
223 let body = CString::new("{}").unwrap();
224
225 let result = mockforge_server_stub(
227 ptr::null_mut(),
228 ptr::null(),
229 path.as_ptr(),
230 200,
231 body.as_ptr(),
232 );
233
234 assert_eq!(result, -1);
235 }
236 }
237
238 #[test]
239 fn test_mockforge_server_stub_null_path() {
240 unsafe {
241 let method = CString::new("GET").unwrap();
242 let body = CString::new("{}").unwrap();
243
244 let result = mockforge_server_stub(
245 ptr::null_mut(),
246 method.as_ptr(),
247 ptr::null(),
248 200,
249 body.as_ptr(),
250 );
251
252 assert_eq!(result, -1);
253 }
254 }
255
256 #[test]
257 fn test_mockforge_server_stub_null_body() {
258 unsafe {
259 let method = CString::new("GET").unwrap();
260 let path = CString::new("/test").unwrap();
261
262 let result = mockforge_server_stub(
263 ptr::null_mut(),
264 method.as_ptr(),
265 path.as_ptr(),
266 200,
267 ptr::null(),
268 );
269
270 assert_eq!(result, -1);
271 }
272 }
273
274 #[test]
275 fn test_mockforge_server_stub_invalid_json() {
276 unsafe {
277 let method = CString::new("GET").unwrap();
278 let path = CString::new("/test").unwrap();
279 let body = CString::new("{invalid json").unwrap();
280
281 let result = mockforge_server_stub(
283 ptr::null_mut(),
284 method.as_ptr(),
285 path.as_ptr(),
286 200,
287 body.as_ptr(),
288 );
289
290 assert_eq!(result, -1);
291 }
292 }
293
294 #[test]
295 fn test_mockforge_server_url_null_handle() {
296 unsafe {
297 let url = mockforge_server_url(ptr::null());
298 assert!(url.is_null());
299 }
300 }
301
302 #[test]
303 fn test_mockforge_free_string_null() {
304 unsafe {
305 mockforge_free_string(ptr::null_mut());
307 }
308 }
309
310 #[test]
311 fn test_mockforge_free_string_valid() {
312 unsafe {
313 let test_str = CString::new("test").unwrap();
314 let raw_ptr = test_str.into_raw();
315
316 mockforge_free_string(raw_ptr);
318
319 }
322 }
323
324 #[test]
325 fn test_mockforge_last_error_returns_null() {
326 unsafe {
327 let error = mockforge_last_error();
328 assert!(error.is_null());
329 }
330 }
331
332 #[test]
333 fn test_cstring_conversion_utf8() {
334 unsafe {
335 let method = CString::new("GET").unwrap();
336 let method_ptr = method.as_ptr();
337
338 let converted = CStr::from_ptr(method_ptr);
339 assert_eq!(converted.to_str().unwrap(), "GET");
340 }
341 }
342
343 #[test]
344 fn test_cstring_conversion_special_chars() {
345 unsafe {
346 let path = CString::new("/api/users/{id}").unwrap();
347 let path_ptr = path.as_ptr();
348
349 let converted = CStr::from_ptr(path_ptr);
350 assert_eq!(converted.to_str().unwrap(), "/api/users/{id}");
351 }
352 }
353
354 #[test]
355 fn test_json_value_parsing() {
356 unsafe {
357 let valid_json = CString::new(r#"{"key":"value"}"#).unwrap();
358 let json_ptr = valid_json.as_ptr();
359
360 let json_str = CStr::from_ptr(json_ptr).to_str().unwrap();
361 let result: Result<serde_json::Value, _> = serde_json::from_str(json_str);
362 assert!(result.is_ok());
363 }
364 }
365
366 #[test]
367 fn test_status_code_range() {
368 let status_codes = [200, 201, 400, 404, 500, 503];
370
371 for &status in &status_codes {
372 assert!(status > 0);
373 assert!(status < 600);
374 }
375 }
376
377 #[test]
378 fn test_mock_server_handle_runtime_creation() {
379 let runtime = Runtime::new();
381 assert!(runtime.is_ok());
382 }
383
384 #[test]
385 fn test_arc_mutex_server_creation() {
386 let server = MockServer::default();
388 let _arc_server = Arc::new(Mutex::new(server));
389 }
391
392 #[tokio::test]
393 async fn test_server_in_ffi_context() {
394 let runtime = Runtime::new().unwrap();
396
397 runtime.block_on(async {
398 let result = MockServer::new().port(0).start().await;
399 if let Ok(mut server) = result {
401 let _ = server.stop().await;
402 }
403 });
404 }
405
406 #[test]
407 fn test_multiple_cstring_allocations() {
408 unsafe {
409 let strings = vec![
411 CString::new("GET").unwrap(),
412 CString::new("POST").unwrap(),
413 CString::new("/api/test").unwrap(),
414 CString::new(r#"{"test":true}"#).unwrap(),
415 ];
416
417 for s in strings {
418 let ptr = s.into_raw();
419 mockforge_free_string(ptr);
420 }
421 }
422 }
423
424 #[test]
425 fn test_error_code_conventions() {
426 let success = 0;
428 let error = -1;
429
430 assert_eq!(success, 0);
431 assert_eq!(error, -1);
432 assert!(error < success);
433 }
434}