Skip to content

Commit 4f4cb40

Browse files
implements environment route tests
1 parent 33e7993 commit 4f4cb40

File tree

8 files changed

+158
-38
lines changed

8 files changed

+158
-38
lines changed

Makefile

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
black:
2-
black ./lib
1+
format: flake8 pylint ruff black
32

4-
lint: flake8 pylint
3+
black:
4+
black ./lib && black ./tests
55

66
flake8:
7-
flake8 --ignore E501,E402,F401,W503 ./lib
7+
flake8 --ignore E501,E402,F401,W503,C0414 ./lib && flake8 --ignore E501,E402,F401,W503,C0414 ./tests
88

99
pylint:
10-
pylint --extension-pkg-whitelist='pydantic' ./lib/*
10+
pylint --extension-pkg-whitelist='pydantic' ./lib && pylint --extension-pkg-whitelist='pydantic' ./tests
11+
12+
ruff:
13+
ruff check --fix
14+
15+
test:
16+
python3 -m pytest .
1117

1218
dev:
1319
python3 -m uvicorn lib:app --reload --port 3000
@@ -19,3 +25,5 @@ clean:
1925

2026
build:
2127
docker build -t infinity-api . --no-cache
28+
29+
@PHONY: black lint flake8 pylint test dev clean build ruff

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
- Install dependencies `python3 -m pip install -r requirements.txt`
1111

1212
## Development
13-
- black ./lib
14-
- pylint --extension-pkg-whitelist='pydantic' ./lib/*
15-
- flake8 --ignore E501,E402,F401,W503 ./lib
13+
- make format
14+
- make test
15+
- make clean
16+
- make build
1617

1718
## Starting the server
1819
- Setup MONGODB_CONNECTION_STRING:

lib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ def parse_error(error):
2525
return f"{exc_type}: {exc_obj}"
2626

2727

28-
from lib.api import app # pylint: disable=wrong-import-position,cyclic-import
28+
from lib.api import app as app # pylint: disable=wrong-import-position,cyclic-import

lib/controllers/environment.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,16 @@ class EnvController:
2727
- CRUD operations over models.Env on the database
2828
"""
2929

30-
def __init__(self, env: Env):
31-
self._env = env
32-
33-
@property
34-
def env(self) -> Env:
35-
return self._env
36-
37-
@env.setter
38-
def env(self, env: Env):
39-
self._env = env
40-
41-
async def create_env(self) -> Union[EnvCreated, HTTPException]:
30+
@staticmethod
31+
async def create_env(env: Env) -> Union[EnvCreated, HTTPException]:
4232
"""
4333
Create a env in the database.
4434
4535
Returns:
4636
views.EnvCreated
4737
"""
4838
try:
49-
async with EnvRepository(self.env) as env_repo:
39+
async with EnvRepository(env) as env_repo:
5040
await env_repo.create_env()
5141
except PyMongoError as e:
5242
logger.error(
@@ -157,8 +147,9 @@ async def get_rocketpy_env_binary(
157147
f"Call to controllers.environment.get_rocketpy_env_binary completed for Env {env_id}"
158148
)
159149

150+
@staticmethod
160151
async def update_env_by_id(
161-
self, env_id: str
152+
env_id: str, env: Env
162153
) -> Union[EnvUpdated, HTTPException]:
163154
"""
164155
Update a models.Env in the database.
@@ -173,7 +164,7 @@ async def update_env_by_id(
173164
HTTP 404 Not Found: If the env is not found in the database.
174165
"""
175166
try:
176-
async with EnvRepository(self.env) as env_repo:
167+
async with EnvRepository(env) as env_repo:
177168
await env_repo.update_env_by_id(env_id)
178169
except PyMongoError as e:
179170
logger.error(

lib/routes/environment.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async def create_env(env: Env) -> EnvCreated:
3636
``` models.Env JSON ```
3737
"""
3838
with tracer.start_as_current_span("create_env"):
39-
return await EnvController(env).create_env()
39+
return await EnvController.create_env(env)
4040

4141

4242
@router.get("/{env_id}")
@@ -63,11 +63,11 @@ async def update_env(env_id: str, env: Env) -> EnvUpdated:
6363
```
6464
"""
6565
with tracer.start_as_current_span("update_env"):
66-
return await EnvController(env).update_env_by_id(env_id)
66+
return await EnvController.update_env_by_id(env_id, env)
6767

6868

6969
@router.get(
70-
"/rocketpy/{env_id}",
70+
"/{env_id}/rocketpy",
7171
responses={
7272
203: {
7373
"description": "Binary file download",
@@ -118,4 +118,4 @@ async def delete_env(env_id: str) -> EnvDeleted:
118118
``` env_id: str ```
119119
"""
120120
with tracer.start_as_current_span("delete_env"):
121-
return await EnvController(env_id).delete_env_by_id(env_id)
121+
return await EnvController.delete_env_by_id(env_id)

pyproject.toml

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
[build-system]
2-
requires = ["setuptools", "setuptools_scm"]
3-
build-backend = "setuptools.build_meta"
4-
5-
[tool.setuptools.dynamic]
6-
dependencies = {file = ["requirements.txt"]}
7-
81
[project]
92
name = "Infinity-API"
10-
version = "2.2.0"
3+
version = "2.3.0"
114
description = "RESTFULL open API for rocketpy"
12-
dynamic = ["dependencies"]
135
requires-python = ">=3.12"
146
authors = [
157
{name = "Gabriel Barberini", email = "[email protected]"}
@@ -21,7 +13,7 @@ maintainers = [
2113
readme = "README.md"
2214
keywords = ["rocketpy", "API", "simulation", "rocket", "flight"]
2315
classifiers = [
24-
"Development Status :: Alpha",
16+
"Development Status :: Production",
2517
"Programming Language :: Python"
2618
]
2719

@@ -52,3 +44,14 @@ disable = """
5244
raise-missing-from,
5345
too-many-instance-attributes,
5446
"""
47+
48+
[tool.ruff]
49+
line-length = 79
50+
target-version = "py313"
51+
52+
[tool.ruff.lint]
53+
select = ["E", "F", "N", "Q"]
54+
ignore = ["N815", "E501", "Q000", "E402"]
55+
fixable = [
56+
"F401",
57+
]

requirements-dev.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
flake8
2+
pylint
3+
ruff
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from unittest.mock import patch
4+
from lib.models.environment import Env
5+
from lib.controllers.environment import EnvController
6+
from lib.views.environment import (
7+
EnvCreated,
8+
EnvUpdated,
9+
EnvDeleted,
10+
EnvSummary,
11+
)
12+
from lib import app
13+
14+
client = TestClient(app)
15+
16+
17+
@pytest.fixture
18+
def mock_env():
19+
return Env(latitude=0, longitude=0)
20+
21+
22+
@pytest.fixture
23+
def mock_env_summary():
24+
return EnvSummary()
25+
26+
27+
def test_create_env(mock_env):
28+
with patch.object(
29+
EnvController, "create_env", return_value=EnvCreated(env_id="123")
30+
) as mock_create_env:
31+
response = client.post(
32+
"/environments/", json={"latitude": 0, "longitude": 0}
33+
)
34+
assert response.status_code == 200
35+
assert response.json() == {
36+
"env_id": "123",
37+
"message": "Environment successfully created",
38+
}
39+
mock_create_env.assert_called_once_with(Env(latitude=0, longitude=0))
40+
41+
42+
def test_read_env(mock_env):
43+
with patch.object(
44+
EnvController, "get_env_by_id", return_value=mock_env
45+
) as mock_read_env:
46+
response = client.get("/environments/123")
47+
assert response.status_code == 200
48+
expected_content = mock_env.model_dump()
49+
expected_content["date"] = expected_content["date"].isoformat()
50+
assert response.json() == expected_content
51+
mock_read_env.assert_called_once_with("123")
52+
53+
54+
def test_update_env():
55+
with patch.object(
56+
EnvController,
57+
"update_env_by_id",
58+
return_value=EnvUpdated(env_id="123"),
59+
) as mock_update_env:
60+
response = client.put(
61+
"/environments/123", json={"longitude": 1, "latitude": 1}
62+
)
63+
assert response.status_code == 200
64+
assert response.json() == {
65+
"env_id": "123",
66+
"message": "Environment successfully updated",
67+
}
68+
mock_update_env.assert_called_once_with(
69+
"123", Env(latitude=1, longitude=1)
70+
)
71+
72+
73+
def test_delete_env():
74+
with patch.object(
75+
EnvController,
76+
"delete_env_by_id",
77+
return_value=EnvDeleted(env_id="123"),
78+
) as mock_delete_env:
79+
response = client.delete("/environments/123")
80+
assert response.status_code == 200
81+
assert response.json() == {
82+
"env_id": "123",
83+
"message": "Environment successfully deleted",
84+
}
85+
mock_delete_env.assert_called_once_with("123")
86+
87+
88+
def test_simulate_env(mock_env_summary):
89+
with patch.object(
90+
EnvController, "simulate_env", return_value=mock_env_summary
91+
) as mock_simulate_env:
92+
response = client.get("/environments/123/summary")
93+
assert response.status_code == 200
94+
expected_content = mock_env_summary.model_dump()
95+
expected_content["date"] = expected_content["date"].isoformat()
96+
expected_content["local_date"] = expected_content[
97+
"local_date"
98+
].isoformat()
99+
expected_content["datetime_date"] = expected_content[
100+
"datetime_date"
101+
].isoformat()
102+
assert response.json() == expected_content
103+
mock_simulate_env.assert_called_once_with("123")
104+
105+
106+
def test_read_rocketpy_env(mock_env):
107+
with patch.object(
108+
EnvController, "get_rocketpy_env_binary", return_value=b'rocketpy'
109+
) as mock_read_rocketpy_env:
110+
response = client.get("/environments/123/rocketpy")
111+
assert response.status_code == 203
112+
assert response.content == b'rocketpy'
113+
assert response.headers["content-type"] == "application/octet-stream"
114+
mock_read_rocketpy_env.assert_called_once_with("123")

0 commit comments

Comments
 (0)