|
| 1 | +import base64 |
| 2 | + |
1 | 3 | import numpy as np |
2 | 4 | import pytest |
| 5 | +import requests |
3 | 6 | import scipy.integrate |
4 | 7 |
|
5 | 8 | from rocketpy import Function, Motor |
@@ -211,3 +214,122 @@ def test_load_from_rse_file(generic_motor): |
211 | 214 | assert thrust_curve[0][1] == 0.0 # First thrust point |
212 | 215 | assert thrust_curve[-1][0] == 2.2 # Last point of time |
213 | 216 | assert thrust_curve[-1][1] == 0.0 # Last thrust point |
| 217 | + |
| 218 | + |
| 219 | +class MockResponse: |
| 220 | + """Mocked response for requests.""" |
| 221 | + |
| 222 | + def __init__(self, json_data): |
| 223 | + self._json_data = json_data |
| 224 | + |
| 225 | + def json(self): |
| 226 | + return self._json_data |
| 227 | + |
| 228 | + def raise_for_status(self): |
| 229 | + return None |
| 230 | + |
| 231 | + |
| 232 | +def _mock_get(search_results=None, download_results=None): |
| 233 | + """Return a mock_get function with predefined search/download results.""" |
| 234 | + |
| 235 | + def _get(url, **_kwargs): |
| 236 | + if "search.json" in url: |
| 237 | + return MockResponse(search_results or {"results": []}) |
| 238 | + if "download.json" in url: |
| 239 | + return MockResponse(download_results or {"results": []}) |
| 240 | + raise RuntimeError(f"Unexpected URL: {url}") |
| 241 | + |
| 242 | + return _get |
| 243 | + |
| 244 | + |
| 245 | +# Module-level constant for expected motor specs |
| 246 | +EXPECTED_MOTOR_SPECS = { |
| 247 | + "burn_time": (0, 3.9), |
| 248 | + "dry_mass": 2.130, |
| 249 | + "propellant_initial_mass": 3.101, |
| 250 | + "chamber_radius": 75 / 1000, |
| 251 | + "chamber_height": 757 / 1000, |
| 252 | + "nozzle_radius": (75 / 1000) * 0.85, |
| 253 | + "average_thrust": 1545.218, |
| 254 | + "total_impulse": 6026.350, |
| 255 | + "max_thrust": 2200.0, |
| 256 | + "exhaust_velocity": 1943.357, |
| 257 | + "chamber_position": 0, |
| 258 | +} |
| 259 | + |
| 260 | + |
| 261 | +def assert_motor_specs(motor): |
| 262 | + specs = EXPECTED_MOTOR_SPECS |
| 263 | + assert motor.burn_time == specs["burn_time"] |
| 264 | + assert motor.dry_mass == specs["dry_mass"] |
| 265 | + assert motor.propellant_initial_mass == specs["propellant_initial_mass"] |
| 266 | + assert motor.chamber_radius == specs["chamber_radius"] |
| 267 | + assert motor.chamber_height == specs["chamber_height"] |
| 268 | + assert motor.chamber_position == specs["chamber_position"] |
| 269 | + assert motor.average_thrust == pytest.approx(specs["average_thrust"]) |
| 270 | + assert motor.total_impulse == pytest.approx(specs["total_impulse"]) |
| 271 | + assert motor.exhaust_velocity.average(*specs["burn_time"]) == pytest.approx( |
| 272 | + specs["exhaust_velocity"] |
| 273 | + ) |
| 274 | + assert motor.max_thrust == pytest.approx(specs["max_thrust"]) |
| 275 | + assert motor.nozzle_radius == pytest.approx(specs["nozzle_radius"]) |
| 276 | + |
| 277 | + |
| 278 | +def test_load_from_thrustcurve_api(monkeypatch, generic_motor): |
| 279 | + """Tests GenericMotor.load_from_thrustcurve_api with mocked API.""" |
| 280 | + |
| 281 | + eng_path = "data/motors/cesaroni/Cesaroni_M1670.eng" |
| 282 | + with open(eng_path, "rb") as f: |
| 283 | + encoded = base64.b64encode(f.read()).decode("utf-8") |
| 284 | + |
| 285 | + search_json = { |
| 286 | + "results": [ |
| 287 | + { |
| 288 | + "motorId": "12345", |
| 289 | + "designation": "Cesaroni_M1670", |
| 290 | + "manufacturer": "Cesaroni", |
| 291 | + } |
| 292 | + ] |
| 293 | + } |
| 294 | + download_json = {"results": [{"data": encoded}]} |
| 295 | + monkeypatch.setattr(requests, "get", _mock_get(search_json, download_json)) |
| 296 | + monkeypatch.setattr(requests.Session, "get", _mock_get(search_json, download_json)) |
| 297 | + |
| 298 | + motor = type(generic_motor).load_from_thrustcurve_api("M1670") |
| 299 | + |
| 300 | + assert_motor_specs(motor) |
| 301 | + |
| 302 | + _, _, points = Motor.import_eng(eng_path) |
| 303 | + assert motor.thrust.y_array == pytest.approx( |
| 304 | + Function(points, "Time (s)", "Thrust (N)", "linear", "zero").y_array |
| 305 | + ) |
| 306 | + |
| 307 | + error_cases = [ |
| 308 | + ("No motor found", {"results": []}, None), |
| 309 | + ( |
| 310 | + "No .eng file found", |
| 311 | + { |
| 312 | + "results": [ |
| 313 | + {"motorId": "123", "designation": "Fake", "manufacturer": "Test"} |
| 314 | + ] |
| 315 | + }, |
| 316 | + {"results": []}, |
| 317 | + ), |
| 318 | + ( |
| 319 | + "Downloaded .eng data", |
| 320 | + { |
| 321 | + "results": [ |
| 322 | + {"motorId": "123", "designation": "Fake", "manufacturer": "Test"} |
| 323 | + ] |
| 324 | + }, |
| 325 | + {"results": [{"data": ""}]}, |
| 326 | + ), |
| 327 | + ] |
| 328 | + |
| 329 | + for msg, search_res, download_res in error_cases: |
| 330 | + monkeypatch.setattr(requests, "get", _mock_get(search_res, download_res)) |
| 331 | + monkeypatch.setattr( |
| 332 | + requests.Session, "get", _mock_get(search_res, download_res) |
| 333 | + ) |
| 334 | + with pytest.raises(ValueError, match=msg): |
| 335 | + type(generic_motor).load_from_thrustcurve_api("FakeMotor") |
0 commit comments