@@ -61,6 +61,7 @@ use pact_ffi::mock_server::handles::{
6161 pactffi_upon_receiving,
6262 pactffi_with_binary_file,
6363 pactffi_with_body,
64+ pactffi_with_binary_body,
6465 pactffi_with_header,
6566 pactffi_with_header_v2,
6667 pactffi_with_multipart_file,
@@ -2309,3 +2310,112 @@ fn date_matcher_in_response_body_without_generator_type() {
23092310
23102311 expect ! ( mismatches) . to ( be_equal_to ( "[]" ) ) ;
23112312}
2313+
2314+ // Test multipart request with JSON metadata and JPEG image using matching rules
2315+ #[ test_log:: test]
2316+ fn mime_multipart_with_json_and_image ( ) {
2317+ let pact_handle = pactffi_new_pact (
2318+ c"multipart-json-image-consumer" . as_ptr ( ) ,
2319+ c"multipart-json-image-provider" . as_ptr ( )
2320+ ) ;
2321+ let interaction = pactffi_new_interaction (
2322+ pact_handle,
2323+ c"multipart_with_json_metadata_and_jpeg_image" . as_ptr ( )
2324+ ) ;
2325+ pactffi_with_request ( interaction, c"POST" . as_ptr ( ) , c"/upload" . as_ptr ( ) ) ;
2326+
2327+ // Create the expected multipart body with JSON metadata and JPEG image
2328+ let jpeg_bytes: [ u8 ; 48 ] = [
2329+
2330+ // Magic bytes
2331+ 0xff , 0xd8 , 0xff , 0xe0 , 0x00 , 0x10 , 0x4a , 0x46 , 0x49 , 0x46 , 0x00 , 0x01 ,
2332+ // Other data
2333+ 0x01 , 0x00 , 0x00 , 0x01 , 0x00 , 0x01 , 0x00 , 0x00 , 0xff , 0xdb , 0x00 , 0x43 ,
2334+ 0x00 , 0x10 , 0x0b , 0x0c , 0x0e , 0x0c , 0x0a , 0x10 , 0x0e , 0x0d , 0x0e , 0x12 ,
2335+ 0x11 , 0x10 , 0x13 , 0x18 , 0x28 , 0x1a , 0x18 , 0x16 , 0x16 , 0x18 , 0x31 , 0x23 ,
2336+ ] ;
2337+
2338+ let boundary = "test-boundary-12345" ;
2339+ let expected_body = format ! (
2340+ "--{boundary}\r \n \
2341+ Content-Type: application/json\r \n \
2342+ Content-Disposition: form-data; name=\" metadata\" \r \n \
2343+ \r \n \
2344+ {{\" name\" :\" test\" ,\" size\" :100}}\r \n \
2345+ --{boundary}\r \n \
2346+ Content-Type: image/jpeg\r \n \
2347+ Content-Disposition: form-data; name=\" image\" ; filename=\" test.jpg\" \r \n \
2348+ \r \n ",
2349+ ) ;
2350+
2351+ let mut body = expected_body. as_bytes ( ) . to_vec ( ) ;
2352+ body. extend_from_slice ( & jpeg_bytes) ;
2353+ body. extend_from_slice ( format ! ( "\r \n --{}--\r \n " , boundary) . as_bytes ( ) ) ;
2354+
2355+ let content_type = CString :: new ( format ! ( "multipart/form-data; boundary={}" , boundary) ) . unwrap ( ) ;
2356+ pactffi_with_binary_body (
2357+ interaction,
2358+ InteractionPart :: Request ,
2359+ content_type. as_ptr ( ) ,
2360+ body. as_ptr ( ) ,
2361+ body. len ( )
2362+ ) ;
2363+
2364+ // Add matching rules for the JSON metadata and image
2365+ let matching_rules = matchingrules ! {
2366+ "body" => {
2367+ "$.metadata" => [ MatchingRule :: Type ] ,
2368+ "$.metadata.name" => [ MatchingRule :: Regex ( "^[a-zA-Z]+$" . to_string( ) ) ] ,
2369+ "$.metadata.size" => [ MatchingRule :: Integer ] ,
2370+ "$.image" => [ MatchingRule :: ContentType ( "image/jpeg" . to_string( ) ) ]
2371+ } ,
2372+ "header" => {
2373+ "Content-Type" => [
2374+ MatchingRule :: Regex ( "multipart/form-data;(\\ s*charset=[^;]*;)?\\ s*boundary=.*" . to_string( ) )
2375+ ]
2376+ }
2377+ } ;
2378+ let matching_rules_json = matchers_to_json ( & matching_rules, & PactSpecification :: V4 ) ;
2379+ println ! ( "Matching rules JSON: {}" , matching_rules_json) ;
2380+ let matching_rules_str = CString :: new ( matching_rules_json. to_string ( ) ) . unwrap ( ) ;
2381+ pactffi_with_matching_rules ( interaction, InteractionPart :: Request , matching_rules_str. as_ptr ( ) ) ;
2382+
2383+ pactffi_response_status ( interaction, 201 ) ;
2384+
2385+ let port = pactffi_create_mock_server_for_transport ( pact_handle, c"127.0.0.1" . as_ptr ( ) , 0 , null ( ) , null ( ) ) ;
2386+ expect ! ( port) . to ( be_greater_than ( 0 ) ) ;
2387+
2388+ // Make actual request with different JSON values and the same JPEG structure
2389+ let client = Client :: default ( ) ;
2390+ let form = reqwest:: blocking:: multipart:: Form :: new ( )
2391+ . text ( "metadata" , r#"{"name":"different","size":200}"# )
2392+ . part (
2393+ "image" ,
2394+ reqwest:: blocking:: multipart:: Part :: bytes ( jpeg_bytes. to_vec ( ) )
2395+ . file_name ( "actual.jpg" )
2396+ . mime_str ( "image/jpeg" ) . unwrap ( )
2397+ ) ;
2398+
2399+ let result = client. post ( format ! ( "http://127.0.0.1:{}/upload" , port) . as_str ( ) )
2400+ . multipart ( form)
2401+ . send ( ) ;
2402+
2403+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ; // Give mock server some time to update events
2404+ let mismatches = unsafe {
2405+ CStr :: from_ptr ( pactffi_mock_server_mismatches ( port) ) . to_string_lossy ( ) . into_owned ( )
2406+ } ;
2407+
2408+ match result {
2409+ Ok ( res) => {
2410+ let status = res. status ( ) ;
2411+ expect ! ( status) . to ( be_eq ( 201 ) ) ;
2412+ } ,
2413+ Err ( err) => {
2414+ panic ! ( "expected 201 response but request failed - {}" , err) ;
2415+ }
2416+ } ;
2417+
2418+ pactffi_cleanup_mock_server ( port) ;
2419+
2420+ expect ! ( mismatches) . to ( be_equal_to ( "[]" ) ) ;
2421+ }
0 commit comments