From b89551f3da196c057c8c59e74c945d885e58df04 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 11:32:56 -0300 Subject: [PATCH 01/11] implements motor route tests --- Makefile | 4 +- lib/controllers/motor.py | 26 +- lib/routes/motor.py | 6 +- lib/views/environment.py | 7 +- lib/views/flight.py | 7 +- lib/views/motor.py | 7 +- lib/views/rocket.py | 7 +- tests/test_routes/test_motor_route.py | 326 ++++++++++++++++++++++++++ 8 files changed, 357 insertions(+), 33 deletions(-) create mode 100644 tests/test_routes/test_motor_route.py diff --git a/Makefile b/Makefile index f8f94f6..fa15f27 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,8 @@ flake8: flake8 --ignore E501,E402,F401,W503,C0414 ./tests || true pylint: - pylint --extension-pkg-whitelist='pydantic' ./lib || true - pylint --extension-pkg-whitelist='pydantic' --disable=E0401,W0621 ./tests || true + pylint ./lib || true + pylint --disable=E0401,W0621 ./tests || true ruff: ruff check --fix ./lib || true diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index fc5a41a..4d58fb2 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -26,18 +26,6 @@ class MotorController: - Create a rocketpy.Motor object from a Motor model object. """ - def __init__(self, motor: Motor): - self.guard(motor) - self._motor = motor - - @property - def motor(self) -> Motor: - return self._motor - - @motor.setter - def motor(self, motor: Motor): - self._motor = motor - @staticmethod def guard(motor: Motor): if ( @@ -51,7 +39,10 @@ def guard(motor: Motor): # TODO: extend guard to check motor kinds and tank kinds specifics - async def create_motor(self) -> Union[MotorCreated, HTTPException]: + @classmethod + async def create_motor( + cls, motor: Motor + ) -> Union[MotorCreated, HTTPException]: """ Create a models.Motor in the database. @@ -59,7 +50,8 @@ async def create_motor(self) -> Union[MotorCreated, HTTPException]: views.MotorCreated """ try: - async with MotorRepository(self.motor) as motor_repo: + cls.guard(motor) + async with MotorRepository(motor) as motor_repo: await motor_repo.create_motor() except PyMongoError as e: logger.error(f"controllers.motor.create_motor: PyMongoError {e}") @@ -173,8 +165,9 @@ async def get_rocketpy_motor_binary( f"Call to controllers.motor.get_rocketpy_motor_binary completed for Motor {motor_id}" ) + @classmethod async def update_motor_by_id( - self, motor_id: str + cls, motor_id: str, motor: Motor ) -> Union[MotorUpdated, HTTPException]: """ Update a motor in the database. @@ -189,7 +182,8 @@ async def update_motor_by_id( HTTP 404 Not Found: If the motor is not found in the database. """ try: - async with MotorRepository(self.motor) as motor_repo: + cls.guard(motor) + async with MotorRepository(motor) as motor_repo: await motor_repo.update_motor_by_id(motor_id) except PyMongoError as e: logger.error(f"controllers.motor.update_motor: PyMongoError {e}") diff --git a/lib/routes/motor.py b/lib/routes/motor.py index d63f39e..5a89da7 100644 --- a/lib/routes/motor.py +++ b/lib/routes/motor.py @@ -38,7 +38,7 @@ async def create_motor(motor: Motor, motor_kind: MotorKinds) -> MotorCreated: """ with tracer.start_as_current_span("create_motor"): motor.set_motor_kind(motor_kind) - return await MotorController(motor).create_motor() + return await MotorController.create_motor(motor) @router.get("/{motor_id}") @@ -68,11 +68,11 @@ async def update_motor( """ with tracer.start_as_current_span("update_motor"): motor.set_motor_kind(motor_kind) - return await MotorController(motor).update_motor_by_id(motor_id) + return await MotorController.update_motor_by_id(motor_id, motor) @router.get( - "/rocketpy/{motor_id}", + "/{motor_id}/rocketpy", responses={ 203: { "description": "Binary file download", diff --git a/lib/views/environment.py b/lib/views/environment.py index a7478f5..d3eecee 100644 --- a/lib/views/environment.py +++ b/lib/views/environment.py @@ -1,6 +1,6 @@ from typing import Optional, Any from datetime import datetime, timedelta -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from lib.models.environment import AtmosphericModelTypes from lib.utils import to_python_primitive @@ -49,8 +49,9 @@ class EnvSummary(BaseModel): geodesic_to_utm: Optional[Any] = None utm_to_geodesic: Optional[Any] = None - class Config: - json_encoders = {Any: to_python_primitive} + model_config = ConfigDict( + json_encoders={Any: to_python_primitive} + ) class EnvCreated(BaseModel): diff --git a/lib/views/flight.py b/lib/views/flight.py index 00ad4f4..5bf5e4a 100644 --- a/lib/views/flight.py +++ b/lib/views/flight.py @@ -1,5 +1,5 @@ from typing import Optional, Any -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from lib.models.flight import Flight from lib.views.rocket import RocketView, RocketSummary from lib.views.environment import EnvSummary @@ -151,8 +151,9 @@ class FlightSummary(RocketSummary, EnvSummary): z_impact: Optional[Any] = None flight_phases: Optional[Any] = None - class Config: - json_encoders = {Any: to_python_primitive} + model_config = ConfigDict( + json_encoders={Any: to_python_primitive} + ) class FlightCreated(BaseModel): diff --git a/lib/views/motor.py b/lib/views/motor.py index 6424096..d14fc03 100644 --- a/lib/views/motor.py +++ b/lib/views/motor.py @@ -1,5 +1,5 @@ from typing import List, Any, Optional -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from lib.models.motor import Motor, MotorKinds, CoordinateSystemOrientation from lib.utils import to_python_primitive @@ -69,8 +69,9 @@ class MotorSummary(BaseModel): total_mass_flow_rate: Optional[Any] = None thrust: Optional[Any] = None - class Config: - json_encoders = {Any: to_python_primitive} + model_config = ConfigDict( + json_encoders={Any: to_python_primitive} + ) class MotorCreated(BaseModel): diff --git a/lib/views/rocket.py b/lib/views/rocket.py index 691ae97..3dabbf5 100644 --- a/lib/views/rocket.py +++ b/lib/views/rocket.py @@ -1,5 +1,5 @@ from typing import Any, Optional -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from lib.models.rocket import Rocket, CoordinateSystemOrientation from lib.views.motor import MotorView, MotorSummary from lib.utils import to_python_primitive @@ -38,8 +38,9 @@ class RocketSummary(MotorSummary): thrust_to_weight: Optional[Any] = None total_lift_coeff_der: Optional[Any] = None - class Config: - json_encoders = {Any: to_python_primitive} + model_config = ConfigDict( + json_encoders={Any: to_python_primitive} + ) class RocketCreated(BaseModel): diff --git a/tests/test_routes/test_motor_route.py b/tests/test_routes/test_motor_route.py new file mode 100644 index 0000000..3f8cc8f --- /dev/null +++ b/tests/test_routes/test_motor_route.py @@ -0,0 +1,326 @@ +from unittest.mock import patch +import json +import pytest +from fastapi.testclient import TestClient +from fastapi import HTTPException +from lib.models.motor import Motor +from lib.controllers.motor import MotorController +from lib.views.motor import ( + MotorKinds, + MotorCreated, + MotorUpdated, + MotorDeleted, + MotorSummary, + MotorView, +) +from lib import app + +client = TestClient(app) + + +@pytest.fixture +def stub_motor(): + motor = Motor( + thrust_source=[[0, 0]], + burn_time=0, + nozzle_radius=0, + dry_mass=0, + dry_inertia=[0, 0, 0], + center_of_dry_mass_position=0, + ) + motor_json = motor.model_dump_json() + return json.loads(motor_json) + + +@pytest.fixture +def stub_motor_summary(): + motor_summary = MotorSummary() + motor_summary_json = motor_summary.model_dump_json() + return json.loads(motor_summary_json) + + +def test_create_motor(stub_motor): + with patch.object( + MotorController, + "create_motor", + return_value=MotorCreated(motor_id="123"), + ) as mock_create_motor: + with patch.object( + Motor, "set_motor_kind", side_effect=None + ) as mock_set_motor_kind: + response = client.post( + "/motors/", json=stub_motor, params={"motor_kind": "HYBRID"} + ) + assert response.status_code == 200 + assert response.json() == { + "motor_id": "123", + "message": "Motor successfully created", + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.HYBRID) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + +def test_create_motor_optional_params(): + test_object = { + "thrust_source": [[0, 0]], + "burn_time": 1, + "nozzle_radius": 1, + "dry_mass": 1, + "center_of_dry_mass_position": 1, + "dry_inertia": [0, 0, 0], + "interpolation_method": "LINEAR", + "coordinate_system_orientation": "NOZZLE_TO_COMBUSTION_CHAMBER", + "reshape_thrust_curve": False, + } + with patch.object( + MotorController, + "create_motor", + return_value=MotorCreated(motor_id="123"), + ) as mock_create_motor: + with patch.object( + Motor, "set_motor_kind", side_effect=None + ) as mock_set_motor_kind: + response = client.post( + "/motors/", json=test_object, params={"motor_kind": "HYBRID"} + ) + assert response.status_code == 200 + assert response.json() == { + "motor_id": "123", + "message": "Motor successfully created", + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.HYBRID) + mock_create_motor.assert_called_once_with(Motor(**test_object)) + + +def test_create_motor_invalid_input(): + response = client.post( + "/motors/", json={"burn_time": "foo", "nozzle_radius": "bar"} + ) + assert response.status_code == 422 + + +def test_create_motor_server_error(stub_motor): + with patch.object( + MotorController, "create_motor", side_effect=Exception("error") + ): + with pytest.raises(Exception): + response = client.post("/motors/", json=stub_motor) + assert response.status_code == 500 + assert response.json() == { + "detail": "Failed to create motor: error" + } + + +def test_read_motor(stub_motor): + stub_motor.update({"selected_motor_kind": "HYBRID"}) + with patch.object( + MotorController, + "get_motor_by_id", + return_value=MotorView(**stub_motor), + ) as mock_read_motor: + response = client.get("/motors/123") + assert response.status_code == 200 + assert response.json() == stub_motor + mock_read_motor.assert_called_once_with("123") + + +def test_read_motor_not_found(): + with patch.object( + MotorController, + "get_motor_by_id", + side_effect=HTTPException(status_code=404), + ) as mock_read_motor: + response = client.get("/motors/123") + assert response.status_code == 404 + assert response.json() == {'detail': 'Not Found'} + mock_read_motor.assert_called_once_with("123") + + +def test_read_motor_server_error(): + with patch.object( + MotorController, "get_motor_by_id", side_effect=Exception("error") + ): + with pytest.raises(Exception): + response = client.get("/motors/123") + assert response.status_code == 500 + assert response.json() == {"detail": "Failed to read motor: error"} + + +def test_update_motor(stub_motor): + with patch.object( + MotorController, + "update_motor_by_id", + return_value=MotorUpdated(motor_id="123"), + ) as mock_update_motor: + with patch.object( + Motor, "set_motor_kind", side_effect=None + ) as mock_set_motor_kind: + response = client.put( + "/motors/123", json=stub_motor, params={"motor_kind": "HYBRID"} + ) + assert response.status_code == 200 + assert response.json() == { + "motor_id": "123", + "message": "Motor successfully updated", + } + mock_update_motor.assert_called_once_with( + "123", Motor(**stub_motor) + ) + mock_set_motor_kind.assert_called_once_with(MotorKinds.HYBRID) + + +def test_update_motor_invalid_input(): + response = client.put( + "/motors/123", + json={"burn_time": "foo", "nozzle_radius": "bar"}, + params={"motor_kind": "HYBRID"}, + ) + assert response.status_code == 422 + + +def test_update_motor_not_found(stub_motor): + with patch.object( + MotorController, + "update_motor_by_id", + side_effect=HTTPException(status_code=404), + ): + response = client.put( + "/motors/123", json=stub_motor, params={"motor_kind": "HYBRID"} + ) + assert response.status_code == 404 + assert response.json() == {'detail': 'Not Found'} + + +def test_update_motor_server_error(stub_motor): + with patch.object( + MotorController, + "update_motor_by_id", + side_effect=Exception("error"), + ): + with pytest.raises(Exception): + response = client.put( + "/motors/123", json=stub_motor, params={"motor_kind": "HYBRID"} + ) + assert response.status_code == 500 + assert response.json() == { + "detail": "Failed to update motor: error" + } + + +def test_delete_motor(): + with patch.object( + MotorController, + "delete_motor_by_id", + return_value=MotorDeleted(motor_id="123"), + ) as mock_delete_motor: + response = client.delete("/motors/123") + assert response.status_code == 200 + assert response.json() == { + "motor_id": "123", + "message": "Motor successfully deleted", + } + mock_delete_motor.assert_called_once_with("123") + + +def test_delete_motor_not_found(): + with patch.object( + MotorController, + "delete_motor_by_id", + return_value=MotorDeleted(motor_id="123"), + ) as mock_delete_motor: + response = client.delete("/motors/123") + assert response.status_code == 200 + assert response.json() == { + "motor_id": "123", + "message": "Motor successfully deleted", + } + mock_delete_motor.assert_called_once_with("123") + + +def test_delete_motor_server_error(): + with patch.object( + MotorController, + "delete_motor_by_id", + side_effect=Exception("error"), + ): + with pytest.raises(Exception): + response = client.delete("/motors/123") + assert response.status_code == 500 + assert response.json() == { + "detail": "Failed to delete motor: error" + } + + +def test_simulate_motor(stub_motor_summary): + with patch.object( + MotorController, + "simulate_motor", + return_value=MotorSummary(**stub_motor_summary), + ) as mock_simulate_motor: + response = client.get("/motors/123/summary") + assert response.status_code == 200 + assert response.json() == stub_motor_summary + mock_simulate_motor.assert_called_once_with("123") + + +def test_simulate_motor_not_found(): + with patch.object( + MotorController, + "simulate_motor", + side_effect=HTTPException(status_code=404), + ) as mock_simulate_motor: + response = client.get("/motors/123/summary") + assert response.status_code == 404 + assert response.json() == {'detail': 'Not Found'} + mock_simulate_motor.assert_called_once_with("123") + + +def test_simulate_motor_server_error(): + with patch.object( + MotorController, + "simulate_motor", + side_effect=Exception("error"), + ): + with pytest.raises(Exception): + response = client.get("/motors/123/summary") + assert response.status_code == 500 + assert response.json() == { + "detail": "Failed to simulate motor: error" + } + + +def test_read_rocketpy_motor(): + with patch.object( + MotorController, "get_rocketpy_motor_binary", return_value=b'rocketpy' + ) as mock_read_rocketpy_motor: + response = client.get("/motors/123/rocketpy") + assert response.status_code == 203 + assert response.content == b'rocketpy' + assert response.headers["content-type"] == "application/octet-stream" + mock_read_rocketpy_motor.assert_called_once_with("123") + + +def test_read_rocketpy_motor_not_found(): + with patch.object( + MotorController, + "get_rocketpy_motor_binary", + side_effect=HTTPException(status_code=404), + ) as mock_read_rocketpy_motor: + response = client.get("/motors/123/rocketpy") + assert response.status_code == 404 + assert response.json() == {'detail': 'Not Found'} + mock_read_rocketpy_motor.assert_called_once_with("123") + + +def test_read_rocketpy_motor_server_error(): + with patch.object( + MotorController, + "get_rocketpy_motor_binary", + side_effect=Exception("error"), + ): + with pytest.raises(Exception): + response = client.get("/motors/123/rocketpy") + assert response.status_code == 500 + assert response.json() == { + "detail": "Failed to read rocketpy motor: error" + } From 8e9e4b131fbec3feec70bd229f2dc8e675424fc8 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 11:42:17 -0300 Subject: [PATCH 02/11] renames tests --- .../{test_environment_route.py => test_environments_route.py} | 0 tests/test_routes/{test_motor_route.py => test_motors_route.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/test_routes/{test_environment_route.py => test_environments_route.py} (100%) rename tests/test_routes/{test_motor_route.py => test_motors_route.py} (100%) diff --git a/tests/test_routes/test_environment_route.py b/tests/test_routes/test_environments_route.py similarity index 100% rename from tests/test_routes/test_environment_route.py rename to tests/test_routes/test_environments_route.py diff --git a/tests/test_routes/test_motor_route.py b/tests/test_routes/test_motors_route.py similarity index 100% rename from tests/test_routes/test_motor_route.py rename to tests/test_routes/test_motors_route.py From 95bce8f8fda564669766c861b6abc0d3bc171af3 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 21:42:42 -0300 Subject: [PATCH 03/11] fixes exception handling tests --- lib/views/environment.py | 4 +- lib/views/flight.py | 4 +- lib/views/motor.py | 4 +- lib/views/rocket.py | 4 +- tests/test_routes/test_environments_route.py | 226 +++++----- tests/test_routes/test_motors_route.py | 418 +++++++++++++------ 6 files changed, 407 insertions(+), 253 deletions(-) diff --git a/lib/views/environment.py b/lib/views/environment.py index d3eecee..e3fcd31 100644 --- a/lib/views/environment.py +++ b/lib/views/environment.py @@ -49,9 +49,7 @@ class EnvSummary(BaseModel): geodesic_to_utm: Optional[Any] = None utm_to_geodesic: Optional[Any] = None - model_config = ConfigDict( - json_encoders={Any: to_python_primitive} - ) + model_config = ConfigDict(json_encoders={Any: to_python_primitive}) class EnvCreated(BaseModel): diff --git a/lib/views/flight.py b/lib/views/flight.py index 5bf5e4a..f06cae4 100644 --- a/lib/views/flight.py +++ b/lib/views/flight.py @@ -151,9 +151,7 @@ class FlightSummary(RocketSummary, EnvSummary): z_impact: Optional[Any] = None flight_phases: Optional[Any] = None - model_config = ConfigDict( - json_encoders={Any: to_python_primitive} - ) + model_config = ConfigDict(json_encoders={Any: to_python_primitive}) class FlightCreated(BaseModel): diff --git a/lib/views/motor.py b/lib/views/motor.py index d14fc03..858bc0e 100644 --- a/lib/views/motor.py +++ b/lib/views/motor.py @@ -69,9 +69,7 @@ class MotorSummary(BaseModel): total_mass_flow_rate: Optional[Any] = None thrust: Optional[Any] = None - model_config = ConfigDict( - json_encoders={Any: to_python_primitive} - ) + model_config = ConfigDict(json_encoders={Any: to_python_primitive}) class MotorCreated(BaseModel): diff --git a/lib/views/rocket.py b/lib/views/rocket.py index 3dabbf5..45664ad 100644 --- a/lib/views/rocket.py +++ b/lib/views/rocket.py @@ -38,9 +38,7 @@ class RocketSummary(MotorSummary): thrust_to_weight: Optional[Any] = None total_lift_coeff_der: Optional[Any] = None - model_config = ConfigDict( - json_encoders={Any: to_python_primitive} - ) + model_config = ConfigDict(json_encoders={Any: to_python_primitive}) class RocketCreated(BaseModel): diff --git a/tests/test_routes/test_environments_route.py b/tests/test_routes/test_environments_route.py index e0e3bb6..c8e651e 100644 --- a/tests/test_routes/test_environments_route.py +++ b/tests/test_routes/test_environments_route.py @@ -2,7 +2,7 @@ import json import pytest from fastapi.testclient import TestClient -from fastapi import HTTPException +from fastapi import HTTPException, status from lib.models.environment import Env from lib.controllers.environment import EnvController from lib.views.environment import ( @@ -32,111 +32,111 @@ def stub_env_summary(): def test_create_env(stub_env): with patch.object( - EnvController, "create_env", return_value=EnvCreated(env_id="123") + EnvController, 'create_env', return_value=EnvCreated(env_id='123') ) as mock_create_env: - response = client.post("/environments/", json=stub_env) + response = client.post('/environments/', json=stub_env) assert response.status_code == 200 assert response.json() == { - "env_id": "123", - "message": "Environment successfully created", + 'env_id': '123', + 'message': 'Environment successfully created', } mock_create_env.assert_called_once_with(Env(**stub_env)) def test_create_env_optional_params(): test_object = { - "latitude": 0, - "longitude": 0, - "elevation": 1, - "atmospheric_model_type": "STANDARD_ATMOSPHERE", - "atmospheric_model_file": None, - "date": "2021-01-01T00:00:00", + 'latitude': 0, + 'longitude': 0, + 'elevation': 1, + 'atmospheric_model_type': 'STANDARD_ATMOSPHERE', + 'atmospheric_model_file': None, + 'date': '2021-01-01T00:00:00', } with patch.object( - EnvController, "create_env", return_value=EnvCreated(env_id="123") + EnvController, 'create_env', return_value=EnvCreated(env_id='123') ) as mock_create_env: - response = client.post("/environments/", json=test_object) + response = client.post('/environments/', json=test_object) assert response.status_code == 200 assert response.json() == { - "env_id": "123", - "message": "Environment successfully created", + 'env_id': '123', + 'message': 'Environment successfully created', } mock_create_env.assert_called_once_with(Env(**test_object)) def test_create_env_invalid_input(): response = client.post( - "/environments/", json={"latitude": "foo", "longitude": "bar"} + '/environments/', json={'latitude': 'foo', 'longitude': 'bar'} ) assert response.status_code == 422 def test_create_env_server_error(stub_env): with patch.object( - EnvController, "create_env", side_effect=Exception("error") + EnvController, + 'create_env', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.post("/environments/", json=stub_env) - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to create environment: error" - } + response = client.post('/environments/', json=stub_env) + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_read_env(stub_env): with patch.object( - EnvController, "get_env_by_id", return_value=Env(**stub_env) + EnvController, 'get_env_by_id', return_value=Env(**stub_env) ) as mock_read_env: - response = client.get("/environments/123") + response = client.get('/environments/123') assert response.status_code == 200 assert response.json() == stub_env - mock_read_env.assert_called_once_with("123") + mock_read_env.assert_called_once_with('123') def test_read_env_not_found(): with patch.object( EnvController, - "get_env_by_id", - side_effect=HTTPException( - status_code=404, detail="Environment not found" - ), + 'get_env_by_id', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ) as mock_read_env: - response = client.get("/environments/123") + response = client.get('/environments/123') assert response.status_code == 404 - assert response.json() == {"detail": "Environment not found"} - mock_read_env.assert_called_once_with("123") + assert response.json() == {'detail': 'Not Found'} + mock_read_env.assert_called_once_with('123') def test_read_env_server_error(): with patch.object( - EnvController, "get_env_by_id", side_effect=Exception("error") + EnvController, + 'get_env_by_id', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.get("/environments/123") - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to read environment: error" - } + response = client.get('/environments/123') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_update_env(stub_env): with patch.object( EnvController, - "update_env_by_id", - return_value=EnvUpdated(env_id="123"), + 'update_env_by_id', + return_value=EnvUpdated(env_id='123'), ) as mock_update_env: - response = client.put("/environments/123", json=stub_env) + response = client.put('/environments/123', json=stub_env) assert response.status_code == 200 assert response.json() == { - "env_id": "123", - "message": "Environment successfully updated", + 'env_id': '123', + 'message': 'Environment successfully updated', } - mock_update_env.assert_called_once_with("123", Env(**stub_env)) + mock_update_env.assert_called_once_with('123', Env(**stub_env)) def test_update_env_invalid_input(): response = client.put( - "/environments/123", json={"latitude": "foo", "longitude": "bar"} + '/environments/123', json={'latitude': 'foo', 'longitude': 'bar'} ) assert response.status_code == 422 @@ -144,148 +144,138 @@ def test_update_env_invalid_input(): def test_update_env_not_found(stub_env): with patch.object( EnvController, - "update_env_by_id", - side_effect=HTTPException( - status_code=404, detail="Environment not found" - ), + 'update_env_by_id', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ): - response = client.put("/environments/123", json=stub_env) + response = client.put('/environments/123', json=stub_env) assert response.status_code == 404 - assert response.json() == {"detail": "Environment not found"} + assert response.json() == {'detail': 'Not Found'} def test_update_env_server_error(stub_env): with patch.object( EnvController, - "update_env_by_id", - side_effect=Exception("error"), + 'update_env_by_id', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.put("/environments/123", json=stub_env) - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to update environment: error" - } + response = client.put('/environments/123', json=stub_env) + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_delete_env(): with patch.object( EnvController, - "delete_env_by_id", - return_value=EnvDeleted(env_id="123"), + 'delete_env_by_id', + return_value=EnvDeleted(env_id='123'), ) as mock_delete_env: - response = client.delete("/environments/123") + response = client.delete('/environments/123') assert response.status_code == 200 assert response.json() == { - "env_id": "123", - "message": "Environment successfully deleted", + 'env_id': '123', + 'message': 'Environment successfully deleted', } - mock_delete_env.assert_called_once_with("123") + mock_delete_env.assert_called_once_with('123') def test_delete_env_not_found(): with patch.object( EnvController, - "delete_env_by_id", - return_value=EnvDeleted(env_id="123"), + 'delete_env_by_id', + return_value=EnvDeleted(env_id='123'), ) as mock_delete_env: - response = client.delete("/environments/123") + response = client.delete('/environments/123') assert response.status_code == 200 assert response.json() == { - "env_id": "123", - "message": "Environment successfully deleted", + 'env_id': '123', + 'message': 'Environment successfully deleted', } - mock_delete_env.assert_called_once_with("123") + mock_delete_env.assert_called_once_with('123') def test_delete_env_server_error(): with patch.object( EnvController, - "delete_env_by_id", - side_effect=Exception("error"), + 'delete_env_by_id', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.delete("/environments/123") - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to delete environment: error" - } + response = client.delete('/environments/123') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_simulate_env(stub_env_summary): with patch.object( EnvController, - "simulate_env", + 'simulate_env', return_value=EnvSummary(**stub_env_summary), ) as mock_simulate_env: - response = client.get("/environments/123/summary") + response = client.get('/environments/123/summary') assert response.status_code == 200 assert response.json() == stub_env_summary - mock_simulate_env.assert_called_once_with("123") + mock_simulate_env.assert_called_once_with('123') def test_simulate_env_not_found(): with patch.object( EnvController, - "simulate_env", - side_effect=HTTPException( - status_code=404, detail="Environment not found" - ), + 'simulate_env', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ) as mock_simulate_env: - response = client.get("/environments/123/summary") + response = client.get('/environments/123/summary') assert response.status_code == 404 - assert response.json() == {"detail": "Environment not found"} - mock_simulate_env.assert_called_once_with("123") + assert response.json() == {'detail': 'Not Found'} + mock_simulate_env.assert_called_once_with('123') def test_simulate_env_server_error(): with patch.object( EnvController, - "simulate_env", - side_effect=Exception("error"), + 'simulate_env', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.get("/environments/123/summary") - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to simulate environment: error" - } + response = client.get('/environments/123/summary') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_read_rocketpy_env(): with patch.object( - EnvController, "get_rocketpy_env_binary", return_value=b'rocketpy' + EnvController, 'get_rocketpy_env_binary', return_value=b'rocketpy' ) as mock_read_rocketpy_env: - response = client.get("/environments/123/rocketpy") + response = client.get('/environments/123/rocketpy') assert response.status_code == 203 assert response.content == b'rocketpy' - assert response.headers["content-type"] == "application/octet-stream" - mock_read_rocketpy_env.assert_called_once_with("123") + assert response.headers['content-type'] == 'application/octet-stream' + mock_read_rocketpy_env.assert_called_once_with('123') def test_read_rocketpy_env_not_found(): with patch.object( EnvController, - "get_rocketpy_env_binary", - side_effect=HTTPException( - status_code=404, detail="Environment not found" - ), + 'get_rocketpy_env_binary', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ) as mock_read_rocketpy_env: - response = client.get("/environments/123/rocketpy") + response = client.get('/environments/123/rocketpy') assert response.status_code == 404 - assert response.json() == {"detail": "Environment not found"} - mock_read_rocketpy_env.assert_called_once_with("123") + assert response.json() == {'detail': 'Not Found'} + mock_read_rocketpy_env.assert_called_once_with('123') def test_read_rocketpy_env_server_error(): with patch.object( EnvController, - "get_rocketpy_env_binary", - side_effect=Exception("error"), + 'get_rocketpy_env_binary', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.get("/environments/123/rocketpy") - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to read rocketpy environment: error" - } + response = client.get('/environments/123/rocketpy') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} diff --git a/tests/test_routes/test_motors_route.py b/tests/test_routes/test_motors_route.py index 3f8cc8f..7ad0728 100644 --- a/tests/test_routes/test_motors_route.py +++ b/tests/test_routes/test_motors_route.py @@ -2,11 +2,16 @@ import json import pytest from fastapi.testclient import TestClient -from fastapi import HTTPException -from lib.models.motor import Motor +from fastapi import HTTPException, status +from lib.models.motor import ( + Motor, + MotorKinds, + MotorTank, + TankFluids, + TankKinds, +) from lib.controllers.motor import MotorController from lib.views.motor import ( - MotorKinds, MotorCreated, MotorUpdated, MotorDeleted, @@ -32,6 +37,57 @@ def stub_motor(): return json.loads(motor_json) +@pytest.fixture +def stub_tank(): + tank = MotorTank( + geometry=[[(0, 0), 0]], + gas=TankFluids(name='gas', density=0), + liquid=TankFluids(name='liquid', density=0), + flux_time=(0, 0), + position=0, + discretize=0, + name='tank', + ) + tank_json = tank.model_dump_json() + return json.loads(tank_json) + + +@pytest.fixture +def stub_level_tank(stub_tank): + stub_tank.update({'tank_kind': TankKinds.LEVEL, 'liquid_height': 0}) + return stub_tank + + +@pytest.fixture +def stub_mass_flow_tank(stub_tank): + stub_tank.update( + { + 'tank_kind': TankKinds.MASS_FLOW, + 'gas_mass_flow_rate_in': 0, + 'gas_mass_flow_rate_out': 0, + 'liquid_mass_flow_rate_in': 0, + 'liquid_mass_flow_rate_out': 0, + 'initial_liquid_mass': 0, + 'initial_gas_mass': 0, + } + ) + return stub_tank + + +@pytest.fixture +def stub_ullage_tank(stub_tank): + stub_tank.update({'tank_kind': TankKinds.ULLAGE, 'ullage': 0}) + return stub_tank + + +@pytest.fixture +def stub_mass_tank(stub_tank): + stub_tank.update( + {'tank_kind': TankKinds.MASS, 'liquid_mass': 0, 'gas_mass': 0} + ) + return stub_tank + + @pytest.fixture def stub_motor_summary(): motor_summary = MotorSummary() @@ -42,138 +98,258 @@ def stub_motor_summary(): def test_create_motor(stub_motor): with patch.object( MotorController, - "create_motor", - return_value=MotorCreated(motor_id="123"), + 'create_motor', + return_value=MotorCreated(motor_id='123'), ) as mock_create_motor: with patch.object( - Motor, "set_motor_kind", side_effect=None + Motor, 'set_motor_kind', side_effect=None ) as mock_set_motor_kind: response = client.post( - "/motors/", json=stub_motor, params={"motor_kind": "HYBRID"} + '/motors/', json=stub_motor, params={'motor_kind': 'HYBRID'} ) assert response.status_code == 200 assert response.json() == { - "motor_id": "123", - "message": "Motor successfully created", + 'motor_id': '123', + 'message': 'Motor successfully created', } mock_set_motor_kind.assert_called_once_with(MotorKinds.HYBRID) mock_create_motor.assert_called_once_with(Motor(**stub_motor)) -def test_create_motor_optional_params(): - test_object = { - "thrust_source": [[0, 0]], - "burn_time": 1, - "nozzle_radius": 1, - "dry_mass": 1, - "center_of_dry_mass_position": 1, - "dry_inertia": [0, 0, 0], - "interpolation_method": "LINEAR", - "coordinate_system_orientation": "NOZZLE_TO_COMBUSTION_CHAMBER", - "reshape_thrust_curve": False, - } +def test_create_motor_optional_params(stub_motor): + stub_motor.update( + { + 'interpolation_method': 'LINEAR', + 'coordinate_system_orientation': 'NOZZLE_TO_COMBUSTION_CHAMBER', + 'reshape_thrust_curve': False, + } + ) with patch.object( MotorController, - "create_motor", - return_value=MotorCreated(motor_id="123"), + 'create_motor', + return_value=MotorCreated(motor_id='123'), ) as mock_create_motor: with patch.object( - Motor, "set_motor_kind", side_effect=None + Motor, 'set_motor_kind', side_effect=None ) as mock_set_motor_kind: response = client.post( - "/motors/", json=test_object, params={"motor_kind": "HYBRID"} + '/motors/', json=stub_motor, params={'motor_kind': 'HYBRID'} ) assert response.status_code == 200 assert response.json() == { - "motor_id": "123", - "message": "Motor successfully created", + 'motor_id': '123', + 'message': 'Motor successfully created', } mock_set_motor_kind.assert_called_once_with(MotorKinds.HYBRID) - mock_create_motor.assert_called_once_with(Motor(**test_object)) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + +def create_generic_motor(stub_motor): + stub_motor.update( + { + 'chamber_radius': 0, + 'chamber_height': 0, + 'chamber_position': 0, + 'propellant_initial_mass': 0, + 'nozzle_position': 0, + } + ) + with patch.object( + MotorController, + 'create_motor', + return_value=MotorCreated(motor_id='123'), + ) as mock_create_motor: + with patch.object( + Motor, 'set_motor_kind', side_effect=None + ) as mock_set_motor_kind: + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'GENERIC'} + ) + assert response.status_code == 200 + assert response.json() == { + 'motor_id': '123', + 'message': 'Motor successfully created', + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.GENERIC) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + +def create_liquid_motor_level_tank(stub_motor, stub_level_tank): + stub_motor.update({'tanks': [stub_level_tank]}) + with patch.object( + MotorController, + 'create_motor', + return_value=MotorCreated(motor_id='123'), + ) as mock_create_motor: + with patch.object( + Motor, 'set_motor_kind', side_effect=None + ) as mock_set_motor_kind: + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'LIQUID'} + ) + assert response.status_code == 200 + assert response.json() == { + 'motor_id': '123', + 'message': 'Motor successfully created', + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.LIQUID) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + +def test_create_hybrid_motor(stub_motor, stub_level_tank): + stub_motor.update( + { + 'grain_number': 0, + 'grain_density': 0, + 'grain_outer_radius': 0, + 'grain_initial_inner_radius': 0, + 'grain_initial_height': 0, + 'grains_center_of_mass_position': 0, + 'grain_separation': 0, + 'throat_radius': 0, + 'tanks': [stub_level_tank], + } + ) + with patch.object( + MotorController, + 'create_motor', + return_value=MotorCreated(motor_id='123'), + ) as mock_create_motor: + with patch.object( + Motor, 'set_motor_kind', side_effect=None + ) as mock_set_motor_kind: + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'HYBRID'} + ) + assert response.status_code == 200 + assert response.json() == { + 'motor_id': '123', + 'message': 'Motor successfully created', + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.HYBRID) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + +def test_create_solid_motor(stub_motor): + stub_motor.update( + { + 'grain_number': 0, + 'grain_density': 0, + 'grain_outer_radius': 0, + 'grain_initial_inner_radius': 0, + 'grain_initial_height': 0, + 'grains_center_of_mass_position': 0, + 'grain_separation': 0, + } + ) + with patch.object( + MotorController, + 'create_motor', + return_value=MotorCreated(motor_id='123'), + ) as mock_create_motor: + with patch.object( + Motor, 'set_motor_kind', side_effect=None + ) as mock_set_motor_kind: + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'SOLID'} + ) + assert response.status_code == 200 + assert response.json() == { + 'motor_id': '123', + 'message': 'Motor successfully created', + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.SOLID) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) def test_create_motor_invalid_input(): response = client.post( - "/motors/", json={"burn_time": "foo", "nozzle_radius": "bar"} + '/motors/', json={'burn_time': 'foo', 'nozzle_radius': 'bar'} ) assert response.status_code == 422 def test_create_motor_server_error(stub_motor): with patch.object( - MotorController, "create_motor", side_effect=Exception("error") + MotorController, + 'create_motor', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.post("/motors/", json=stub_motor) - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to create motor: error" - } + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'HYBRID'} + ) + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_read_motor(stub_motor): - stub_motor.update({"selected_motor_kind": "HYBRID"}) + stub_motor.update({'selected_motor_kind': 'HYBRID'}) with patch.object( MotorController, - "get_motor_by_id", + 'get_motor_by_id', return_value=MotorView(**stub_motor), ) as mock_read_motor: - response = client.get("/motors/123") + response = client.get('/motors/123') assert response.status_code == 200 assert response.json() == stub_motor - mock_read_motor.assert_called_once_with("123") + mock_read_motor.assert_called_once_with('123') def test_read_motor_not_found(): with patch.object( MotorController, - "get_motor_by_id", - side_effect=HTTPException(status_code=404), + 'get_motor_by_id', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ) as mock_read_motor: - response = client.get("/motors/123") + response = client.get('/motors/123') assert response.status_code == 404 assert response.json() == {'detail': 'Not Found'} - mock_read_motor.assert_called_once_with("123") + mock_read_motor.assert_called_once_with('123') def test_read_motor_server_error(): with patch.object( - MotorController, "get_motor_by_id", side_effect=Exception("error") + MotorController, + 'get_motor_by_id', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.get("/motors/123") - assert response.status_code == 500 - assert response.json() == {"detail": "Failed to read motor: error"} + response = client.get('/motors/123') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_update_motor(stub_motor): with patch.object( MotorController, - "update_motor_by_id", - return_value=MotorUpdated(motor_id="123"), + 'update_motor_by_id', + return_value=MotorUpdated(motor_id='123'), ) as mock_update_motor: with patch.object( - Motor, "set_motor_kind", side_effect=None + Motor, 'set_motor_kind', side_effect=None ) as mock_set_motor_kind: response = client.put( - "/motors/123", json=stub_motor, params={"motor_kind": "HYBRID"} + '/motors/123', json=stub_motor, params={'motor_kind': 'HYBRID'} ) assert response.status_code == 200 assert response.json() == { - "motor_id": "123", - "message": "Motor successfully updated", + 'motor_id': '123', + 'message': 'Motor successfully updated', } mock_update_motor.assert_called_once_with( - "123", Motor(**stub_motor) + '123', Motor(**stub_motor) ) mock_set_motor_kind.assert_called_once_with(MotorKinds.HYBRID) def test_update_motor_invalid_input(): response = client.put( - "/motors/123", - json={"burn_time": "foo", "nozzle_radius": "bar"}, - params={"motor_kind": "HYBRID"}, + '/motors/123', + json={'burn_time': 'foo', 'nozzle_radius': 'bar'}, + params={'motor_kind': 'HYBRID'}, ) assert response.status_code == 422 @@ -181,11 +357,11 @@ def test_update_motor_invalid_input(): def test_update_motor_not_found(stub_motor): with patch.object( MotorController, - "update_motor_by_id", - side_effect=HTTPException(status_code=404), + 'update_motor_by_id', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ): response = client.put( - "/motors/123", json=stub_motor, params={"motor_kind": "HYBRID"} + '/motors/123', json=stub_motor, params={'motor_kind': 'HYBRID'} ) assert response.status_code == 404 assert response.json() == {'detail': 'Not Found'} @@ -194,133 +370,129 @@ def test_update_motor_not_found(stub_motor): def test_update_motor_server_error(stub_motor): with patch.object( MotorController, - "update_motor_by_id", - side_effect=Exception("error"), + 'update_motor_by_id', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.put( - "/motors/123", json=stub_motor, params={"motor_kind": "HYBRID"} - ) - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to update motor: error" - } + response = client.put( + '/motors/123', json=stub_motor, params={'motor_kind': 'HYBRID'} + ) + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_delete_motor(): with patch.object( MotorController, - "delete_motor_by_id", - return_value=MotorDeleted(motor_id="123"), + 'delete_motor_by_id', + return_value=MotorDeleted(motor_id='123'), ) as mock_delete_motor: - response = client.delete("/motors/123") + response = client.delete('/motors/123') assert response.status_code == 200 assert response.json() == { - "motor_id": "123", - "message": "Motor successfully deleted", + 'motor_id': '123', + 'message': 'Motor successfully deleted', } - mock_delete_motor.assert_called_once_with("123") + mock_delete_motor.assert_called_once_with('123') def test_delete_motor_not_found(): with patch.object( MotorController, - "delete_motor_by_id", - return_value=MotorDeleted(motor_id="123"), + 'delete_motor_by_id', + return_value=MotorDeleted(motor_id='123'), ) as mock_delete_motor: - response = client.delete("/motors/123") + response = client.delete('/motors/123') assert response.status_code == 200 assert response.json() == { - "motor_id": "123", - "message": "Motor successfully deleted", + 'motor_id': '123', + 'message': 'Motor successfully deleted', } - mock_delete_motor.assert_called_once_with("123") + mock_delete_motor.assert_called_once_with('123') def test_delete_motor_server_error(): with patch.object( MotorController, - "delete_motor_by_id", - side_effect=Exception("error"), + 'delete_motor_by_id', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.delete("/motors/123") - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to delete motor: error" - } + response = client.delete('/motors/123') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_simulate_motor(stub_motor_summary): with patch.object( MotorController, - "simulate_motor", + 'simulate_motor', return_value=MotorSummary(**stub_motor_summary), ) as mock_simulate_motor: - response = client.get("/motors/123/summary") + response = client.get('/motors/123/summary') assert response.status_code == 200 assert response.json() == stub_motor_summary - mock_simulate_motor.assert_called_once_with("123") + mock_simulate_motor.assert_called_once_with('123') def test_simulate_motor_not_found(): with patch.object( MotorController, - "simulate_motor", - side_effect=HTTPException(status_code=404), + 'simulate_motor', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ) as mock_simulate_motor: - response = client.get("/motors/123/summary") + response = client.get('/motors/123/summary') assert response.status_code == 404 assert response.json() == {'detail': 'Not Found'} - mock_simulate_motor.assert_called_once_with("123") + mock_simulate_motor.assert_called_once_with('123') def test_simulate_motor_server_error(): with patch.object( MotorController, - "simulate_motor", - side_effect=Exception("error"), + 'simulate_motor', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.get("/motors/123/summary") - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to simulate motor: error" - } + response = client.get('/motors/123/summary') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} def test_read_rocketpy_motor(): with patch.object( - MotorController, "get_rocketpy_motor_binary", return_value=b'rocketpy' + MotorController, 'get_rocketpy_motor_binary', return_value=b'rocketpy' ) as mock_read_rocketpy_motor: - response = client.get("/motors/123/rocketpy") + response = client.get('/motors/123/rocketpy') assert response.status_code == 203 assert response.content == b'rocketpy' - assert response.headers["content-type"] == "application/octet-stream" - mock_read_rocketpy_motor.assert_called_once_with("123") + assert response.headers['content-type'] == 'application/octet-stream' + mock_read_rocketpy_motor.assert_called_once_with('123') def test_read_rocketpy_motor_not_found(): with patch.object( MotorController, - "get_rocketpy_motor_binary", - side_effect=HTTPException(status_code=404), + 'get_rocketpy_motor_binary', + side_effect=HTTPException(status_code=status.HTTP_404_NOT_FOUND), ) as mock_read_rocketpy_motor: - response = client.get("/motors/123/rocketpy") + response = client.get('/motors/123/rocketpy') assert response.status_code == 404 assert response.json() == {'detail': 'Not Found'} - mock_read_rocketpy_motor.assert_called_once_with("123") + mock_read_rocketpy_motor.assert_called_once_with('123') def test_read_rocketpy_motor_server_error(): with patch.object( MotorController, - "get_rocketpy_motor_binary", - side_effect=Exception("error"), + 'get_rocketpy_motor_binary', + side_effect=HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ), ): - with pytest.raises(Exception): - response = client.get("/motors/123/rocketpy") - assert response.status_code == 500 - assert response.json() == { - "detail": "Failed to read rocketpy motor: error" - } + response = client.get('/motors/123/rocketpy') + assert response.status_code == 500 + assert response.json() == {'detail': 'Internal Server Error'} From 921153e1b71a37edd9c3479db2ef5558eeab2d82 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 21:45:19 -0300 Subject: [PATCH 04/11] removes useless test case --- tests/test_routes/test_environments_route.py | 15 --------------- tests/test_routes/test_motors_route.py | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/tests/test_routes/test_environments_route.py b/tests/test_routes/test_environments_route.py index c8e651e..a32bf48 100644 --- a/tests/test_routes/test_environments_route.py +++ b/tests/test_routes/test_environments_route.py @@ -180,21 +180,6 @@ def test_delete_env(): mock_delete_env.assert_called_once_with('123') -def test_delete_env_not_found(): - with patch.object( - EnvController, - 'delete_env_by_id', - return_value=EnvDeleted(env_id='123'), - ) as mock_delete_env: - response = client.delete('/environments/123') - assert response.status_code == 200 - assert response.json() == { - 'env_id': '123', - 'message': 'Environment successfully deleted', - } - mock_delete_env.assert_called_once_with('123') - - def test_delete_env_server_error(): with patch.object( EnvController, diff --git a/tests/test_routes/test_motors_route.py b/tests/test_routes/test_motors_route.py index 7ad0728..168e69a 100644 --- a/tests/test_routes/test_motors_route.py +++ b/tests/test_routes/test_motors_route.py @@ -397,21 +397,6 @@ def test_delete_motor(): mock_delete_motor.assert_called_once_with('123') -def test_delete_motor_not_found(): - with patch.object( - MotorController, - 'delete_motor_by_id', - return_value=MotorDeleted(motor_id='123'), - ) as mock_delete_motor: - response = client.delete('/motors/123') - assert response.status_code == 200 - assert response.json() == { - 'motor_id': '123', - 'message': 'Motor successfully deleted', - } - mock_delete_motor.assert_called_once_with('123') - - def test_delete_motor_server_error(): with patch.object( MotorController, From 55a445a838fdc05390820924490561e6d402eb99 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 21:58:44 -0300 Subject: [PATCH 05/11] removes unnecessary class init state info from controllers --- lib/controllers/environment.py | 3 --- lib/controllers/flight.py | 32 ++++++++++---------------------- lib/controllers/motor.py | 3 --- lib/controllers/rocket.py | 32 ++++++++++---------------------- lib/routes/flight.py | 4 ++-- lib/routes/rocket.py | 4 ++-- 6 files changed, 24 insertions(+), 54 deletions(-) diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index 20f4874..b50354f 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -19,9 +19,6 @@ class EnvController: """ Controller for the Environment model. - Init Attributes: - env: models.Env - Enables: - Simulation of a RocketPy Environment from models.Env - CRUD operations over models.Env on the database diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index 34c672d..83f4ca3 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -25,9 +25,6 @@ class FlightController: """ Controller for the Flight model. - Init Attributes: - flight (models.Flight): Flight model object. - Enables: - Create a RocketPyFlight object from a Flight model object. - Generate trajectory simulation from a RocketPyFlight object. @@ -39,26 +36,14 @@ class FlightController: """ - def __init__( - self, - flight: Flight, - ): - self.guard(flight) - self._flight = flight - - @property - def flight(self) -> Flight: - return self._flight - - @flight.setter - def flight(self, flight: Flight): - self._flight = flight - @staticmethod def guard(flight: Flight): RocketController.guard(flight.rocket) - async def create_flight(self) -> Union[FlightCreated, HTTPException]: + @classmethod + async def create_flight( + cls, flight: Flight + ) -> Union[FlightCreated, HTTPException]: """ Create a flight in the database. @@ -66,7 +51,8 @@ async def create_flight(self) -> Union[FlightCreated, HTTPException]: views.FlightCreated """ try: - async with FlightRepository(self.flight) as flight_repo: + cls.guard(flight) + async with FlightRepository(flight) as flight_repo: await flight_repo.create_flight() except PyMongoError as e: logger.error(f"controllers.flight.create_flight: PyMongoError {e}") @@ -189,8 +175,9 @@ async def get_rocketpy_flight_binary( f"Call to controllers.flight.get_rocketpy_flight_binary completed for Flight {flight_id}" ) + @classmethod async def update_flight_by_id( - self, flight_id: str + cls, flight: Flight, flight_id: str ) -> Union[FlightUpdated, HTTPException]: """ Update a models.Flight in the database. @@ -205,7 +192,8 @@ async def update_flight_by_id( HTTP 404 Not Found: If the flight is not found in the database. """ try: - async with FlightRepository(self.flight) as flight_repo: + cls.guard(flight) + async with FlightRepository(flight) as flight_repo: await flight_repo.update_flight_by_id(flight_id) except PyMongoError as e: logger.error(f"controllers.flight.update_flight: PyMongoError {e}") diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 4d58fb2..552e871 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -19,9 +19,6 @@ class MotorController: """ Controller for the motor model. - Init Attributes: - motor (models.Motor): Motor model object. - Enables: - Create a rocketpy.Motor object from a Motor model object. """ diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index ef5524c..9bf0e2f 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -22,33 +22,18 @@ class RocketController: """ Controller for the Rocket model. - Init Attributes: - rocket: models.Rocket. - Enables: - CRUD operations over models.Rocket on the database. """ - def __init__( - self, - rocket: Rocket, - ): - self.guard(rocket) - self._rocket = rocket - - @property - def rocket(self) -> Rocket: - return self._rocket - - @rocket.setter - def rocket(self, rocket: Rocket): - self._rocket = rocket - @staticmethod def guard(rocket: Rocket): MotorController.guard(rocket.motor) - async def create_rocket(self) -> Union[RocketCreated, HTTPException]: + @classmethod + async def create_rocket( + cls, rocket: Rocket + ) -> Union[RocketCreated, HTTPException]: """ Create a models.Rocket in the database. @@ -56,7 +41,8 @@ async def create_rocket(self) -> Union[RocketCreated, HTTPException]: views.RocketCreated """ try: - async with RocketRepository(self.rocket) as rocket_repo: + cls.guard(rocket) + async with RocketRepository(rocket) as rocket_repo: await rocket_repo.create_rocket() except PyMongoError as e: logger.error(f"controllers.rocket.create_rocket: PyMongoError {e}") @@ -175,8 +161,9 @@ async def get_rocketpy_rocket_binary( f"Call to controllers.rocket.get_rocketpy_rocket_binary completed for Rocket {rocket_id}" ) + @classmethod async def update_rocket_by_id( - self, rocket_id: str + cls, rocket: Rocket, rocket_id: str ) -> Union[RocketUpdated, HTTPException]: """ Update a models.Rocket in the database. @@ -191,7 +178,8 @@ async def update_rocket_by_id( HTTP 404 Not Found: If the rocket is not found in the database. """ try: - async with RocketRepository(self.rocket) as rocket_repo: + cls.guard(rocket) + async with RocketRepository(rocket) as rocket_repo: await rocket_repo.update_rocket_by_id(rocket_id) except PyMongoError as e: logger.error(f"controllers.rocket.update_rocket: PyMongoError {e}") diff --git a/lib/routes/flight.py b/lib/routes/flight.py index 7045e2c..cb921db 100644 --- a/lib/routes/flight.py +++ b/lib/routes/flight.py @@ -43,7 +43,7 @@ async def create_flight( """ with tracer.start_as_current_span("create_flight"): flight.rocket.motor.set_motor_kind(motor_kind) - return await FlightController(flight).create_flight() + return await FlightController.create_flight(flight) @router.get("/{flight_id}") @@ -146,7 +146,7 @@ async def update_flight( """ with tracer.start_as_current_span("update_flight"): flight.rocket.motor.set_motor_kind(motor_kind) - return await FlightController(flight).update_flight_by_id(flight_id) + return await FlightController.update_flight_by_id(flight, flight_id) @router.get("/{flight_id}/summary") diff --git a/lib/routes/rocket.py b/lib/routes/rocket.py index e0b7a8a..cbbe93d 100644 --- a/lib/routes/rocket.py +++ b/lib/routes/rocket.py @@ -41,7 +41,7 @@ async def create_rocket( """ with tracer.start_as_current_span("create_rocket"): rocket.motor.set_motor_kind(motor_kind) - return await RocketController(rocket).create_rocket() + return await RocketController.create_rocket(rocket) @router.get("/{rocket_id}") @@ -73,7 +73,7 @@ async def update_rocket( """ with tracer.start_as_current_span("update_rocket"): rocket.motor.set_motor_kind(motor_kind) - return await RocketController(rocket).update_rocket_by_id(rocket_id) + return await RocketController.update_rocket_by_id(rocket, rocket_id) @router.get( From ada3010fd5024a0b4b946077408d9fc877731aa0 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 22:04:21 -0300 Subject: [PATCH 06/11] adds fault tolerancy for controllers creation logs --- lib/controllers/environment.py | 3 ++- lib/controllers/flight.py | 3 ++- lib/controllers/motor.py | 3 ++- lib/controllers/rocket.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index b50354f..fe965a5 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -32,6 +32,7 @@ async def create_env(env: Env) -> Union[EnvCreated, HTTPException]: Returns: views.EnvCreated """ + env_repo = None try: async with EnvRepository(env) as env_repo: await env_repo.create_env() @@ -56,7 +57,7 @@ async def create_env(env: Env) -> Union[EnvCreated, HTTPException]: return EnvCreated(env_id=env_repo.env_id) finally: logger.info( - f"Call to controllers.environment.create_env completed for Env {env_repo.env_id}" + f"Call to controllers.environment.create_env completed for Env {None or env_repo.env_id}" ) @staticmethod diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index 83f4ca3..eb07cff 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -50,6 +50,7 @@ async def create_flight( Returns: views.FlightCreated """ + flight_repo = None try: cls.guard(flight) async with FlightRepository(flight) as flight_repo: @@ -73,7 +74,7 @@ async def create_flight( return FlightCreated(flight_id=flight_repo.flight_id) finally: logger.info( - f"Call to controllers.flight.create_flight completed for Flight {flight_repo.flight_id}" + f"Call to controllers.flight.create_flight completed for Flight {None or flight_repo.flight_id}" ) @staticmethod diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 552e871..37298df 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -46,6 +46,7 @@ async def create_motor( Returns: views.MotorCreated """ + motor_repo = None try: cls.guard(motor) async with MotorRepository(motor) as motor_repo: @@ -69,7 +70,7 @@ async def create_motor( return MotorCreated(motor_id=motor_repo.motor_id) finally: logger.info( - f"Call to controllers.motor.create_motor completed for Motor {motor_repo.motor_id}" + f"Call to controllers.motor.create_motor completed for Motor {None or motor_repo.motor_id}" ) @staticmethod diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index 9bf0e2f..a59f551 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -40,6 +40,7 @@ async def create_rocket( Returns: views.RocketCreated """ + rocket_repo = None try: cls.guard(rocket) async with RocketRepository(rocket) as rocket_repo: @@ -63,7 +64,7 @@ async def create_rocket( return RocketCreated(rocket_id=rocket_repo.rocket_id) finally: logger.info( - f"Call to controllers.rocket.create_rocket completed for Rocket {rocket_repo.rocket_id}" + f"Call to controllers.rocket.create_rocket completed for Rocket {None or rocket_repo.rocket_id}" ) @staticmethod From d7578d9240dd7550adc1bca477aaaa959a5da147 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 22:06:58 -0300 Subject: [PATCH 07/11] fixes controllers create logs fault tolerancy --- lib/controllers/environment.py | 7 ++++--- lib/controllers/flight.py | 7 ++++--- lib/controllers/motor.py | 7 ++++--- lib/controllers/rocket.py | 7 ++++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index fe965a5..bb2b09b 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -56,9 +56,10 @@ async def create_env(env: Env) -> Union[EnvCreated, HTTPException]: else: return EnvCreated(env_id=env_repo.env_id) finally: - logger.info( - f"Call to controllers.environment.create_env completed for Env {None or env_repo.env_id}" - ) + if env_repo: + logger.info( + f"Call to controllers.environment.create_env completed for Env {env_repo.env_id}" + ) @staticmethod async def get_env_by_id(env_id: str) -> Union[Env, HTTPException]: diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index eb07cff..245bf21 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -73,9 +73,10 @@ async def create_flight( else: return FlightCreated(flight_id=flight_repo.flight_id) finally: - logger.info( - f"Call to controllers.flight.create_flight completed for Flight {None or flight_repo.flight_id}" - ) + if flight_repo: + logger.info( + f"Call to controllers.flight.create_flight completed for Flight {flight_repo.flight_id}" + ) @staticmethod async def get_flight_by_id( diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 37298df..10fad67 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -69,9 +69,10 @@ async def create_motor( else: return MotorCreated(motor_id=motor_repo.motor_id) finally: - logger.info( - f"Call to controllers.motor.create_motor completed for Motor {None or motor_repo.motor_id}" - ) + if motor_repo: + logger.info( + f"Call to controllers.motor.create_motor completed for Motor {motor_repo.motor_id}" + ) @staticmethod async def get_motor_by_id( diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index a59f551..4a7ad17 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -63,9 +63,10 @@ async def create_rocket( else: return RocketCreated(rocket_id=rocket_repo.rocket_id) finally: - logger.info( - f"Call to controllers.rocket.create_rocket completed for Rocket {None or rocket_repo.rocket_id}" - ) + if rocket_repo: + logger.info( + f"Call to controllers.rocket.create_rocket completed for Rocket {rocket_repo.rocket_id}" + ) @staticmethod async def get_rocket_by_id( From 670220dd55c602da3bbb4b2a7157b202449d9bb5 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 22:12:18 -0300 Subject: [PATCH 08/11] fixes motor tank model --- lib/models/motor.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/models/motor.py b/lib/models/motor.py index 302dfc9..c8121bf 100644 --- a/lib/models/motor.py +++ b/lib/models/motor.py @@ -46,25 +46,25 @@ class MotorTank(BaseModel): discretize: int # Level based tank parameters - liquid_height: Optional[float] + liquid_height: Optional[float] = None # Mass based tank parameters - liquid_mass: Optional[float] - gas_mass: Optional[float] + liquid_mass: Optional[float] = None + gas_mass: Optional[float] = None # Mass flow based tank parameters - gas_mass_flow_rate_in: Optional[float] - gas_mass_flow_rate_out: Optional[float] - liquid_mass_flow_rate_in: Optional[float] - liquid_mass_flow_rate_out: Optional[float] - initial_liquid_mass: Optional[float] - initial_gas_mass: Optional[float] + gas_mass_flow_rate_in: Optional[float] = None + gas_mass_flow_rate_out: Optional[float] = None + liquid_mass_flow_rate_in: Optional[float] = None + liquid_mass_flow_rate_out: Optional[float] = None + initial_liquid_mass: Optional[float] = None + initial_gas_mass: Optional[float] = None # Ullage based tank parameters - ullage: Optional[float] + ullage: Optional[float] = None # Optional parameters - name: Optional[str] + name: Optional[str] = None # Computed parameters tank_kind: TankKinds = TankKinds.MASS_FLOW From 208ab3763fae631f8fc81cb719f671b2f10304c9 Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 22:16:20 -0300 Subject: [PATCH 09/11] adds tank tests --- tests/test_routes/test_motors_route.py | 70 +++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/tests/test_routes/test_motors_route.py b/tests/test_routes/test_motors_route.py index 168e69a..b7cab89 100644 --- a/tests/test_routes/test_motors_route.py +++ b/tests/test_routes/test_motors_route.py @@ -144,7 +144,7 @@ def test_create_motor_optional_params(stub_motor): mock_create_motor.assert_called_once_with(Motor(**stub_motor)) -def create_generic_motor(stub_motor): +def test_create_generic_motor(stub_motor): stub_motor.update( { 'chamber_radius': 0, @@ -174,7 +174,7 @@ def create_generic_motor(stub_motor): mock_create_motor.assert_called_once_with(Motor(**stub_motor)) -def create_liquid_motor_level_tank(stub_motor, stub_level_tank): +def test_create_liquid_motor_level_tank(stub_motor, stub_level_tank): stub_motor.update({'tanks': [stub_level_tank]}) with patch.object( MotorController, @@ -196,6 +196,72 @@ def create_liquid_motor_level_tank(stub_motor, stub_level_tank): mock_create_motor.assert_called_once_with(Motor(**stub_motor)) +def test_create_liquid_motor_mass_flow_tank(stub_motor, stub_mass_flow_tank): + stub_motor.update({'tanks': [stub_mass_flow_tank]}) + with patch.object( + MotorController, + 'create_motor', + return_value=MotorCreated(motor_id='123'), + ) as mock_create_motor: + with patch.object( + Motor, 'set_motor_kind', side_effect=None + ) as mock_set_motor_kind: + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'LIQUID'} + ) + assert response.status_code == 200 + assert response.json() == { + 'motor_id': '123', + 'message': 'Motor successfully created', + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.LIQUID) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + +def test_create_liquid_motor_ullage_tank(stub_motor, stub_ullage_tank): + stub_motor.update({'tanks': [stub_ullage_tank]}) + with patch.object( + MotorController, + 'create_motor', + return_value=MotorCreated(motor_id='123'), + ) as mock_create_motor: + with patch.object( + Motor, 'set_motor_kind', side_effect=None + ) as mock_set_motor_kind: + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'LIQUID'} + ) + assert response.status_code == 200 + assert response.json() == { + 'motor_id': '123', + 'message': 'Motor successfully created', + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.LIQUID) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + +def test_create_liquid_motor_mass_tank(stub_motor, stub_mass_tank): + stub_motor.update({'tanks': [stub_mass_tank]}) + with patch.object( + MotorController, + 'create_motor', + return_value=MotorCreated(motor_id='123'), + ) as mock_create_motor: + with patch.object( + Motor, 'set_motor_kind', side_effect=None + ) as mock_set_motor_kind: + response = client.post( + '/motors/', json=stub_motor, params={'motor_kind': 'LIQUID'} + ) + assert response.status_code == 200 + assert response.json() == { + 'motor_id': '123', + 'message': 'Motor successfully created', + } + mock_set_motor_kind.assert_called_once_with(MotorKinds.LIQUID) + mock_create_motor.assert_called_once_with(Motor(**stub_motor)) + + def test_create_hybrid_motor(stub_motor, stub_level_tank): stub_motor.update( { From f844a44cccd31262766ef890688f7ca67ac1c57f Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 22:47:12 -0300 Subject: [PATCH 10/11] Update lib/controllers/flight.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- lib/controllers/flight.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index 245bf21..7aa7ad0 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -73,10 +73,10 @@ async def create_flight( else: return FlightCreated(flight_id=flight_repo.flight_id) finally: - if flight_repo: - logger.info( - f"Call to controllers.flight.create_flight completed for Flight {flight_repo.flight_id}" - ) + flight_id = getattr(flight_repo, 'flight_id', 'unknown') if flight_repo else 'unknown' + logger.info( + f"Call to controllers.flight.create_flight completed for Flight {flight_id}" + ) @staticmethod async def get_flight_by_id( From 7058abd6e46ffc9e5cac99639addff018353061b Mon Sep 17 00:00:00 2001 From: Gabriel Barberini Date: Fri, 15 Nov 2024 22:52:25 -0300 Subject: [PATCH 11/11] applies format and improve fault tolerancy for creation logs on controllers --- lib/controllers/environment.py | 7 ++++++- lib/controllers/flight.py | 6 +++++- lib/controllers/motor.py | 7 ++++++- lib/controllers/rocket.py | 7 ++++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/controllers/environment.py b/lib/controllers/environment.py index bb2b09b..40bd7bd 100644 --- a/lib/controllers/environment.py +++ b/lib/controllers/environment.py @@ -56,9 +56,14 @@ async def create_env(env: Env) -> Union[EnvCreated, HTTPException]: else: return EnvCreated(env_id=env_repo.env_id) finally: + env_id = ( + getattr(env_repo, 'env_id', 'unknown') + if env_repo + else 'unknown' + ) if env_repo: logger.info( - f"Call to controllers.environment.create_env completed for Env {env_repo.env_id}" + f"Call to controllers.environment.create_env completed for Env {env_id}" ) @staticmethod diff --git a/lib/controllers/flight.py b/lib/controllers/flight.py index 7aa7ad0..1d2aa8d 100644 --- a/lib/controllers/flight.py +++ b/lib/controllers/flight.py @@ -73,7 +73,11 @@ async def create_flight( else: return FlightCreated(flight_id=flight_repo.flight_id) finally: - flight_id = getattr(flight_repo, 'flight_id', 'unknown') if flight_repo else 'unknown' + flight_id = ( + getattr(flight_repo, 'flight_id', 'unknown') + if flight_repo + else 'unknown' + ) logger.info( f"Call to controllers.flight.create_flight completed for Flight {flight_id}" ) diff --git a/lib/controllers/motor.py b/lib/controllers/motor.py index 10fad67..824dfd1 100644 --- a/lib/controllers/motor.py +++ b/lib/controllers/motor.py @@ -69,9 +69,14 @@ async def create_motor( else: return MotorCreated(motor_id=motor_repo.motor_id) finally: + motor_id = ( + getattr(motor_repo, 'motor_id', 'unknown') + if motor_repo + else 'unknown' + ) if motor_repo: logger.info( - f"Call to controllers.motor.create_motor completed for Motor {motor_repo.motor_id}" + f"Call to controllers.motor.create_motor completed for Motor {motor_id}" ) @staticmethod diff --git a/lib/controllers/rocket.py b/lib/controllers/rocket.py index 4a7ad17..5b42b60 100644 --- a/lib/controllers/rocket.py +++ b/lib/controllers/rocket.py @@ -63,9 +63,14 @@ async def create_rocket( else: return RocketCreated(rocket_id=rocket_repo.rocket_id) finally: + rocket_id = ( + getattr(rocket_repo, 'rocket_id', 'unknown') + if rocket_repo + else 'unknown' + ) if rocket_repo: logger.info( - f"Call to controllers.rocket.create_rocket completed for Rocket {rocket_repo.rocket_id}" + f"Call to controllers.rocket.create_rocket completed for Rocket {rocket_id}" ) @staticmethod