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