@@ -913,3 +913,182 @@ def upload_with_public_types(
913
913
914
914
assert "name" in properties
915
915
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