@@ -913,3 +913,182 @@ def upload_with_public_types(
913913
914914 assert "name" in properties
915915 assert properties ["name" ].type == "string"
916+
917+
918+ def test_openapi_file_parameter_with_custom_schema_extra ():
919+ """Test File parameter with custom json_schema_extra that gets merged with format: binary."""
920+ from aws_lambda_powertools .event_handler .openapi .params import _File
921+
922+ app = APIGatewayRestResolver (enable_validation = True )
923+
924+ @app .post ("/upload-custom" )
925+ def upload_with_custom_schema (
926+ file : Annotated [
927+ bytes ,
928+ _File (
929+ description = "Custom file upload" , json_schema_extra = {"example" : "file_content" , "title" : "Custom File" }
930+ ),
931+ ],
932+ ):
933+ return {"status" : "uploaded" }
934+
935+ schema = app .get_openapi_schema ()
936+
937+ # Check that the endpoint is present
938+ assert "/upload-custom" in schema .paths
939+
940+ post_op = schema .paths ["/upload-custom" ].post
941+ request_body = post_op .requestBody
942+
943+ # Should use multipart/form-data for file uploads
944+ assert "multipart/form-data" in request_body .content
945+
946+ # Get the component schema
947+ multipart_content = request_body .content ["multipart/form-data" ]
948+ schema_ref = multipart_content .schema_ .ref
949+ component_name = schema_ref .split ("/" )[- 1 ]
950+ component_schema = schema .components .schemas [component_name ]
951+
952+ properties = component_schema .properties
953+
954+ # Check file parameter has both binary format and custom schema extras
955+ assert "file" in properties
956+ file_prop = properties ["file" ]
957+ assert file_prop .format == "binary" # This should be preserved
958+ assert file_prop .description == "Custom file upload"
959+
960+
961+ def test_openapi_body_param_with_conflicting_field_info ():
962+ """Test error condition when both FieldInfo annotation and value are provided."""
963+ from aws_lambda_powertools .event_handler .openapi .params import _File
964+ import pytest
965+
966+ app = APIGatewayRestResolver (enable_validation = True )
967+
968+ # This should work fine - using FieldInfo as annotation
969+ @app .post ("/upload-normal" )
970+ def upload_normal (file : Annotated [bytes , _File (description = "File to upload" )]):
971+ return {"status" : "uploaded" }
972+
973+ # Test that the normal case works
974+ schema = app .get_openapi_schema ()
975+ assert "/upload-normal" in schema .paths
976+
977+
978+ def test_openapi_mixed_body_media_types ():
979+ """Test mixed Body parameters with different media types."""
980+ from aws_lambda_powertools .event_handler .openapi .params import Body
981+ from pydantic import BaseModel
982+
983+ class UserData (BaseModel ):
984+ name : str
985+ email : str
986+
987+ app = APIGatewayRestResolver (enable_validation = True )
988+
989+ @app .post ("/mixed-body" )
990+ def mixed_body_endpoint (user_data : Annotated [UserData , Body (media_type = "application/json" )]):
991+ return {"status" : "created" }
992+
993+ schema = app .get_openapi_schema ()
994+
995+ # Check that the endpoint uses the specified media type
996+ assert "/mixed-body" in schema .paths
997+
998+ post_op = schema .paths ["/mixed-body" ].post
999+ request_body = post_op .requestBody
1000+
1001+ # Should use the specified media type
1002+ assert "application/json" in request_body .content
1003+
1004+
1005+ def test_openapi_form_parameter_edge_cases ():
1006+ """Test Form parameters with various edge cases."""
1007+ from aws_lambda_powertools .event_handler .openapi .params import _Form
1008+ from typing import Optional
1009+
1010+ app = APIGatewayRestResolver (enable_validation = True )
1011+
1012+ @app .post ("/form-edge-cases" )
1013+ def form_edge_cases (
1014+ required_field : Annotated [str , _Form (description = "Required field" )],
1015+ optional_field : Annotated [Optional [str ], _Form (description = "Optional field" )] = None ,
1016+ field_with_default : Annotated [str , _Form (description = "Field with default" )] = "default_value" ,
1017+ ):
1018+ return {"required" : required_field , "optional" : optional_field , "default" : field_with_default }
1019+
1020+ schema = app .get_openapi_schema ()
1021+
1022+ # Check that the endpoint is present
1023+ assert "/form-edge-cases" in schema .paths
1024+
1025+ post_op = schema .paths ["/form-edge-cases" ].post
1026+ request_body = post_op .requestBody
1027+
1028+ # Should use application/x-www-form-urlencoded for form-only parameters
1029+ assert "application/x-www-form-urlencoded" in request_body .content
1030+
1031+ # Get the component schema
1032+ form_content = request_body .content ["application/x-www-form-urlencoded" ]
1033+ schema_ref = form_content .schema_ .ref
1034+ component_name = schema_ref .split ("/" )[- 1 ]
1035+ component_schema = schema .components .schemas [component_name ]
1036+
1037+ properties = component_schema .properties
1038+
1039+ # Check all fields are present
1040+ assert "required_field" in properties
1041+ assert "optional_field" in properties
1042+ assert "field_with_default" in properties
1043+
1044+ # Check required vs optional handling
1045+ assert "required_field" in component_schema .required
1046+ assert "optional_field" not in component_schema .required # Optional
1047+ assert "field_with_default" not in component_schema .required # Has default
1048+
1049+
1050+ def test_openapi_file_with_list_type_edge_case ():
1051+ """Test File parameter with nested List types for edge case coverage."""
1052+ from aws_lambda_powertools .event_handler .openapi .params import _File , _Form
1053+ from typing import List , Optional
1054+
1055+ app = APIGatewayRestResolver (enable_validation = True )
1056+
1057+ @app .post ("/upload-complex" )
1058+ def upload_complex_types (
1059+ files : Annotated [List [bytes ], _File (description = "Multiple files" )],
1060+ metadata : Annotated [Optional [str ], _Form (description = "Optional metadata" )] = None ,
1061+ ):
1062+ total_size = sum (len (file ) for file in files ) if files else 0
1063+ return {"file_count" : len (files ) if files else 0 , "total_size" : total_size , "metadata" : metadata }
1064+
1065+ schema = app .get_openapi_schema ()
1066+
1067+ # Check that the endpoint is present
1068+ assert "/upload-complex" in schema .paths
1069+
1070+ post_op = schema .paths ["/upload-complex" ].post
1071+ request_body = post_op .requestBody
1072+
1073+ # Should use multipart/form-data when files are present
1074+ assert "multipart/form-data" in request_body .content
1075+
1076+ # Get the component schema
1077+ multipart_content = request_body .content ["multipart/form-data" ]
1078+ schema_ref = multipart_content .schema_ .ref
1079+ component_name = schema_ref .split ("/" )[- 1 ]
1080+ component_schema = schema .components .schemas [component_name ]
1081+
1082+ properties = component_schema .properties
1083+
1084+ # Check files parameter is array with binary format items
1085+ assert "files" in properties
1086+ files_prop = properties ["files" ]
1087+ assert files_prop .type == "array"
1088+ assert files_prop .items .type == "string"
1089+ assert files_prop .items .format == "binary"
1090+
1091+ # Check metadata is optional
1092+ assert "metadata" in properties
1093+ assert "files" in component_schema .required
1094+ assert "metadata" not in component_schema .required
0 commit comments