diff --git a/README.md b/README.md index 59d435b..db6ac78 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ - [install mongodb-atlas](https://www.mongodb.com/try/download/community) - Install dependencies `python3 -m pip install -r requirements.txt` +### Windows Users +⚠️ **Known Issue**: `uvloop` does not support Windows and will fail during pip installation with: +``` +RuntimeError: uvloop does not support Windows at the moment +``` + +**Recommended Solution**: Use Docker (easiest approach) + ## Development - make format - make test diff --git a/src/dependencies.py b/src/dependencies.py new file mode 100644 index 0000000..dcb410a --- /dev/null +++ b/src/dependencies.py @@ -0,0 +1,62 @@ +from functools import lru_cache +from typing import Annotated + +from fastapi import Depends + +from src.controllers.rocket import RocketController +from src.controllers.motor import MotorController +from src.controllers.environment import EnvironmentController +from src.controllers.flight import FlightController + +@lru_cache(maxsize=1) +def get_rocket_controller() -> RocketController: + """ + Provides a singleton RocketController instance. + + The controller is stateless and can be safely reused across requests. + Using lru_cache ensures thread-safe singleton behavior. + + Returns: + RocketController: Shared controller instance for rocket operations. + """ + return RocketController() + + +@lru_cache(maxsize=1) +def get_motor_controller() -> MotorController: + """ + Provides a singleton MotorController instance. + + Returns: + MotorController: Shared controller instance for motor operations. + """ + return MotorController() + + +@lru_cache(maxsize=1) +def get_environment_controller() -> EnvironmentController: + """ + Provides a singleton EnvironmentController instance. + + Returns: + EnvironmentController: Shared controller instance for environment operations. + """ + return EnvironmentController() + + +@lru_cache(maxsize=1) +def get_flight_controller() -> FlightController: + """ + Provides a singleton FlightController instance. + + Returns: + FlightController: Shared controller instance for flight operations. + """ + return FlightController() + +RocketControllerDep = Annotated[RocketController, Depends(get_rocket_controller)] +MotorControllerDep = Annotated[MotorController, Depends(get_motor_controller)] +EnvironmentControllerDep = Annotated[ + EnvironmentController, Depends(get_environment_controller) +] +FlightControllerDep = Annotated[FlightController, Depends(get_flight_controller)] diff --git a/src/routes/environment.py b/src/routes/environment.py index 6c5e8b2..3879c50 100644 --- a/src/routes/environment.py +++ b/src/routes/environment.py @@ -11,7 +11,7 @@ EnvironmentRetrieved, ) from src.models.environment import EnvironmentModel -from src.controllers.environment import EnvironmentController +from src.dependencies import EnvironmentControllerDep router = APIRouter( prefix="/environments", @@ -29,6 +29,7 @@ @router.post("/", status_code=201) async def create_environment( environment: EnvironmentModel, + controller: EnvironmentControllerDep, ) -> EnvironmentCreated: """ Creates a new environment @@ -37,12 +38,14 @@ async def create_environment( ``` models.Environment JSON ``` """ with tracer.start_as_current_span("create_environment"): - controller = EnvironmentController() return await controller.post_environment(environment) @router.get("/{environment_id}") -async def read_environment(environment_id: str) -> EnvironmentRetrieved: +async def read_environment( + environment_id: str, + controller: EnvironmentControllerDep, +) -> EnvironmentRetrieved: """ Reads an existing environment @@ -50,13 +53,14 @@ async def read_environment(environment_id: str) -> EnvironmentRetrieved: ``` environment_id: str ``` """ with tracer.start_as_current_span("read_environment"): - controller = EnvironmentController() return await controller.get_environment_by_id(environment_id) @router.put("/{environment_id}", status_code=204) async def update_environment( - environment_id: str, environment: EnvironmentModel + environment_id: str, + environment: EnvironmentModel, + controller: EnvironmentControllerDep, ) -> None: """ Updates an existing environment @@ -68,14 +72,16 @@ async def update_environment( ``` """ with tracer.start_as_current_span("update_environment"): - controller = EnvironmentController() return await controller.put_environment_by_id( environment_id, environment ) @router.delete("/{environment_id}", status_code=204) -async def delete_environment(environment_id: str) -> None: +async def delete_environment( + environment_id: str, + controller: EnvironmentControllerDep, +) -> None: """ Deletes an existing environment @@ -83,7 +89,6 @@ async def delete_environment(environment_id: str) -> None: ``` environment_id: str ``` """ with tracer.start_as_current_span("delete_environment"): - controller = EnvironmentController() return await controller.delete_environment_by_id(environment_id) @@ -98,7 +103,10 @@ async def delete_environment(environment_id: str) -> None: status_code=200, response_class=Response, ) -async def get_rocketpy_environment_binary(environment_id: str): +async def get_rocketpy_environment_binary( + environment_id: str, + controller: EnvironmentControllerDep, +): """ Loads rocketpy.environment as a dill binary. Currently only amd64 architecture is supported. @@ -110,7 +118,6 @@ async def get_rocketpy_environment_binary(environment_id: str): headers = { 'Content-Disposition': f'attachment; filename="rocketpy_environment_{environment_id}.dill"' } - controller = EnvironmentController() binary = await controller.get_rocketpy_environment_binary( environment_id ) @@ -125,6 +132,7 @@ async def get_rocketpy_environment_binary(environment_id: str): @router.get("/{environment_id}/simulate") async def get_environment_simulation( environment_id: str, + controller: EnvironmentControllerDep, ) -> EnvironmentSimulation: """ Simulates an environment @@ -133,5 +141,4 @@ async def get_environment_simulation( ``` environment_id: Environment ID``` """ with tracer.start_as_current_span("get_environment_simulation"): - controller = EnvironmentController() return await controller.get_environment_simulation(environment_id) diff --git a/src/routes/flight.py b/src/routes/flight.py index 6afd13a..03f8460 100644 --- a/src/routes/flight.py +++ b/src/routes/flight.py @@ -1,5 +1,5 @@ """ -Flight routes +Flight routes with dependency injection for improved performance. """ from fastapi import APIRouter, Response @@ -13,7 +13,7 @@ from src.models.environment import EnvironmentModel from src.models.flight import FlightModel, FlightWithReferencesRequest from src.models.rocket import RocketModel -from src.controllers.flight import FlightController +from src.dependencies import FlightControllerDep router = APIRouter( prefix="/flights", @@ -29,7 +29,10 @@ @router.post("/", status_code=201) -async def create_flight(flight: FlightModel) -> FlightCreated: +async def create_flight( + flight: FlightModel, + controller: FlightControllerDep, +) -> FlightCreated: """ Creates a new flight @@ -37,13 +40,13 @@ async def create_flight(flight: FlightModel) -> FlightCreated: ``` models.Flight JSON ``` """ with tracer.start_as_current_span("create_flight"): - controller = FlightController() return await controller.post_flight(flight) @router.post("/from-references", status_code=201) async def create_flight_from_references( payload: FlightWithReferencesRequest, + controller: FlightControllerDep, ) -> FlightCreated: """ Creates a flight using existing rocket and environment references. @@ -56,12 +59,14 @@ async def create_flight_from_references( ``` """ with tracer.start_as_current_span("create_flight_from_references"): - controller = FlightController() return await controller.create_flight_from_references(payload) @router.get("/{flight_id}") -async def read_flight(flight_id: str) -> FlightRetrieved: +async def read_flight( + flight_id: str, + controller: FlightControllerDep, +) -> FlightRetrieved: """ Reads an existing flight @@ -69,12 +74,14 @@ async def read_flight(flight_id: str) -> FlightRetrieved: ``` flight_id: str ``` """ with tracer.start_as_current_span("read_flight"): - controller = FlightController() return await controller.get_flight_by_id(flight_id) - @router.put("/{flight_id}", status_code=204) -async def update_flight(flight_id: str, flight: FlightModel) -> None: +async def update_flight( + flight_id: str, + flight: FlightModel, + controller: FlightControllerDep, +) -> None: """ Updates an existing flight @@ -85,7 +92,6 @@ async def update_flight(flight_id: str, flight: FlightModel) -> None: ``` """ with tracer.start_as_current_span("update_flight"): - controller = FlightController() return await controller.put_flight_by_id(flight_id, flight) @@ -93,6 +99,7 @@ async def update_flight(flight_id: str, flight: FlightModel) -> None: async def update_flight_from_references( flight_id: str, payload: FlightWithReferencesRequest, + controller: FlightControllerDep, ) -> None: """ Updates a flight using existing rocket and environment references. @@ -106,14 +113,15 @@ async def update_flight_from_references( ``` """ with tracer.start_as_current_span("update_flight_from_references"): - controller = FlightController() return await controller.update_flight_from_references( flight_id, payload ) - @router.delete("/{flight_id}", status_code=204) -async def delete_flight(flight_id: str) -> None: +async def delete_flight( + flight_id: str, + controller: FlightControllerDep, +) -> None: """ Deletes an existing flight @@ -121,7 +129,6 @@ async def delete_flight(flight_id: str) -> None: ``` flight_id: str ``` """ with tracer.start_as_current_span("delete_flight"): - controller = FlightController() return await controller.delete_flight_by_id(flight_id) @@ -136,7 +143,11 @@ async def delete_flight(flight_id: str) -> None: status_code=200, response_class=Response, ) -async def get_rocketpy_flight_binary(flight_id: str): + +async def get_rocketpy_flight_binary( + flight_id: str, + controller: FlightControllerDep, +): """ Loads rocketpy.flight as a dill binary. Currently only amd64 architecture is supported. @@ -145,7 +156,6 @@ async def get_rocketpy_flight_binary(flight_id: str): ``` flight_id: str ``` """ with tracer.start_as_current_span("get_rocketpy_flight_binary"): - controller = FlightController() headers = { 'Content-Disposition': f'attachment; filename="rocketpy_flight_{flight_id}.dill"' } @@ -160,7 +170,9 @@ async def get_rocketpy_flight_binary(flight_id: str): @router.put("/{flight_id}/environment", status_code=204) async def update_flight_environment( - flight_id: str, environment: EnvironmentModel + flight_id: str, + environment: EnvironmentModel, + controller: FlightControllerDep, ) -> None: """ Updates flight environment @@ -172,14 +184,17 @@ async def update_flight_environment( ``` """ with tracer.start_as_current_span("update_flight_environment"): - controller = FlightController() return await controller.update_environment_by_flight_id( flight_id, environment=environment ) @router.put("/{flight_id}/rocket", status_code=204) -async def update_flight_rocket(flight_id: str, rocket: RocketModel) -> None: +async def update_flight_rocket( + flight_id: str, + rocket: RocketModel, + controller: FlightControllerDep, +) -> None: """ Updates flight rocket. @@ -190,15 +205,16 @@ async def update_flight_rocket(flight_id: str, rocket: RocketModel) -> None: ``` """ with tracer.start_as_current_span("update_flight_rocket"): - controller = FlightController() return await controller.update_rocket_by_flight_id( flight_id, rocket=rocket, ) - @router.get("/{flight_id}/simulate") -async def get_flight_simulation(flight_id: str) -> FlightSimulation: +async def get_flight_simulation( + flight_id: str, + controller: FlightControllerDep, +) -> FlightSimulation: """ Simulates a flight @@ -206,5 +222,4 @@ async def get_flight_simulation(flight_id: str) -> FlightSimulation: ``` flight_id: Flight ID ``` """ with tracer.start_as_current_span("get_flight_simulation"): - controller = FlightController() - return await controller.get_flight_simulation(flight_id) + return await controller.get_flight_simulation(flight_id) \ No newline at end of file diff --git a/src/routes/motor.py b/src/routes/motor.py index 3143c26..0673708 100644 --- a/src/routes/motor.py +++ b/src/routes/motor.py @@ -11,7 +11,7 @@ MotorRetrieved, ) from src.models.motor import MotorModel -from src.controllers.motor import MotorController +from src.dependencies import MotorControllerDep router = APIRouter( prefix="/motors", @@ -27,7 +27,10 @@ @router.post("/", status_code=201) -async def create_motor(motor: MotorModel) -> MotorCreated: +async def create_motor( + motor: MotorModel, + controller: MotorControllerDep, +) -> MotorCreated: """ Creates a new motor @@ -35,12 +38,14 @@ async def create_motor(motor: MotorModel) -> MotorCreated: ``` models.Motor JSON ``` """ with tracer.start_as_current_span("create_motor"): - controller = MotorController() return await controller.post_motor(motor) @router.get("/{motor_id}") -async def read_motor(motor_id: str) -> MotorRetrieved: +async def read_motor( + motor_id: str, + controller: MotorControllerDep, +) -> MotorRetrieved: """ Reads an existing motor @@ -48,12 +53,15 @@ async def read_motor(motor_id: str) -> MotorRetrieved: ``` motor_id: str ``` """ with tracer.start_as_current_span("read_motor"): - controller = MotorController() return await controller.get_motor_by_id(motor_id) @router.put("/{motor_id}", status_code=204) -async def update_motor(motor_id: str, motor: MotorModel) -> None: +async def update_motor( + motor_id: str, + motor: MotorModel, + controller: MotorControllerDep, +) -> None: """ Updates an existing motor @@ -64,12 +72,14 @@ async def update_motor(motor_id: str, motor: MotorModel) -> None: ``` """ with tracer.start_as_current_span("update_motor"): - controller = MotorController() return await controller.put_motor_by_id(motor_id, motor) @router.delete("/{motor_id}", status_code=204) -async def delete_motor(motor_id: str) -> None: +async def delete_motor( + motor_id: str, + controller: MotorControllerDep, +) -> None: """ Deletes an existing motor @@ -77,7 +87,6 @@ async def delete_motor(motor_id: str) -> None: ``` motor_id: str ``` """ with tracer.start_as_current_span("delete_motor"): - controller = MotorController() return await controller.delete_motor_by_id(motor_id) @@ -92,7 +101,10 @@ async def delete_motor(motor_id: str) -> None: status_code=200, response_class=Response, ) -async def get_rocketpy_motor_binary(motor_id: str): +async def get_rocketpy_motor_binary( + motor_id: str, + controller: MotorControllerDep, +): """ Loads rocketpy.motor as a dill binary. Currently only amd64 architecture is supported. @@ -104,7 +116,6 @@ async def get_rocketpy_motor_binary(motor_id: str): headers = { 'Content-Disposition': f'attachment; filename="rocketpy_motor_{motor_id}.dill"' } - controller = MotorController() binary = await controller.get_rocketpy_motor_binary(motor_id) return Response( content=binary, @@ -115,7 +126,10 @@ async def get_rocketpy_motor_binary(motor_id: str): @router.get("/{motor_id}/simulate") -async def get_motor_simulation(motor_id: str) -> MotorSimulation: +async def get_motor_simulation( + motor_id: str, + controller: MotorControllerDep, +) -> MotorSimulation: """ Simulates a motor @@ -123,5 +137,4 @@ async def get_motor_simulation(motor_id: str) -> MotorSimulation: ``` motor_id: Motor ID ``` """ with tracer.start_as_current_span("get_motor_simulation"): - controller = MotorController() return await controller.get_motor_simulation(motor_id) diff --git a/src/routes/rocket.py b/src/routes/rocket.py index d5e4bdf..54059b9 100644 --- a/src/routes/rocket.py +++ b/src/routes/rocket.py @@ -14,7 +14,7 @@ RocketModel, RocketWithMotorReferenceRequest, ) -from src.controllers.rocket import RocketController +from src.dependencies import RocketControllerDep router = APIRouter( prefix="/rockets", @@ -30,7 +30,10 @@ @router.post("/", status_code=201) -async def create_rocket(rocket: RocketModel) -> RocketCreated: +async def create_rocket( + rocket: RocketModel, + controller: RocketControllerDep, +) -> RocketCreated: """ Creates a new rocket @@ -38,13 +41,11 @@ async def create_rocket(rocket: RocketModel) -> RocketCreated: ``` models.Rocket JSON ``` """ with tracer.start_as_current_span("create_rocket"): - controller = RocketController() return await controller.post_rocket(rocket) - - @router.post("/from-motor-reference", status_code=201) async def create_rocket_from_motor_reference( payload: RocketWithMotorReferenceRequest, + controller: RocketControllerDep, ) -> RocketCreated: """ Creates a rocket using an existing motor reference. @@ -56,12 +57,14 @@ async def create_rocket_from_motor_reference( ``` """ with tracer.start_as_current_span("create_rocket_from_motor_reference"): - controller = RocketController() return await controller.create_rocket_from_motor_reference(payload) @router.get("/{rocket_id}") -async def read_rocket(rocket_id: str) -> RocketRetrieved: +async def read_rocket( + rocket_id: str, + controller: RocketControllerDep, +) -> RocketRetrieved: """ Reads an existing rocket @@ -69,12 +72,15 @@ async def read_rocket(rocket_id: str) -> RocketRetrieved: ``` rocket_id: str ``` """ with tracer.start_as_current_span("read_rocket"): - controller = RocketController() return await controller.get_rocket_by_id(rocket_id) @router.put("/{rocket_id}", status_code=204) -async def update_rocket(rocket_id: str, rocket: RocketModel) -> None: +async def update_rocket( + rocket_id: str, + rocket: RocketModel, + controller: RocketControllerDep, +) -> None: """ Updates an existing rocket @@ -85,7 +91,6 @@ async def update_rocket(rocket_id: str, rocket: RocketModel) -> None: ``` """ with tracer.start_as_current_span("update_rocket"): - controller = RocketController() return await controller.put_rocket_by_id(rocket_id, rocket) @@ -93,6 +98,7 @@ async def update_rocket(rocket_id: str, rocket: RocketModel) -> None: async def update_rocket_from_motor_reference( rocket_id: str, payload: RocketWithMotorReferenceRequest, + controller: RocketControllerDep, ) -> None: """ Updates a rocket using an existing motor reference. @@ -105,14 +111,14 @@ async def update_rocket_from_motor_reference( ``` """ with tracer.start_as_current_span("update_rocket_from_motor_reference"): - controller = RocketController() return await controller.update_rocket_from_motor_reference( rocket_id, payload ) - - @router.delete("/{rocket_id}", status_code=204) -async def delete_rocket(rocket_id: str) -> None: +async def delete_rocket( + rocket_id: str, + controller: RocketControllerDep, +) -> None: """ Deletes an existing rocket @@ -120,7 +126,6 @@ async def delete_rocket(rocket_id: str) -> None: ``` rocket_id: str ``` """ with tracer.start_as_current_span("delete_rocket"): - controller = RocketController() return await controller.delete_rocket_by_id(rocket_id) @@ -135,7 +140,10 @@ async def delete_rocket(rocket_id: str) -> None: status_code=200, response_class=Response, ) -async def get_rocketpy_rocket_binary(rocket_id: str): +async def get_rocketpy_rocket_binary( + rocket_id: str, + controller: RocketControllerDep, +): """ Loads rocketpy.rocket as a dill binary. Currently only amd64 architecture is supported. @@ -147,7 +155,6 @@ async def get_rocketpy_rocket_binary(rocket_id: str): headers = { 'Content-Disposition': f'attachment; filename="rocketpy_rocket_{rocket_id}.dill"' } - controller = RocketController() binary = await controller.get_rocketpy_rocket_binary(rocket_id) return Response( content=binary, @@ -158,7 +165,10 @@ async def get_rocketpy_rocket_binary(rocket_id: str): @router.get("/{rocket_id}/simulate") -async def simulate_rocket(rocket_id: str) -> RocketSimulation: +async def simulate_rocket( + rocket_id: str, + controller: RocketControllerDep, +) -> RocketSimulation: """ Simulates a rocket @@ -166,5 +176,4 @@ async def simulate_rocket(rocket_id: str) -> RocketSimulation: ``` rocket_id: Rocket ID ``` """ with tracer.start_as_current_span("get_rocket_simulation"): - controller = RocketController() return await controller.get_rocket_simulation(rocket_id) diff --git a/tests/unit/test_routes/test_environments_route.py b/tests/unit/test_routes/test_environments_route.py index 93ff4c3..c5090d4 100644 --- a/tests/unit/test_routes/test_environments_route.py +++ b/tests/unit/test_routes/test_environments_route.py @@ -10,6 +10,9 @@ EnvironmentRetrieved, EnvironmentSimulation, ) + +from src.dependencies import get_environment_controller + from src import app client = TestClient(app) @@ -24,17 +27,25 @@ def stub_environment_simulation_dump(): @pytest.fixture(autouse=True) def mock_controller_instance(): - with patch( - "src.routes.environment.EnvironmentController", autospec=True - ) as mock_controller: - mock_controller_instance = mock_controller.return_value - mock_controller_instance.post_environment = Mock() - mock_controller_instance.get_environment_by_id = Mock() - mock_controller_instance.put_environment_by_id = Mock() - mock_controller_instance.delete_environment_by_id = Mock() - mock_controller_instance.get_environment_simulation = Mock() - mock_controller_instance.get_rocketpy_environment_binary = Mock() - yield mock_controller_instance + with patch("src.dependencies.EnvironmentController") as mock_class: + mock_controller = AsyncMock() + mock_controller.post_environment = AsyncMock() + mock_controller.get_environment_by_id = AsyncMock() + mock_controller.put_environment_by_id = AsyncMock() + mock_controller.delete_environment_by_id = AsyncMock() + mock_controller.get_environment_simulation = AsyncMock() + mock_controller.get_rocketpy_environment_binary = AsyncMock() + + mock_class.return_value = mock_controller + get_environment_controller.cache_clear() + + get_environment_controller.cache_clear() + + yield mock_controller + + get_environment_controller.cache_clear() + + get_environment_controller.cache_clear() def test_create_environment(stub_environment_dump, mock_controller_instance): diff --git a/tests/unit/test_routes/test_flights_route.py b/tests/unit/test_routes/test_flights_route.py index 5190c45..d53ca54 100644 --- a/tests/unit/test_routes/test_flights_route.py +++ b/tests/unit/test_routes/test_flights_route.py @@ -15,6 +15,9 @@ FlightSimulation, FlightView, ) + +from src.dependencies import get_flight_controller + from src import app client = TestClient(app) @@ -43,21 +46,26 @@ def stub_flight_simulate_dump(): @pytest.fixture(autouse=True) def mock_controller_instance(): - with patch( - "src.routes.flight.FlightController", autospec=True - ) as mock_controller: - mock_controller_instance = mock_controller.return_value - mock_controller_instance.post_flight = Mock() - mock_controller_instance.get_flight_by_id = Mock() - mock_controller_instance.put_flight_by_id = Mock() - mock_controller_instance.delete_flight_by_id = Mock() - mock_controller_instance.get_flight_simulation = Mock() - mock_controller_instance.get_rocketpy_flight_binary = Mock() - mock_controller_instance.update_environment_by_flight_id = Mock() - mock_controller_instance.update_rocket_by_flight_id = Mock() - mock_controller_instance.create_flight_from_references = Mock() - mock_controller_instance.update_flight_from_references = Mock() - yield mock_controller_instance + with patch("src.dependencies.FlightController") as mock_class: + mock_controller = AsyncMock() + mock_controller.post_flight = AsyncMock() + mock_controller.get_flight_by_id = AsyncMock() + mock_controller.put_flight_by_id = AsyncMock() + mock_controller.delete_flight_by_id = AsyncMock() + mock_controller.get_flight_simulation = AsyncMock() + mock_controller.get_rocketpy_flight_binary = AsyncMock() + mock_controller.update_environment_by_flight_id = AsyncMock() + mock_controller.update_rocket_by_flight_id = AsyncMock() + mock_controller.create_flight_from_references = AsyncMock() + mock_controller.update_flight_from_references = AsyncMock() + + mock_class.return_value = mock_controller + + get_flight_controller.cache_clear() + + yield mock_controller + + get_flight_controller.cache_clear() @pytest.fixture diff --git a/tests/unit/test_routes/test_motors_route.py b/tests/unit/test_routes/test_motors_route.py index e55c976..552b94b 100644 --- a/tests/unit/test_routes/test_motors_route.py +++ b/tests/unit/test_routes/test_motors_route.py @@ -10,6 +10,9 @@ MotorSimulation, MotorView, ) + +from src.dependencies import get_motor_controller + from src import app client = TestClient(app) @@ -24,17 +27,22 @@ def stub_motor_dump_simulation(): @pytest.fixture(autouse=True) def mock_controller_instance(): - with patch( - "src.routes.motor.MotorController", autospec=True - ) as mock_controller: - mock_controller_instance = mock_controller.return_value - mock_controller_instance.post_motor = Mock() - mock_controller_instance.get_motor_by_id = Mock() - mock_controller_instance.put_motor_by_id = Mock() - mock_controller_instance.delete_motor_by_id = Mock() - mock_controller_instance.get_motor_simulation = Mock() - mock_controller_instance.get_rocketpy_motor_binary = Mock() - yield mock_controller_instance + with patch("src.dependencies.MotorController") as mock_class: + mock_controller = AsyncMock() + mock_controller.post_motor = AsyncMock() + mock_controller.get_motor_by_id = AsyncMock() + mock_controller.put_motor_by_id = AsyncMock() + mock_controller.delete_motor_by_id = AsyncMock() + mock_controller.get_motor_simulation = AsyncMock() + mock_controller.get_rocketpy_motor_binary = AsyncMock() + + mock_class.return_value = mock_controller + + get_motor_controller.cache_clear() + + yield mock_controller + + get_motor_controller.cache_clear() def test_create_motor(stub_motor_dump, mock_controller_instance): diff --git a/tests/unit/test_routes/test_rockets_route.py b/tests/unit/test_routes/test_rockets_route.py index a91041d..6bf5e1d 100644 --- a/tests/unit/test_routes/test_rockets_route.py +++ b/tests/unit/test_routes/test_rockets_route.py @@ -19,6 +19,10 @@ RocketSimulation, RocketView, ) + + +from src.dependencies import get_rocket_controller + from src import app client = TestClient(app) @@ -72,19 +76,24 @@ def stub_parachute_dump(): @pytest.fixture(autouse=True) def mock_controller_instance(): - with patch( - "src.routes.rocket.RocketController", autospec=True - ) as mock_controller: - mock_controller_instance = mock_controller.return_value - mock_controller_instance.post_rocket = Mock() - mock_controller_instance.get_rocket_by_id = Mock() - mock_controller_instance.put_rocket_by_id = Mock() - mock_controller_instance.delete_rocket_by_id = Mock() - mock_controller_instance.get_rocket_simulation = Mock() - mock_controller_instance.get_rocketpy_rocket_binary = Mock() - mock_controller_instance.create_rocket_from_motor_reference = Mock() - mock_controller_instance.update_rocket_from_motor_reference = Mock() - yield mock_controller_instance + with patch("src.dependencies.RocketController") as mock_class: + mock_controller = AsyncMock() + mock_controller.post_rocket = AsyncMock() + mock_controller.get_rocket_by_id = AsyncMock() + mock_controller.put_rocket_by_id = AsyncMock() + mock_controller.delete_rocket_by_id = AsyncMock() + mock_controller.get_rocket_simulation = AsyncMock() + mock_controller.get_rocketpy_rocket_binary = AsyncMock() + mock_controller.create_rocket_from_motor_reference = AsyncMock() + mock_controller.update_rocket_from_motor_reference = AsyncMock() + + mock_class.return_value = mock_controller + + get_rocket_controller.cache_clear() + + yield mock_controller + + get_rocket_controller.cache_clear() @pytest.fixture