Skip to content

Commit 1fbe7cf

Browse files
authored
Merge pull request #76 from Vidushi2709/feature/unit-tests
feat: Add comprehensive unit tests
2 parents 2d9dc69 + 89a4e3d commit 1fbe7cf

11 files changed

Lines changed: 413 additions & 493 deletions

pytest.ini

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
[pytest]
2+
# Pytest configuration for OpenAgri-WeatherService
3+
4+
# Test discovery patterns
5+
python_files = test_*.py
6+
python_classes = Test*
7+
python_functions = test_*
8+
9+
# Test paths
10+
testpaths = tests
11+
12+
# Asyncio configuration
13+
asyncio_mode = auto
14+
15+
# Output options
16+
addopts =
17+
-v
18+
--strict-markers
19+
--tb=short
20+
--cov=src
21+
--cov-report=term-missing
22+
--cov-report=html
23+
--cov-report=xml
24+
-p no:warnings
25+
26+
# Markers
27+
markers =
28+
unit: Unit tests
29+
integration: Integration tests
30+
slow: Slow running tests
31+
anyio: Async tests using anyio
32+
33+
# Coverage options
34+
[coverage:run]
35+
source = src
36+
omit =
37+
*/tests/*
38+
*/venv/*
39+
*/__pycache__/*
40+
*/site-packages/*
41+
42+
[coverage:report]
43+
exclude_lines =
44+
pragma: no cover
45+
def __repr__
46+
raise AssertionError
47+
raise NotImplementedError
48+
if __name__ == .__main__.:
49+
if TYPE_CHECKING:
50+
@abstractmethod
51+

requirements-test.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pytest==8.3.3
2+
pytest-asyncio==0.24.0
3+
pytest-cov==5.0.0
4+
httpx==0.27.0
5+
mongomock-motor==0.0.34
6+

src/external_services/interoperability.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ def predictions_to_jsonld(cls, predictions: List[Prediction], spatial_entity: Po
155155
collection_schema["hasFeatureOfInterest"]["long"] = spatial_entity.location.coordinates[1]
156156

157157
for p in preds:
158-
item_prefix = f"weather:forecast:{property_schema[p.measurement_type]["measurement"].lower()}"
158+
measurement_name = property_schema[p.measurement_type]["measurement"].lower()
159+
item_prefix = f"weather:forecast:{measurement_name}"
159160
item_schema = utils.deepcopy_dict(cls.item_schema)
160161
item_schema["@id"] = utils.generate_uuid(item_prefix, p.id)
161162
item_schema["observedProperty"] = f"cf:{p.measurement_type}"

src/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ def load_class(classpath):
116116
def load_classes(pathname, base_classes):
117117
classes = []
118118
for path in glob.glob(pathname, recursive=True):
119-
module = import_module(os.path.splitext(path)[0].strip('./').replace('/', '.'))
119+
# Handle both Windows and Unix path separators
120+
module_path = os.path.splitext(path)[0].strip('./').replace('/', '.').replace('\\', '.')
121+
module = import_module(module_path)
120122
for _, obj in inspect.getmembers(module):
121123
if inspect.isclass(obj) and obj not in base_classes and issubclass(obj, base_classes):
122124
classes.append(obj)

tests/conftest.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
Pytest configuration and shared fixtures for all tests.
3+
This file is automatically loaded by pytest.
4+
"""
5+
6+
import pytest
7+
8+
9+
# Configure pytest-asyncio
10+
@pytest.fixture
11+
def anyio_backend():
12+
"""Configure anyio backend for async tests"""
13+
return "asyncio"
14+
15+
16+
# Configuration for pytest markers
17+
def pytest_configure(config):
18+
"""Configure custom pytest markers"""
19+
config.addinivalue_line("markers", "integration: mark test as integration test")
20+
config.addinivalue_line("markers", "unit: mark test as unit test")
21+
config.addinivalue_line("markers", "slow: mark test as slow running")

tests/core/test_models.py

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import pytest
2-
3-
from tests.fixtures import *
42
from pydantic import ValidationError
53

64
from src.models.point import Point
75
from src.models.prediction import Prediction
86
from src.models.weather_data import WeatherData
7+
from tests.fixtures import *
8+
99

1010
class TestModels:
1111

@@ -14,10 +14,7 @@ async def test_point_model_valid(self, app):
1414
valid_point = {
1515
"title": "Point title",
1616
"type": "field",
17-
"location": {
18-
"type": "Point",
19-
"coordinates": [55.43989, 43.121]
20-
}
17+
"location": {"type": "Point", "coordinates": [55.43989, 43.121]},
2118
}
2219
point = Point(**valid_point)
2320
assert point.title == "Point title"
@@ -28,49 +25,40 @@ async def test_point_model_invalid_point_type(self, app):
2825
invalid_point = {
2926
"title": "Point title",
3027
"type": "lala",
31-
"location": {
32-
"type": "Point",
33-
"coordinates": [55.43989, 43.121]
34-
}
28+
"location": {"type": "Point", "coordinates": [55.43989, 43.121]},
3529
}
3630
with pytest.raises(ValidationError):
3731
Point(**invalid_point)
3832

3933
@pytest.mark.anyio
4034
async def test_prediction_model_valid(self, app):
4135
valid_prediction = {
42-
"value": 32.3,
43-
"timestamp": "2024-06-21T15:00:00.000Z",
44-
"source": "openweathermaps",
45-
"data_type": "weather",
46-
"measurement_type": "ambient_temperature",
47-
"spatial_entity": {
48-
"id": "bad6cd67-638f-42d8-82b8-d4d191174dd6",
49-
"type": "POI",
50-
"location": {
51-
"type": "Point",
52-
"coordinates": [ 39.1436, 26.40518]
53-
},
54-
},
55-
}
36+
"value": 32.3,
37+
"timestamp": "2024-06-21T15:00:00.000Z",
38+
"source": "openweathermaps",
39+
"data_type": "weather",
40+
"measurement_type": "ambient_temperature",
41+
"spatial_entity": {
42+
"id": "bad6cd67-638f-42d8-82b8-d4d191174dd6",
43+
"type": "POI",
44+
"location": {"type": "Point", "coordinates": [39.1436, 26.40518]},
45+
},
46+
}
5647
prediction = Prediction(**valid_prediction)
5748
assert prediction.spatial_entity.location.type == "Point"
58-
assert hasattr(prediction, 'created_at')
49+
assert hasattr(prediction, "created_at")
5950
assert prediction.value == 32.3
6051

6152
@pytest.mark.anyio
6253
async def test_weatherdata_model_valid(self, app):
6354
valid_weeatherdata = {
64-
"spatial_entity": {
65-
"id": "0b1b7964-8f89-465c-a8b2-3d50a53459e0",
66-
"type": "POI",
67-
"location": {
68-
"type": "Point",
69-
"coordinates": [ 39.1436, 26.40518]
70-
},
71-
},
72-
"data": {}
73-
}
55+
"spatial_entity": {
56+
"id": "0b1b7964-8f89-465c-a8b2-3d50a53459e0",
57+
"type": "POI",
58+
"location": {"type": "Point", "coordinates": [39.1436, 26.40518]},
59+
},
60+
"data": {},
61+
}
7462
weatherdata = WeatherData(**valid_weeatherdata)
7563
assert weatherdata.spatial_entity.location.coordinates == [39.1436, 26.40518]
7664
assert weatherdata.spatial_entity.location.type == "Point"
@@ -79,16 +67,12 @@ async def test_weatherdata_model_valid(self, app):
7967
@pytest.mark.anyio
8068
async def test_weatherdata_model_not_valid_point_type(self, app):
8169
valid_weeatherdata = {
82-
"spatial_entity": {
83-
"id": "0b1b7964-8f89-465c-a8b2-3d50a53459e0",
84-
"type": "routa",
85-
"location": {
86-
"type": "Point",
87-
"coordinates": [ 39.1436, 26.40518]
88-
},
89-
},
90-
"data": {}
91-
}
70+
"spatial_entity": {
71+
"id": "0b1b7964-8f89-465c-a8b2-3d50a53459e0",
72+
"type": "routa",
73+
"location": {"type": "Point", "coordinates": [39.1436, 26.40518]},
74+
},
75+
"data": {},
76+
}
9277
with pytest.raises(ValidationError):
9378
WeatherData(**valid_weeatherdata)
94-

tests/core/test_routes.py

Lines changed: 0 additions & 85 deletions
This file was deleted.

tests/core/test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
2-
from tests.fixtures import *
32

43
from src.utils import calculate_thi
4+
from tests.fixtures import *
55

66

77
class TestUtils:

0 commit comments

Comments
 (0)