Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test cases for user and project functionalities with fixtures #415

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
18 changes: 13 additions & 5 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
import uuid
from typing import Annotated, Optional, List
from typing import Annotated, Optional, List, Union
from datetime import datetime, date
import geojson
from loguru import logger as log
Expand Down Expand Up @@ -73,9 +73,16 @@ def validate_geojson(
return None


def enum_to_str(value: IntEnum) -> str:
"""Get the string value of the enum for db insert."""
return value.name
def enum_to_str(value: Union[IntEnum, str]) -> str:
"""
Get the string value of the enum for db insert.
Handles both IntEnum objects and string values.
"""
if isinstance(value, str):
return value
if isinstance(value, IntEnum):
return value.name
return value


class ProjectIn(BaseModel):
Expand Down Expand Up @@ -150,7 +157,8 @@ def slug(self) -> str:
return slug_with_date
except Exception as e:
log.error(f"An error occurred while generating the slug: {e}")
return ""

return ""

@model_validator(mode="before")
@classmethod
Expand Down
35 changes: 24 additions & 11 deletions src/backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from psycopg import AsyncConnection
from app.users.user_schemas import DbUser
import pytest
from app.projects.project_schemas import ProjectIn, DbProject
from app.projects.project_schemas import DbProject, ProjectIn


@pytest_asyncio.fixture(scope="function")
Expand All @@ -28,7 +28,7 @@ async def db() -> AsyncConnection:


@pytest_asyncio.fixture(scope="function")
async def user(db) -> AuthUser:
async def auth_user(db) -> AuthUser:
"""Create a test user."""
db_user = await DbUser.get_or_create_user(
db,
Expand All @@ -44,15 +44,11 @@ async def user(db) -> AuthUser:


@pytest_asyncio.fixture(scope="function")
async def project_info(db, user):
async def project_info():
"""
Fixture to create project metadata for testing.

"""
print(
f"User passed to project_info fixture: {user}, ID: {getattr(user, 'id', 'No ID')}"
)

project_metadata = ProjectIn(
name="TEST 98982849249278787878778",
description="",
Expand Down Expand Up @@ -93,12 +89,30 @@ async def project_info(db, user):
)

try:
await DbProject.create(db, project_metadata, getattr(user, "id", ""))
return project_metadata
except Exception as e:
pytest.fail(f"Fixture setup failed with exception: {str(e)}")


@pytest_asyncio.fixture(scope="function")
async def create_test_project(db, auth_user, project_info):
"""
Fixture to create a test project and return its project_id.
"""
project_id = await DbProject.create(db, project_info, auth_user.id)
return str(project_id)


@pytest_asyncio.fixture(scope="function")
async def test_get_project(db, create_test_project):
"""
Fixture to create a test project and return its project_id.
"""
project_id = create_test_project
project_info = await DbProject.one(db, project_id)
return project_info


@pytest_asyncio.fixture(autouse=True)
async def app() -> AsyncGenerator[FastAPI, Any]:
"""Get the FastAPI test server."""
Expand All @@ -125,12 +139,11 @@ def drone_info():


@pytest_asyncio.fixture(scope="function")
async def client(app: FastAPI, db: AsyncConnection):
async def client(app: FastAPI, db: AsyncConnection, auth_user: AuthUser):
"""The FastAPI test server."""
# Override server db connection
app.dependency_overrides[get_db] = lambda: db
app.dependency_overrides[login_required] = lambda: user

app.dependency_overrides[login_required] = lambda: auth_user
async with LifespanManager(app) as manager:
async with AsyncClient(
transport=ASGITransport(app=manager.app),
Expand Down
98 changes: 71 additions & 27 deletions src/backend/tests/test_projects_routes.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,71 @@
# import pytest
# import json


# @pytest.mark.asyncio
# async def test_create_project_with_files(client, project_info,):
# """
# Test to verify the project creation API with file upload (image as binary data).
# """
# project_info_json = json.dumps(project_info.model_dump())
# files = {
# "project_info": (None, project_info_json, "application/json"),
# "dem": None,
# "image": None
# }

# files = {k: v for k, v in files.items() if v is not None}
# response = await client.post(
# "/api/projects/",
# files=files
# )
# assert response.status_code == 201
# return response.json()

# if __name__ == "__main__":
# """Main func if file invoked directly."""
# pytest.main()
import pytest
import json
from io import BytesIO
from loguru import logger as log


@pytest.mark.asyncio
async def test_create_project_with_files(
client,
project_info,
):
"""
Test to verify the project creation API with file upload (image as binary data).
"""
project_info_json = json.dumps(project_info.model_dump())
files = {
"project_info": (None, project_info_json, "application/json"),
"dem": None,
"image": None,
}

files = {k: v for k, v in files.items() if v is not None}
response = await client.post("/api/projects/", files=files)
assert response.status_code == 200
return response.json()


@pytest.mark.asyncio
async def test_upload_project_task_boundaries(client, test_get_project):
"""
Test to verify the upload of task boundaries.
"""
project_id = str(test_get_project.id)
log.debug(f"Testing project ID: {project_id}")
task_geojson = json.dumps(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[
[85.32002733312942, 27.706336826417214],
[85.31945017091391, 27.705465823954995],
[85.32117509889912, 27.704809664174988],
[85.32135218276034, 27.70612197978899],
[85.32002733312942, 27.706336826417214],
]
],
"type": "Polygon",
},
}
],
}
).encode("utf-8")

geojosn_files = {
"geojson": ("file.geojson", BytesIO(task_geojson), "application/geo+json")
}
response = await client.post(
f"/api/projects/{project_id}/upload-task-boundaries/", files=geojosn_files
)
assert response.status_code == 200
return response.json()


if __name__ == "__main__":
"""Main func if file invoked directly."""
pytest.main()
17 changes: 17 additions & 0 deletions src/backend/tests/test_tasks_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest
import uuid


@pytest.mark.asyncio
async def test_read_task(client):
task_id = uuid.uuid4()
response = await client.get(f"/api/tasks/{task_id}")
assert response.status_code == 200


@pytest.mark.asyncio
async def test_task_states(client, create_test_project):
project_id = create_test_project

response = await client.get(f"/api/tasks/states/{project_id}")
assert response.status_code == 200
4 changes: 2 additions & 2 deletions src/backend/tests/test_users_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@


@pytest_asyncio.fixture(scope="function")
def token(user):
def token(auth_user):
"""
Create a reset password token for a given user.
"""
payload = {
"sub": user.email_address,
"sub": auth_user.email_address,
"exp": datetime.utcnow()
+ timedelta(minutes=settings.RESET_PASSWORD_TOKEN_EXPIRE_MINUTES),
}
Expand Down
Loading