Skip to content

Commit 2f95240

Browse files
committed
feat(locate): add test cases for project locate features
- add locate project type specifics on mutation
1 parent 0914726 commit 2f95240

File tree

3 files changed

+314
-0
lines changed

3 files changed

+314
-0
lines changed

apps/project/graphql/inputs/inputs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .project_types.compare import CompareProjectPropertyInput
1919
from .project_types.completeness import CompletenessProjectPropertyInput
2020
from .project_types.find import FindProjectPropertyInput
21+
from .project_types.locate import LocateProjectPropertyInput
2122
from .project_types.street import StreetProjectPropertyInput
2223
from .project_types.validate import ValidateProjectPropertyInput
2324
from .project_types.validate_image import ValidateImageProjectPropertyInput
@@ -53,6 +54,7 @@ class ProjectTypeSpecificInput:
5354
validate: ValidateProjectPropertyInput | None = strawberry.UNSET
5455
validate_image: ValidateImageProjectPropertyInput | None = strawberry.UNSET
5556
street: StreetProjectPropertyInput | None = strawberry.UNSET
57+
locate: LocateProjectPropertyInput | None = strawberry.UNSET
5658

5759

5860
# NOTE: Make sure this matches with the serializers ../serializers.py

apps/project/tests/mutation_test.py

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
from main.tests import TestCase
2828
from project_types.street import project as street_project
2929
from project_types.tile_map_service.compare import project as compare_project
30+
from project_types.tile_map_service.locate import project as locate_project
31+
from project_types.tile_map_service.locate.project import SubGridSizeEnum
3032
from utils.geo.raster_tile_server.config import RasterTileServerNameEnum
3133

3234
BASE_DIR = Path(__file__).resolve().parent
@@ -1692,3 +1694,302 @@ def test_project_street(self, mock_requests): # type: ignore[reportMissingParam
16921694
assert resp_data["result"]["status"] == self.genum(Project.Status.READY_TO_PROCESS)
16931695

16941696
mock_requests.assert_called_once()
1697+
1698+
@patch("apps.project.serializers.process_project_task.delay")
1699+
def test_project_locate(self, mock_requests): # type: ignore[reportMissingParameterType]
1700+
self.force_login(self.user)
1701+
project_data = {
1702+
**self.project_data,
1703+
"projectType": self.genum(ProjectTypeEnum.LOCATE),
1704+
"clientId": str(ULID()),
1705+
}
1706+
content = self._create_project_mutation(project_data)
1707+
resp_data = content["data"]["createProject"]
1708+
assert resp_data["errors"] is None, content
1709+
1710+
project_id = resp_data["result"]["id"]
1711+
project_client_id = resp_data["result"]["clientId"]
1712+
1713+
# Creating AOI Project Asset
1714+
project_asset_data = {
1715+
"project": project_id,
1716+
"clientId": str(ULID()),
1717+
}
1718+
content = self._create_project_aoi_asset(project_asset_data)
1719+
resp_data = content["data"]["createProjectAsset"]
1720+
assert resp_data["errors"] is None, content
1721+
aoi_geometry_asset = resp_data["result"]
1722+
1723+
# Creating Project Image Asset
1724+
project_asset_data = {
1725+
"project": project_id,
1726+
"clientId": str(ULID()),
1727+
}
1728+
content = self._create_project_image_asset(project_asset_data)
1729+
resp_data = content["data"]["createProjectAsset"]
1730+
assert resp_data["errors"] is None, content
1731+
image_asset = resp_data["result"]
1732+
1733+
# Updating Project
1734+
project_data = {
1735+
"clientId": project_client_id,
1736+
"image": image_asset["id"],
1737+
"verificationNumber": 10,
1738+
"projectTypeSpecifics": {
1739+
"locate": {
1740+
"aoiGeometry": aoi_geometry_asset["id"],
1741+
"zoomLevel": 15,
1742+
"tileServerProperty": {
1743+
"name": self.genum(RasterTileServerNameEnum.CUSTOM),
1744+
"custom": {
1745+
"url": "https://hi-there/{x}/{y}/{z}",
1746+
"credits": "My Map",
1747+
},
1748+
},
1749+
"subGridSize": self.genum(SubGridSizeEnum.SIZE_2X2),
1750+
},
1751+
},
1752+
}
1753+
content = self._update_project_mutation(project_id, project_data)
1754+
resp_data = content["data"]["updateProject"]
1755+
assert resp_data["errors"] is None, content
1756+
1757+
latest_project = Project.objects.get(pk=project_id)
1758+
assert latest_project.created_by_id == self.user.pk
1759+
assert latest_project.modified_by_id == self.user.pk
1760+
assert latest_project.image_id == int(image_asset["id"])
1761+
assert latest_project.aoi_geometry_input_asset
1762+
assert latest_project.aoi_geometry_input_asset.id == int(aoi_geometry_asset["id"])
1763+
assert latest_project.project_type_specifics == {
1764+
"aoi_geometry": aoi_geometry_asset["id"],
1765+
"zoom_level": 15,
1766+
"tile_server_property": {
1767+
"name": RasterTileServerNameEnum.CUSTOM.value,
1768+
"custom": {
1769+
"url": "https://hi-there/{x}/{y}/{z}",
1770+
"credits": "My Map",
1771+
},
1772+
},
1773+
"sub_grid_size": SubGridSizeEnum.SIZE_2X2.value,
1774+
}
1775+
locate_project.LocateProjectProperty.model_validate(
1776+
latest_project.project_type_specifics,
1777+
context={"project_id": latest_project.pk},
1778+
)
1779+
1780+
# Updating Project:
1781+
# Test project processing
1782+
project_data = {
1783+
"clientId": project_client_id,
1784+
"status": self.genum(Project.Status.READY_TO_PROCESS),
1785+
}
1786+
content = self._update_project_status_mutation(project_id, project_data)
1787+
resp_data = content["data"]["updateProjectStatus"]
1788+
assert resp_data["errors"] is None, content
1789+
assert resp_data["result"]["status"] == self.genum(Project.Status.READY_TO_PROCESS)
1790+
latest_project.refresh_from_db()
1791+
assert latest_project.processing_status is None
1792+
1793+
mock_requests.assert_called_once()
1794+
mock_requests.assert_has_calls([call(int(project_id))])
1795+
1796+
process_project_task(int(project_id))
1797+
1798+
latest_project.refresh_from_db()
1799+
1800+
project_task_group_qs = ProjectTaskGroup.objects.filter(project=latest_project)
1801+
project_task_qs = ProjectTask.objects.filter(task_group__project=latest_project)
1802+
1803+
class TaskGroupSpecificsType(typing.TypedDict):
1804+
x_max: int
1805+
x_min: int
1806+
y_max: int
1807+
y_min: int
1808+
1809+
class TaskGroupType(typing.TypedDict):
1810+
firebase_id: str
1811+
number_of_tasks: int
1812+
required_count: int
1813+
total_area: float
1814+
project_type_specifics: TaskGroupSpecificsType
1815+
1816+
expected_tasks_groups: list[TaskGroupType] = [
1817+
{
1818+
"firebase_id": "g101",
1819+
"number_of_tasks": 18,
1820+
"project_type_specifics": {
1821+
"x_max": 24152,
1822+
"x_min": 24147,
1823+
"y_max": 13755,
1824+
"y_min": 13753,
1825+
},
1826+
"required_count": 10,
1827+
"total_area": 21.010735845202447,
1828+
},
1829+
{
1830+
"firebase_id": "g102",
1831+
"number_of_tasks": 24,
1832+
"project_type_specifics": {
1833+
"x_max": 24153,
1834+
"x_min": 24146,
1835+
"y_max": 13758,
1836+
"y_min": 13756,
1837+
},
1838+
"required_count": 10,
1839+
"total_area": 28.02915392364502,
1840+
},
1841+
{
1842+
"firebase_id": "g103",
1843+
"number_of_tasks": 24,
1844+
"project_type_specifics": {
1845+
"x_max": 24153,
1846+
"x_min": 24146,
1847+
"y_max": 13761,
1848+
"y_min": 13759,
1849+
},
1850+
"required_count": 10,
1851+
"total_area": 28.043986769512177,
1852+
},
1853+
{
1854+
"firebase_id": "g104",
1855+
"number_of_tasks": 6,
1856+
"project_type_specifics": {
1857+
"x_max": 24150,
1858+
"x_min": 24149,
1859+
"y_max": 13764,
1860+
"y_min": 13762,
1861+
},
1862+
"required_count": 10,
1863+
"total_area": 7.014703242812157,
1864+
},
1865+
]
1866+
1867+
expected_last_5_tasks = [
1868+
{
1869+
"firebase_id": "15-24147-13753",
1870+
"project_type_specifics": {
1871+
"tile_x": 24147,
1872+
"tile_y": 13753,
1873+
"url": "https://hi-there/24147/13753/15",
1874+
},
1875+
},
1876+
{
1877+
"firebase_id": "15-24147-13754",
1878+
"project_type_specifics": {
1879+
"tile_x": 24147,
1880+
"tile_y": 13754,
1881+
"url": "https://hi-there/24147/13754/15",
1882+
},
1883+
},
1884+
{
1885+
"firebase_id": "15-24147-13755",
1886+
"project_type_specifics": {
1887+
"tile_x": 24147,
1888+
"tile_y": 13755,
1889+
"url": "https://hi-there/24147/13755/15",
1890+
},
1891+
},
1892+
{
1893+
"firebase_id": "15-24148-13753",
1894+
"project_type_specifics": {
1895+
"tile_x": 24148,
1896+
"tile_y": 13753,
1897+
"url": "https://hi-there/24148/13753/15",
1898+
},
1899+
},
1900+
{
1901+
"firebase_id": "15-24148-13754",
1902+
"project_type_specifics": {
1903+
"tile_x": 24148,
1904+
"tile_y": 13754,
1905+
"url": "https://hi-there/24148/13754/15",
1906+
},
1907+
},
1908+
]
1909+
1910+
assert {
1911+
"required_results": (18 + 24 + 24 + 6) * 10,
1912+
"tasks_groups_count": project_task_group_qs.count(),
1913+
"tasks_groups": list(
1914+
project_task_group_qs.order_by("id").values(
1915+
"firebase_id",
1916+
"number_of_tasks",
1917+
"required_count",
1918+
"total_area",
1919+
"project_type_specifics",
1920+
),
1921+
),
1922+
"tasks_count": project_task_qs.count(),
1923+
"tasks": list(
1924+
project_task_qs.order_by("id").values(
1925+
"firebase_id",
1926+
"project_type_specifics",
1927+
)[:5],
1928+
),
1929+
"status": latest_project.status,
1930+
"processing_status": latest_project.processing_status,
1931+
} == {
1932+
"required_results": latest_project.required_results,
1933+
"tasks_groups_count": len(expected_tasks_groups),
1934+
"tasks_groups": expected_tasks_groups,
1935+
"tasks_count": 72,
1936+
"tasks": expected_last_5_tasks,
1937+
"status": Project.Status.PROCESSED,
1938+
"processing_status": Project.ProcessingStatus.COMPLETED,
1939+
}
1940+
1941+
# Updating Processed Project:
1942+
# Attaching tutorial
1943+
locate_tutorial = TutorialFactory.create(
1944+
**self.user_resource_kwargs,
1945+
project=latest_project,
1946+
)
1947+
project_data = {
1948+
"clientId": project_client_id,
1949+
"tutorial": str(locate_tutorial.id),
1950+
}
1951+
content = self._update_processed_project_mutation(project_id, project_data)
1952+
resp_data = content["data"]["updateProcessedProject"]
1953+
assert resp_data["errors"] is None, content
1954+
assert resp_data["result"]["tutorialId"] == str(locate_tutorial.id)
1955+
1956+
# project is not sync to firebase
1957+
project_ref = self.firebase_helper.ref(
1958+
Config.FirebaseKeys.project(latest_project.firebase_id),
1959+
)
1960+
fb_project: typing.Any = project_ref.get()
1961+
assert fb_project is None
1962+
1963+
# Updating Processed Project:
1964+
# Publishing project
1965+
project_data = {
1966+
"clientId": project_client_id,
1967+
"status": self.genum(Project.Status.READY_TO_PUBLISH),
1968+
}
1969+
content = self._update_project_status_mutation(project_id, project_data)
1970+
resp_data = content["data"]["updateProjectStatus"]
1971+
assert resp_data["errors"] is None, content
1972+
assert resp_data["result"]["status"] == self.genum(Project.Status.READY_TO_PUBLISH)
1973+
1974+
latest_project.refresh_from_db()
1975+
assert latest_project.processing_status == Project.ProcessingStatus.COMPLETED
1976+
1977+
# project is sync to firebase after publish
1978+
fb_project: typing.Any = project_ref.get()
1979+
assert fb_project is not None
1980+
1981+
# Updating Processed Project after publishing
1982+
project_data = {
1983+
"clientId": project_client_id,
1984+
"maxTasksPerUser": 1000,
1985+
}
1986+
content = self._update_processed_project_mutation(project_id, project_data)
1987+
resp_data = content["data"]["updateProcessedProject"]
1988+
assert resp_data["errors"] is None, content
1989+
assert resp_data["result"]["maxTasksPerUser"] == 1000
1990+
project_ref = self.firebase_helper.ref(
1991+
Config.FirebaseKeys.project(latest_project.firebase_id),
1992+
)
1993+
fb_project: typing.Any = project_ref.get()
1994+
assert fb_project is not None
1995+
assert fb_project["maxTasksPerUser"] == 1000

schema.graphql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,16 @@ input IntRangeLookup {
935935
start: Int = null
936936
}
937937

938+
input LocateProjectPropertyInput {
939+
"""Numeric value as string"""
940+
aoiGeometry: String!
941+
subGridSize: SubGridSizeEnum!
942+
tileServerProperty: ProjectRasterTileServerConfigInput!
943+
944+
"""Zoom level from 14 to 22"""
945+
zoomLevel: Int!
946+
}
947+
938948
type LocateProjectPropertyType {
939949
"""Numeric value as string"""
940950
aoiGeometry: String!
@@ -1954,6 +1964,7 @@ input ProjectTypeSpecificInput @oneOf {
19541964
compare: CompareProjectPropertyInput
19551965
completeness: CompletenessProjectPropertyInput
19561966
find: FindProjectPropertyInput
1967+
locate: LocateProjectPropertyInput
19571968
street: StreetProjectPropertyInput
19581969
validate: ValidateProjectPropertyInput
19591970
validateImage: ValidateImageProjectPropertyInput

0 commit comments

Comments
 (0)