Skip to content

Commit c17f5d1

Browse files
authored
Merge pull request #55 from openagri-eu/main
Satellite Images, EU footer, New Reporting endpoints and TestBase Implementation
2 parents 14c2c25 + 4d58b53 commit c17f5d1

23 files changed

+1252
-920
lines changed

README.md

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ docker compose up
6060

6161
The application will be served on http://127.0.0.1:8009 (I.E. typing localhost/docs in your browser will load the swagger documentation)
6262

63-
Full list of APIs available you can check [here](https://editor-next.swagger.io/?url=https://gist.githubusercontent.com/JoleVLF/c29adf44808a683149426912383c75eb/raw/7b57a845ab37954424c0ef2108962fd248c6966f/api_v4.json)
63+
Full list of APIs available you can check [here](https://editor-next.swagger.io/?url=https://gist.githubusercontent.com/JoleVLF/7f5771b23a44e508b82e47d5fafd9f9c/raw/0ab48e33303efb114d7467a3ec1d8833fd62ff22/v5.json)
6464
# Documentation
6565
<h3>GET</h3>
6666

@@ -211,14 +211,79 @@ If a valid JSON file is uploaded, the API processes the data directly to generat
211211

212212
Response is uuid of generated PDF file.
213213

214+
<h3>POST</h3>
215+
216+
```
217+
/api/v1/openagri-report/pesticides-report/
218+
```
219+
220+
## Request Params
221+
222+
### pesticide_id
223+
- **Type**: `uudi str`
224+
- **Description**: ID of Pesticide operation for which PDF is generated (optional)
225+
226+
### data
227+
- **Type**: `UploadFile`
228+
- **Description**: API processes the data directly to generate the report if data passed. This parameter is not required and when it is, must be provided as an `UploadFile`.
229+
230+
- ### from_date
231+
- **Type**: `date`
232+
- **Description**: Optional date filter (from which data is filtered)
233+
234+
- ### to_date
235+
- **Type**: `date`
236+
- **Description**: AOptional date filter (until which data is filtered)
237+
238+
- ### parcel_id
239+
- **Type**: `str`
240+
- **Description**: Optional parcel filter.
241+
-
242+
## Response
243+
244+
Response is uuid of generated PDF file.
245+
246+
<h3>POST</h3>
247+
248+
249+
```
250+
/api/v1/openagri-report/fertilization-report/
251+
```
252+
253+
## Request Params
254+
255+
### fertilization_id
256+
- **Type**: `uudi str`
257+
- **Description**: ID of fert operation for which PDF is generated (optional)
258+
259+
### data
260+
- **Type**: `UploadFile`
261+
- **Description**: API processes the data directly to generate the report if data passed. This parameter is not required and when it is, must be provided as an `UploadFile`.
262+
263+
- ### from_date
264+
- **Type**: `date`
265+
- **Description**: Optional date filter (from which data is filtered)
266+
267+
- ### to_date
268+
- **Type**: `date`
269+
- **Description**: AOptional date filter (until which data is filtered)
270+
271+
- ### parcel_id
272+
- **Type**: `str`
273+
- **Description**: Optional parcel filter.
274+
-
275+
## Response
276+
277+
Response is uuid of generated PDF file.
278+
214279
<h2>Pytest</h2>
215-
Pytest can be run on the same machine the service has been deployed to by moving into the tests dir and running:
280+
Pytest can be run on the same machine the service has been deployed to by moving into the app dir and running:
216281

217282
```
218-
pytest tests_.py
283+
pytest
219284
```
220285

221-
This will run the tests and return success values for each api tested in the terminal.
286+
This will run all tests and return success values for each api tested in the terminal.
222287

223288
<h3>These tests will NOT result in generated .pdf files.</h3>
224289

app/api/api_v1/endpoints/report.py

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from utils import decode_jwt_token
1919
from utils.animals_report import process_animal_data
2020
from utils.farm_calendar_report import process_farm_calendar_data
21-
from utils.irrigation_report import process_irrigation_data
21+
from utils.irrig_fert_pest_report import process_irrigation_fertilization_data
2222
from fastapi.responses import FileResponse
2323

2424
router = APIRouter()
@@ -83,13 +83,13 @@ async def generate_irrigation_report(
8383
if data:
8484
data = data.file.read()
8585
background_tasks.add_task(
86-
process_irrigation_data,
86+
process_irrigation_fertilization_data,
8787
data=data,
8888
token=token,
8989
pdf_file_name=uuid_of_pdf,
9090
from_date=from_date,
9191
to_date=to_date,
92-
irrigation_id=irrigation_id,
92+
operation_id=irrigation_id,
9393
parcel_id=parcel_id
9494
)
9595

@@ -194,3 +194,96 @@ async def generate_animal_report(
194194
)
195195

196196
return PDF(uuid=uuid_v4)
197+
198+
199+
200+
@router.post("/fertilization-report/", response_model=PDF)
201+
async def generate_fertilization_report(
202+
background_tasks: BackgroundTasks,
203+
token=Depends(deps.get_current_user),
204+
fertilization_id: str = None,
205+
data: UploadFile = None,
206+
from_date: datetime.date = None,
207+
to_date: datetime.date = None,
208+
parcel_id: str = None
209+
):
210+
"""
211+
Generates Fertilization Report PDF file
212+
213+
"""
214+
uuid_v4 = str(uuid.uuid4())
215+
user_id = (
216+
decode_jwt_token(token)["user_id"]
217+
if settings.REPORTING_USING_GATEKEEPER
218+
else token.id
219+
)
220+
uuid_of_pdf = f"{user_id}/{uuid_v4}"
221+
222+
if not data and not settings.REPORTING_USING_GATEKEEPER:
223+
raise HTTPException(
224+
status_code=400,
225+
detail=f"Data file must be provided if gatekeeper is not used.",
226+
)
227+
if data:
228+
data = data.file.read()
229+
background_tasks.add_task(
230+
process_irrigation_fertilization_data,
231+
data=data,
232+
token=token,
233+
pdf_file_name=uuid_of_pdf,
234+
from_date=from_date,
235+
to_date=to_date,
236+
operation_id=fertilization_id,
237+
parcel_id=parcel_id,
238+
irrigation_flag=False,
239+
fertilization_flag=True
240+
)
241+
242+
return PDF(uuid=uuid_v4)
243+
244+
245+
@router.post("/pesticides-report/", response_model=PDF)
246+
async def generate_pesticides_report(
247+
background_tasks: BackgroundTasks,
248+
token=Depends(deps.get_current_user),
249+
pesticide_id: str = None,
250+
data: UploadFile = None,
251+
from_date: datetime.date = None,
252+
to_date: datetime.date = None,
253+
parcel_id: str = None
254+
):
255+
"""
256+
Generates Pesticides Report PDF file
257+
258+
"""
259+
uuid_v4 = str(uuid.uuid4())
260+
user_id = (
261+
decode_jwt_token(token)["user_id"]
262+
if settings.REPORTING_USING_GATEKEEPER
263+
else token.id
264+
)
265+
uuid_of_pdf = f"{user_id}/{uuid_v4}"
266+
267+
if not data and not settings.REPORTING_USING_GATEKEEPER:
268+
raise HTTPException(
269+
status_code=400,
270+
detail=f"Data file must be provided if gatekeeper is not used.",
271+
)
272+
273+
if data:
274+
data = data.file.read()
275+
276+
background_tasks.add_task(
277+
process_irrigation_fertilization_data,
278+
data=data,
279+
token=token,
280+
pdf_file_name=uuid_of_pdf,
281+
from_date=from_date,
282+
to_date=to_date,
283+
operation_id=pesticide_id,
284+
parcel_id=parcel_id,
285+
irrigation_flag=False,
286+
pesticides_flag= True
287+
)
288+
289+
return PDF(uuid=uuid_v4)

app/assets/eu.png

97.8 KB
Loading

app/core/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class Settings(BaseSettings):
2525
REPORTING_FARMCALENDAR_BASE_URL: str = "api/proxy/farmcalendar/api/v1"
2626
REPORTING_FARMCALENDAR_URLS: dict = {
2727
"irrigations": "/IrrigationOperations/",
28+
"fertilization": "/FertilizationOperations/",
29+
"pesticides": "/CropProtectionOperations/",
30+
"pest": "/Pesticides/",
2831
"activity_types": "/FarmCalendarActivityTypes/",
2932
"observations": "/Observations/",
3033
"operations": "/CompostOperations/",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[pytest]
22
env_files =
33
testing.env
4+
pythonpath = .
5+

app/schemas/irrigation.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ class QuantityValue(BaseModel):
1010
unit: str
1111
numericValue: float
1212

13-
14-
class IrrigationOperation(BaseModel):
15-
"""Model for irrigation operations"""
16-
13+
class GenericModel(BaseModel):
1714
type: str = Field(alias="@type")
1815
id: str = Field(alias="@id")
1916
activityType: Optional[dict] = None
@@ -24,5 +21,16 @@ class IrrigationOperation(BaseModel):
2421
responsibleAgent: Optional[str] = None
2522
usesAgriculturalMachinery: List[dict] = []
2623
hasAppliedAmount: QuantityValue
24+
25+
class IrrigationOperation(GenericModel):
26+
"""Model for irrigation operations"""
2727
usesIrrigationSystem: Optional[Union[str, dict]] = None
2828
operatedOn: Optional[dict] = None
29+
30+
class FertilizationOperation(IrrigationOperation):
31+
hasApplicationMethod: Optional[str] = None
32+
usesFertilizer: Optional[dict] = None
33+
34+
class CropProtectionOperation(GenericModel):
35+
operatedOn: Optional[dict] = None
36+
usesPesticide: Optional[dict] = None
File renamed without changes.

app/tests/unit/__init__.py

Whitespace-only changes.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from unittest import TestCase
2+
from unittest.mock import patch, MagicMock
3+
4+
from fastapi import HTTPException
5+
import os
6+
7+
REQUIRED_ENV_VARS = {
8+
"REPORTING_GATEKEEPER_USERNAME": "mock_user",
9+
"REPORTING_GATEKEEPER_PASSWORD": "mock_pass",
10+
"REPORTING_BACKEND_CORS_ORIGINS": '["*"]',
11+
"REPORTING_POSTGRES_USER": "mock_pg_user",
12+
"REPORTING_POSTGRES_PASSWORD": "mock_pg_pass",
13+
"REPORTING_POSTGRES_DB": "mock_db",
14+
"REPORTING_POSTGRES_HOST": "mock_host",
15+
"REPORTING_POSTGRES_PORT": "5432",
16+
"REPORTING_SERVICE_NAME": "mock_service",
17+
"REPORTING_SERVICE_PORT": "8000",
18+
"REPORTING_GATEKEEPER_BASE_URL": "http://mock.gatekeeper",
19+
"JWT_ACCESS_TOKEN_EXPIRATION_TIME": "3600",
20+
"JWT_SIGNING_KEY": "mock_jwt_secret",
21+
"PDF_DIRECTORY": "/",
22+
"REPORTING_USING_GATEKEEPER": "True"
23+
}
24+
25+
# TestReportAPI Class that will be used for unit test of report endpoints
26+
class TestReportAPI(TestCase):
27+
28+
CORRECT_TOKEN = 'eyJhbGciOiJIUzI1NiJ9.eyJJc3N1ZXIiOiJJc3N1ZXIifQ.HLkw6rgYSwcv0sE69OKiNQFvHoo-6VqlxC5nKuMmftg'
29+
WRONG_TOKEN = "ayJhbGciOiJIUzI1NiJ9.eyJJc3N1ZXIiOiJJc3N1ZXIifQ.HLkw6rgYSwcv0sE69OKiNQFvHoo-6VqlxC5nKuMmftg"
30+
BASE_URL = "/api/v1/openagri-report"
31+
32+
@staticmethod
33+
def user_login(token):
34+
if token == TestReportAPI.CORRECT_TOKEN:
35+
return ""
36+
else:
37+
raise HTTPException(status_code=401, detail='Not Auth!')
38+
39+
40+
def patch(self, obj, attr, value = None):
41+
if value is None:
42+
value = MagicMock()
43+
patcher = patch.object(obj, attr, value)
44+
self.addCleanup(patcher.stop)
45+
return patcher.start()
46+
47+
48+
def setUp(self):
49+
for k, v in REQUIRED_ENV_VARS.items():
50+
os.environ[k] = v
51+
52+
super().setUpClass()
53+
from main import app
54+
from api.api_v1.endpoints import report
55+
from fastapi.testclient import TestClient
56+
from api.deps import get_current_user
57+
58+
app.dependency_overrides[get_current_user] = TestReportAPI.user_login
59+
self.patch(
60+
report,
61+
"decode_jwt_token",
62+
MagicMock(return_value={"user_id": "123"})
63+
)
64+
self.client=TestClient(app)
65+
66+
67+
def test_get_report_endpoint_not_auth(self):
68+
response = self.client.get(f"{TestReportAPI.BASE_URL}/123/", headers={"X-Token": "OK"},
69+
params={"token": TestReportAPI.WRONG_TOKEN})
70+
assert response.status_code == 401
71+
72+
def test_get_report_endpoint_auth_in_progress(self):
73+
response = self.client.get(f"{TestReportAPI.BASE_URL}/123/", headers={"X-Token": "OK"},
74+
params={"token": TestReportAPI.CORRECT_TOKEN})
75+
assert response.status_code == 202
76+
77+
def test_get_report_endpoint_success(self):
78+
from api.api_v1.endpoints import report
79+
from fastapi import Response
80+
self.patch(
81+
report,
82+
"FileResponse",
83+
MagicMock(return_value=Response(content=b"PDF", media_type="application/pdf"))
84+
)
85+
86+
self.patch(
87+
report.os.path,
88+
"exists",
89+
MagicMock(return_value=True)
90+
)
91+
response = self.client.get(f"{TestReportAPI.BASE_URL}/123/", headers={"X-Token": "OK"},
92+
params={"token": TestReportAPI.CORRECT_TOKEN})
93+
assert response.status_code == 200

0 commit comments

Comments
 (0)