From e542b068695906fcb576986a0a664030df7b1038 Mon Sep 17 00:00:00 2001 From: Nikita Vakula <52108696+krjakbrjak@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:14:16 +0200 Subject: [PATCH] [python-fastapi] return 500 if not implemented, added some unittests (#19196) * [python-fastapi] Added some tests for FastAPI generator 1. Checks the generation of the implementation package. 2. Checks if the endpoints with and without descriptions generate correct output. Signed-off-by: Nikita Vakula * [python-fastapi] Raise 500 if there is no implementation Signed-off-by: Nikita Vakula --------- Signed-off-by: Nikita Vakula --- .../resources/python-fastapi/api.mustache | 5 +- .../python/PythonFastapiCodegenTest.java | 56 +++++++++++++++++++ .../src/test/resources/3_1/nodesc.yaml | 35 ++++++++++++ .../src/openapi_server/apis/fake_api.py | 3 + .../src/openapi_server/apis/pet_api.py | 17 ++++++ .../src/openapi_server/apis/store_api.py | 9 +++ .../src/openapi_server/apis/user_api.py | 17 ++++++ 7 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java create mode 100644 modules/openapi-generator/src/test/resources/3_1/nodesc.yaml diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache index 43d115853833..48bbdc26783e 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache @@ -14,6 +14,7 @@ from fastapi import ( # noqa: F401 Depends, Form, Header, + HTTPException, Path, Query, Response, @@ -65,7 +66,9 @@ async def {{operationId}}( {{/hasAuthMethods}} ) -> {{returnType}}{{^returnType}}None{{/returnType}}: {{#notes}}"""{{.}}""" - {{/notes}}return await Base{{classname}}.subclasses[0]().{{operationId}}({{#allParams}}{{>impl_argument}}{{^-last}}, {{/-last}}{{/allParams}}) + {{/notes}}if not Base{{classname}}.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") + return await Base{{classname}}.subclasses[0]().{{operationId}}({{#allParams}}{{>impl_argument}}{{^-last}}, {{/-last}}{{/allParams}}) {{^-last}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java new file mode 100644 index 000000000000..458c9415ebee --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java @@ -0,0 +1,56 @@ +package org.openapitools.codegen.python; + +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.testng.annotations.Test; +import org.openapitools.codegen.CodegenConstants; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class PythonFastapiCodegenTest { + @Test + public void testAdditionalPropertiesPutForConfigValues() throws Exception { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final String IMPL_PKG = "impl_package"; + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("python-fastapi") + .setPackageName("nodesc") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")) + .setInputSpec("src/test/resources/3_1/nodesc.yaml") + .addAdditionalProperty(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, IMPL_PKG); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + TestUtils.assertFileExists(Paths.get(output.getAbsolutePath(), "/src", IMPL_PKG, "__init__.py")); + } + + @Test + public void testEndpointSpecsWithoutDescription() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("python-fastapi") + .setPackageName("nodesc") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")) + .setInputSpec("src/test/resources/3_1/nodesc.yaml"); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + TestUtils.assertFileContains(Paths.get(output + "/src/nodesc/apis/nodesc_api.py"), + "return await BaseNodescApi.subclasses[0]().nodesc()\n"); + TestUtils.assertFileContains(Paths.get(output + "/src/nodesc/apis/desc_api.py"), + "return await BaseDescApi.subclasses[0]().desc()\n"); + } +} diff --git a/modules/openapi-generator/src/test/resources/3_1/nodesc.yaml b/modules/openapi-generator/src/test/resources/3_1/nodesc.yaml new file mode 100644 index 000000000000..8aea06fd7126 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/nodesc.yaml @@ -0,0 +1,35 @@ +openapi: 3.1.0 +info: + description: >- + Yet another example. + version: 1.0.0 + title: OpenAPI example +tags: + - name: nodesc + description: Endpoints have no description + - name: desc + description: Endpoints have description +paths: + /nodesc: + post: + tags: + - nodesc + summary: dummy operation + operationId: nodesc + responses: + '200': + description: successful operation + '405': + description: Invalid input + /desc: + post: + tags: + - desc + summary: dummy operation + description: 'Description' + operationId: desc + responses: + '200': + description: successful operation + '405': + description: Invalid input diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py index 66a8d7fb4d3e..d9ee44de6900 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py @@ -14,6 +14,7 @@ Depends, Form, Header, + HTTPException, Path, Query, Response, @@ -46,4 +47,6 @@ async def fake_query_param_default( no_default: str = Query(None, description="no default value", alias="noDefault"), ) -> None: """""" + if not BaseFakeApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseFakeApi.subclasses[0]().fake_query_param_default(has_default, no_default) diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py index 56535370873c..70feda075f28 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py @@ -14,6 +14,7 @@ Depends, Form, Header, + HTTPException, Path, Query, Response, @@ -50,6 +51,8 @@ async def add_pet( ), ) -> Pet: """""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().add_pet(pet) @@ -70,6 +73,8 @@ async def delete_pet( ), ) -> None: """""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().delete_pet(petId, api_key) @@ -90,6 +95,8 @@ async def find_pets_by_status( ), ) -> List[Pet]: """Multiple status values can be provided with comma separated strings""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().find_pets_by_status(status) @@ -110,6 +117,8 @@ async def find_pets_by_tags( ), ) -> List[Pet]: """Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().find_pets_by_tags(tags) @@ -131,6 +140,8 @@ async def get_pet_by_id( ), ) -> Pet: """Returns a single pet""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().get_pet_by_id(petId) @@ -153,6 +164,8 @@ async def update_pet( ), ) -> Pet: """""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().update_pet(pet) @@ -174,6 +187,8 @@ async def update_pet_with_form( ), ) -> None: """""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().update_pet_with_form(petId, name, status) @@ -195,4 +210,6 @@ async def upload_file( ), ) -> ApiResponse: """""" + if not BasePetApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BasePetApi.subclasses[0]().upload_file(petId, additional_metadata, file) diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py index d367a22e62ea..8f492e32ceff 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py @@ -14,6 +14,7 @@ Depends, Form, Header, + HTTPException, Path, Query, Response, @@ -46,6 +47,8 @@ async def delete_order( orderId: str = Path(..., description="ID of the order that needs to be deleted"), ) -> None: """For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors""" + if not BaseStoreApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseStoreApi.subclasses[0]().delete_order(orderId) @@ -64,6 +67,8 @@ async def get_inventory( ), ) -> Dict[str, int]: """Returns a map of status codes to quantities""" + if not BaseStoreApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseStoreApi.subclasses[0]().get_inventory() @@ -82,6 +87,8 @@ async def get_order_by_id( orderId: int = Path(..., description="ID of pet that needs to be fetched", ge=1, le=5), ) -> Order: """For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions""" + if not BaseStoreApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseStoreApi.subclasses[0]().get_order_by_id(orderId) @@ -99,4 +106,6 @@ async def place_order( order: Order = Body(None, description="order placed for purchasing the pet"), ) -> Order: """""" + if not BaseStoreApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseStoreApi.subclasses[0]().place_order(order) diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py index 5bbd3240d8db..f09f127ba51a 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py @@ -14,6 +14,7 @@ Depends, Form, Header, + HTTPException, Path, Query, Response, @@ -48,6 +49,8 @@ async def create_user( ), ) -> None: """This can only be done by the logged in user.""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().create_user(user) @@ -67,6 +70,8 @@ async def create_users_with_array_input( ), ) -> None: """""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().create_users_with_array_input(user) @@ -86,6 +91,8 @@ async def create_users_with_list_input( ), ) -> None: """""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().create_users_with_list_input(user) @@ -106,6 +113,8 @@ async def delete_user( ), ) -> None: """This can only be done by the logged in user.""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().delete_user(username) @@ -124,6 +133,8 @@ async def get_user_by_name( username: str = Path(..., description="The name that needs to be fetched. Use user1 for testing."), ) -> User: """""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().get_user_by_name(username) @@ -142,6 +153,8 @@ async def login_user( password: str = Query(None, description="The password for login in clear text", alias="password"), ) -> str: """""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().login_user(username, password) @@ -160,6 +173,8 @@ async def logout_user( ), ) -> None: """""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().logout_user() @@ -181,4 +196,6 @@ async def update_user( ), ) -> None: """This can only be done by the logged in user.""" + if not BaseUserApi.subclasses: + raise HTTPException(status_code=500, detail="Not implemented") return await BaseUserApi.subclasses[0]().update_user(username, user)