@@ -649,3 +649,271 @@ def handler(
649
649
assert parameter .schema_ .type == "integer"
650
650
assert parameter .schema_ .default == 1
651
651
assert parameter .schema_ .title == "Count"
652
+
653
+
654
+ def test_openapi_file_upload_parameters ():
655
+ """Test File parameter generates correct OpenAPI schema for file uploads."""
656
+ from aws_lambda_powertools .event_handler .openapi .params import _File , _Form
657
+
658
+ app = APIGatewayRestResolver (enable_validation = True )
659
+
660
+ @app .post ("/upload" )
661
+ def upload_file (
662
+ file : Annotated [bytes , _File (description = "File to upload" )],
663
+ filename : Annotated [str , _Form (description = "Name of the file" )]
664
+ ):
665
+ return {"message" : f"Uploaded { filename } " , "size" : len (file )}
666
+
667
+ schema = app .get_openapi_schema ()
668
+
669
+ # Check that the endpoint is present
670
+ assert "/upload" in schema .paths
671
+
672
+ post_op = schema .paths ["/upload" ].post
673
+ assert post_op is not None
674
+
675
+ # Check request body
676
+ request_body = post_op .requestBody
677
+ assert request_body is not None
678
+ assert request_body .required is True
679
+
680
+ # Check content type is multipart/form-data
681
+ assert "multipart/form-data" in request_body .content
682
+
683
+ # Get the schema reference
684
+ multipart_content = request_body .content ["multipart/form-data" ]
685
+ assert multipart_content .schema_ is not None
686
+
687
+ # Check that it references a component schema
688
+ schema_ref = multipart_content .schema_ .ref
689
+ assert schema_ref is not None
690
+ assert schema_ref .startswith ("#/components/schemas/" )
691
+
692
+ # Get the component schema name
693
+ component_name = schema_ref .split ("/" )[- 1 ]
694
+ assert component_name in schema .components .schemas
695
+
696
+ # Check the component schema properties
697
+ component_schema = schema .components .schemas [component_name ]
698
+ properties = component_schema .properties
699
+
700
+ # Check file parameter
701
+ assert "file" in properties
702
+ file_prop = properties ["file" ]
703
+ assert file_prop .type == "string"
704
+ assert file_prop .format == "binary" # This is the key assertion
705
+ assert file_prop .title == "File"
706
+ assert file_prop .description == "File to upload"
707
+
708
+ # Check form parameter
709
+ assert "filename" in properties
710
+ filename_prop = properties ["filename" ]
711
+ assert filename_prop .type == "string"
712
+ assert filename_prop .title == "Filename"
713
+ assert filename_prop .description == "Name of the file"
714
+
715
+ # Check required fields
716
+ assert component_schema .required == ["file" , "filename" ]
717
+
718
+
719
+ def test_openapi_form_only_parameters ():
720
+ """Test Form parameters generate application/x-www-form-urlencoded content type."""
721
+ from aws_lambda_powertools .event_handler .openapi .params import _Form
722
+
723
+ app = APIGatewayRestResolver (enable_validation = True )
724
+
725
+ @app .post ("/form-data" )
726
+ def create_form_data (
727
+ name : Annotated [str , _Form (description = "User name" )],
728
+ email :
Annotated [
str ,
_Form (
description = "User email" )]
= "[email protected] "
729
+ ):
730
+ return {"name" : name , "email" : email }
731
+
732
+ schema = app .get_openapi_schema ()
733
+
734
+ # Check that the endpoint is present
735
+ assert "/form-data" in schema .paths
736
+
737
+ post_op = schema .paths ["/form-data" ].post
738
+ assert post_op is not None
739
+
740
+ # Check request body
741
+ request_body = post_op .requestBody
742
+ assert request_body is not None
743
+
744
+ # Check content type is application/x-www-form-urlencoded
745
+ assert "application/x-www-form-urlencoded" in request_body .content
746
+
747
+ # Get the schema reference
748
+ form_content = request_body .content ["application/x-www-form-urlencoded" ]
749
+ assert form_content .schema_ is not None
750
+
751
+ # Check that it references a component schema
752
+ schema_ref = form_content .schema_ .ref
753
+ assert schema_ref is not None
754
+ assert schema_ref .startswith ("#/components/schemas/" )
755
+
756
+ # Get the component schema
757
+ component_name = schema_ref .split ("/" )[- 1 ]
758
+ assert component_name in schema .components .schemas
759
+
760
+ component_schema = schema .components .schemas [component_name ]
761
+ properties = component_schema .properties
762
+
763
+ # Check form parameters
764
+ assert "name" in properties
765
+ name_prop = properties ["name" ]
766
+ assert name_prop .type == "string"
767
+ assert name_prop .description == "User name"
768
+
769
+ assert "email" in properties
770
+ email_prop = properties ["email" ]
771
+ assert email_prop .type == "string"
772
+ assert email_prop .description == "User email"
773
+ assert email_prop .
default == "[email protected] "
774
+
775
+ # Check required fields (only name should be required since email has default)
776
+ assert component_schema .required == ["name" ]
777
+
778
+
779
+ def test_openapi_mixed_file_and_form_parameters ():
780
+ """Test mixed File and Form parameters use multipart/form-data."""
781
+ from aws_lambda_powertools .event_handler .openapi .params import _File , _Form
782
+
783
+ app = APIGatewayRestResolver (enable_validation = True )
784
+
785
+ @app .post ("/mixed" )
786
+ def upload_with_metadata (
787
+ file : Annotated [bytes , _File (description = "Document to upload" )],
788
+ title : Annotated [str , _Form (description = "Document title" )],
789
+ category : Annotated [str , _Form (description = "Document category" )] = "general"
790
+ ):
791
+ return {
792
+ "title" : title ,
793
+ "category" : category ,
794
+ "file_size" : len (file )
795
+ }
796
+
797
+ schema = app .get_openapi_schema ()
798
+
799
+ # Check that the endpoint is present
800
+ assert "/mixed" in schema .paths
801
+
802
+ post_op = schema .paths ["/mixed" ].post
803
+ request_body = post_op .requestBody
804
+
805
+ # When both File and Form parameters are present, should use multipart/form-data
806
+ assert "multipart/form-data" in request_body .content
807
+
808
+ # Get the component schema
809
+ multipart_content = request_body .content ["multipart/form-data" ]
810
+ schema_ref = multipart_content .schema_ .ref
811
+ component_name = schema_ref .split ("/" )[- 1 ]
812
+ component_schema = schema .components .schemas [component_name ]
813
+
814
+ properties = component_schema .properties
815
+
816
+ # Check file parameter has binary format
817
+ assert "file" in properties
818
+ file_prop = properties ["file" ]
819
+ assert file_prop .format == "binary"
820
+
821
+ # Check form parameters are present
822
+ assert "title" in properties
823
+ assert "category" in properties
824
+
825
+ # Check required fields
826
+ assert "file" in component_schema .required
827
+ assert "title" in component_schema .required
828
+ assert "category" not in component_schema .required # has default value
829
+
830
+
831
+ def test_openapi_multiple_file_uploads ():
832
+ """Test multiple file uploads with List[bytes] type."""
833
+ from aws_lambda_powertools .event_handler .openapi .params import _File , _Form
834
+
835
+ app = APIGatewayRestResolver (enable_validation = True )
836
+
837
+ @app .post ("/upload-multiple" )
838
+ def upload_multiple_files (
839
+ files : Annotated [List [bytes ], _File (description = "Files to upload" )],
840
+ description : Annotated [str , _Form (description = "Upload description" )]
841
+ ):
842
+ return {
843
+ "message" : f"Uploaded { len (files )} files" ,
844
+ "description" : description ,
845
+ "total_size" : sum (len (file ) for file in files )
846
+ }
847
+
848
+ schema = app .get_openapi_schema ()
849
+
850
+ # Check that the endpoint is present
851
+ assert "/upload-multiple" in schema .paths
852
+
853
+ post_op = schema .paths ["/upload-multiple" ].post
854
+ request_body = post_op .requestBody
855
+
856
+ # Should use multipart/form-data for file uploads
857
+ assert "multipart/form-data" in request_body .content
858
+
859
+ # Get the component schema
860
+ multipart_content = request_body .content ["multipart/form-data" ]
861
+ schema_ref = multipart_content .schema_ .ref
862
+ component_name = schema_ref .split ("/" )[- 1 ]
863
+ component_schema = schema .components .schemas [component_name ]
864
+
865
+ properties = component_schema .properties
866
+
867
+ # Check files parameter
868
+ assert "files" in properties
869
+ files_prop = properties ["files" ]
870
+
871
+ # For List[bytes] with File annotation, should be array of strings with binary format
872
+ assert files_prop .type == "array"
873
+ assert files_prop .items .type == "string"
874
+ assert files_prop .items .format == "binary"
875
+
876
+ # Check form parameter
877
+ assert "description" in properties
878
+ description_prop = properties ["description" ]
879
+ assert description_prop .type == "string"
880
+
881
+
882
+ def test_openapi_public_file_form_exports ():
883
+ """Test that File and Form are properly exported for public use."""
884
+ from aws_lambda_powertools .event_handler .openapi .params import File , Form
885
+
886
+ app = APIGatewayRestResolver (enable_validation = True )
887
+
888
+ @app .post ("/public-api" )
889
+ def upload_with_public_types (
890
+ file : File , # Using the public export
891
+ name : Form # Using the public export
892
+ ):
893
+ return {"status" : "uploaded" }
894
+
895
+ schema = app .get_openapi_schema ()
896
+
897
+ # Check that the endpoint works with public exports
898
+ assert "/public-api" in schema .paths
899
+
900
+ post_op = schema .paths ["/public-api" ].post
901
+ request_body = post_op .requestBody
902
+
903
+ # Should generate multipart/form-data
904
+ assert "multipart/form-data" in request_body .content
905
+
906
+ # Get the component schema
907
+ multipart_content = request_body .content ["multipart/form-data" ]
908
+ schema_ref = multipart_content .schema_ .ref
909
+ component_name = schema_ref .split ("/" )[- 1 ]
910
+ component_schema = schema .components .schemas [component_name ]
911
+
912
+ properties = component_schema .properties
913
+
914
+ # Check that both parameters are present and correctly typed
915
+ assert "file" in properties
916
+ assert properties ["file" ].format == "binary"
917
+
918
+ assert "name" in properties
919
+ assert properties ["name" ].type == "string"
0 commit comments