From a00ea8233db2702c985545e24a588743bd1bf11a Mon Sep 17 00:00:00 2001 From: spwoodcock Date: Mon, 29 Jul 2024 17:39:18 +0100 Subject: [PATCH] test: update entity tests to support latest bulk entity upload api --- pyproject.toml | 2 +- tests/conftest.py | 73 ++++++++++++++++---------- tests/test_entities.py | 113 +++++++++++++++++++++-------------------- 3 files changed, 105 insertions(+), 83 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1b0e4f1a..06d534dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,7 +117,7 @@ exclude = [ [tool.ruff.lint] select = ["I", "E", "W", "D", "B", "F", "N", "Q"] ignore = ["N805", "B008"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "osm_fieldwork/basemapper.py" = ["N802"] [tool.ruff.lint.pydocstyle] convention = "google" diff --git a/tests/conftest.py b/tests/conftest.py index ce00a051..4190563d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,7 +26,6 @@ from osm_fieldwork.OdkCentral import OdkAppUser, OdkForm, OdkProject from osm_fieldwork.OdkCentralAsync import OdkDataset -from osm_fieldwork.xlsforms import entities_registration_xml logging.basicConfig( level="DEBUG", @@ -155,51 +154,69 @@ def odk_form_cleanup(odk_form): assert success -# NOTE this is session scoped as odk_entity_cleanup depends on it @pytest.fixture(scope="session") -def odk_entity(project_details) -> tuple: - """Get entity for a project.""" +async def odk_dataset(project_details) -> tuple: + """Get dataset (entity list) for a project.""" odk_id = project_details.get("id") - entity = OdkDataset( + dataset = OdkDataset( url="https://proxy", user="test@hotosm.org", passwd="Password1234", ) - return odk_id, entity + # Create the dataset + async with dataset as odk_dataset: + created_dataset = await odk_dataset.createDataset( + odk_id, + "features", + [ + "geometry", + "project_id", + "task_id", + "osm_id", + "tags", + "version", + "changeset", + "timestamp", + "status", + ], + ) + assert created_dataset.get("name") == "features" + assert sorted(created_dataset.get("properties", [])) == sorted( + [ + "geometry", + "project_id", + "task_id", + "osm_id", + "tags", + "version", + "changeset", + "timestamp", + "status", + ] + ) + + return odk_id, dataset -# NOTE this is session scoped to avoid attempting to create duplicate form -@pytest.fixture(scope="session") -async def odk_entity_cleanup(odk_entity): - """Get Entity for project, with automatic cleanup after.""" - odk_id, entity = odk_entity - # Create entity registration form - form = OdkForm( - entity.url, - entity.user, - entity.passwd, - ) - form_name = form.createForm(odk_id, str(entities_registration_xml), publish=True) - if not form_name: - raise AssertionError("Failed to create form") +@pytest.fixture(scope="session") +async def odk_dataset_cleanup(odk_dataset): + """Get Dataset for project, with automatic cleanup after.""" + odk_id, dataset = odk_dataset dataset_name = "features" - async with entity: - entity_json = await entity.createEntity(odk_id, dataset_name, "test entity", {"osm_id": "1", "geometry": "test"}) + async with dataset as odk_dataset: + entity_json = await odk_dataset.createEntity(odk_id, dataset_name, "test entity", {"osm_id": "1", "geometry": "test"}) entity_uuid = entity_json.get("uuid") # Before yield is used in tests - yield odk_id, dataset_name, entity_uuid, entity + yield odk_id, dataset_name, entity_uuid, dataset # After yield is test cleanup - async with entity: - entity_deleted = await entity.deleteEntity(odk_id, dataset_name, entity_uuid) + async with dataset as odk_dataset: + entity_deleted = await odk_dataset.deleteEntity(odk_id, dataset_name, entity_uuid) assert entity_deleted - form_deleted = form.deleteForm(odk_id, form_name) - assert form_deleted - @pytest.fixture(scope="session", autouse=True) def cleanup(): diff --git a/tests/test_entities.py b/tests/test_entities.py index 527a3cdb..72d7b232 100644 --- a/tests/test_entities.py +++ b/tests/test_entities.py @@ -20,11 +20,13 @@ from datetime import datetime, timezone import pytest +from aiohttp.client_exceptions import ClientError -async def test_entity_modify(odk_entity_cleanup): +async def test_entity_modify(odk_dataset_cleanup): """Test modifying an entity.""" - odk_id, dataset_name, entity_uuid, entity = odk_entity_cleanup + odk_id, dataset_name, entity_uuid, entity = odk_dataset_cleanup + print(dataset_name) async with entity: updated_entity = await entity.updateEntity(odk_id, dataset_name, entity_uuid, label="new label") assert updated_entity.get("currentVersion").get("label") == "new label" @@ -38,66 +40,74 @@ async def test_entity_modify(odk_entity_cleanup): assert new_data.get("project_id") == "100" -async def test_create_invalid_entities(odk_entity_cleanup): +async def test_create_invalid_entities(odk_dataset_cleanup): """Test uploading invalid data to an entity (HTTP 400).""" - odk_id, dataset_name, entity_uuid, entity = odk_entity_cleanup + odk_id, dataset_name, entity_uuid, entity = odk_dataset_cleanup async with entity: # NOTE entity must have a geometry data field with pytest.raises(ValueError): await entity.createEntity(odk_id, dataset_name, label="test", data={"status": 0}) - # NOTE data fields cannot be integer, this should 400 response - invalid_data_type = await entity.createEntity(odk_id, dataset_name, label="test", data={"geometry": "", "status": 0}) - assert invalid_data_type == {} - - bulk_entities_one_invaid = await entity.createEntities( - odk_id, - dataset_name, - { - "test entity 2": {"osm_id": 55, "geometry": "test"}, - "test entity 3": {"osm_id": "66", "geometry": "test"}, - }, - ) - assert len(bulk_entities_one_invaid) == 1 + # NOTE data fields cannot be integer, this should raise error + with pytest.raises(ClientError): + await entity.createEntity(odk_id, dataset_name, label="test", data={"geometry": "", "status": 0}) + + # Also test bulk entity create using integer data + with pytest.raises(ClientError): + await entity.createEntities( + odk_id, + dataset_name, + [ + {"label": "test entity 2", "data": {"osm_id": 55, "geometry": "test"}}, + {"label": "test entity 3", "data": {"osm_id": "66", "geometry": "test"}}, + ], + ) + + # Bulk Entity creation, not a list + with pytest.raises(ValueError): + await entity.createEntities( + odk_id, + dataset_name, + {"label": "test", "data": {}}, + ) -async def test_bulk_create_entity_count(odk_entity_cleanup): +async def test_bulk_create_entity_count(odk_dataset_cleanup): """Test bulk creation of Entities.""" - odk_id, dataset_name, entity_uuid, entity = odk_entity_cleanup + odk_id, dataset_name, entity_uuid, entity = odk_dataset_cleanup async with entity: - created_entities = await entity.createEntities( + await entity.createEntities( odk_id, dataset_name, - { - "test entity 1": {"osm_id": "44", "geometry": "test"}, - "test entity 2": {"osm_id": "55", "geometry": "test"}, - "test entity 3": {"osm_id": "66", "geometry": "test"}, - }, + [ + {"label": "test entity 1", "data": {"osm_id": "44", "geometry": "test"}}, + {"label": "test entity 2", "data": {"osm_id": "55", "geometry": "test"}}, + {"label": "test entity 3", "data": {"osm_id": "66", "geometry": "test"}}, + ], ) entity_count = await entity.getEntityCount(odk_id, dataset_name) - assert created_entities[0].get("currentVersion").get("data").get("geometry") == "test" # NOTE this may be cumulative from the session... either 4 or 5 assert entity_count >= 4 -async def test_get_entity_data(odk_entity_cleanup): +async def test_get_entity_data(odk_dataset_cleanup): """Test getting entity data, inluding via a OData filter.""" - odk_id, dataset_name, entity_uuid, entity = odk_entity_cleanup + odk_id, dataset_name, entity_uuid, entity = odk_dataset_cleanup async with entity: - new_entities = await entity.createEntities( + await entity.createEntities( odk_id, dataset_name, - { - "test entity 1": {"geometry": "test"}, - "test entity 2": {"geometry": "test"}, - "test entity 3": {"geometry": "test"}, - "test entity 4": {"geometry": "test"}, - "test entity 5": {"geometry": "test"}, - "test entity 6": {"geometry": "test"}, - "test entity 7": {"geometry": "test"}, - "test entity 8": {"geometry": "test"}, - }, + [ + {"label": "test entity 1", "data": {"geometry": "test"}}, + {"label": "test entity 2", "data": {"geometry": "test"}}, + {"label": "test entity 3", "data": {"geometry": "test"}}, + {"label": "test entity 4", "data": {"geometry": "test"}}, + {"label": "test entity 5", "data": {"geometry": "test"}}, + {"label": "test entity 6", "data": {"geometry": "test"}}, + {"label": "test entity 7", "data": {"geometry": "test"}}, + {"label": "test entity 8", "data": {"geometry": "test"}}, + ], ) all_entities = await entity.getEntityData(odk_id, dataset_name) @@ -113,18 +123,12 @@ async def test_get_entity_data(odk_entity_cleanup): assert filtered_entities.get("@odata.count") >= 9 assert "@odata.nextLink" in filtered_entities.keys() - entity_uuids = [_entity.get("uuid") for _entity in new_entities] - - # Update all entities, so updatedAt is not 'None' - for uuid in entity_uuids: - await entity.updateEntity(odk_id, dataset_name, uuid, data={"status": "READY"}) - # Get current time NOTE time format = 2022-01-31T23:59:59.999Z time_now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ") - # Update last 3 entities prior to filter - entity_uuids = [_entity.get("uuid") for _entity in new_entities] - for uuid in entity_uuids[5:]: + # Update first 5 entities prior to filter + entity_uuids = [_entity.get("__id") for _entity in all_entities] + for uuid in sorted(entity_uuids[:5]): await entity.updateEntity(odk_id, dataset_name, uuid, data={"status": "LOCKED_FOR_MAPPING"}) filter_updated = await entity.getEntityData( @@ -132,13 +136,14 @@ async def test_get_entity_data(odk_entity_cleanup): dataset_name, url_params=f"$filter=__system/updatedAt gt {time_now}", ) - assert len(filter_updated) == 3 - assert filter_updated[0].get("status") == "LOCKED_FOR_MAPPING" + assert len(filter_updated) == 5 + for entity in filter_updated: + assert entity.get("status") == "LOCKED_FOR_MAPPING" -async def test_get_entity_data_select_params(odk_entity_cleanup): +async def test_get_entity_data_select_params(odk_dataset_cleanup): """Test selecting specific param for an Entity.""" - odk_id, dataset_name, entity_uuid, entity = odk_entity_cleanup + odk_id, dataset_name, entity_uuid, entity = odk_dataset_cleanup async with entity: entities_select_params = await entity.getEntityData( odk_id, @@ -153,9 +158,9 @@ async def test_get_entity_data_select_params(odk_entity_cleanup): assert "geometry" in first_entity, "Missing 'geometry' key" -async def test_get_single_entity(odk_entity_cleanup): +async def test_get_single_entity(odk_dataset_cleanup): """Test getting specific Entity by UUID.""" - odk_id, dataset_name, entity_uuid, entity = odk_entity_cleanup + odk_id, dataset_name, entity_uuid, entity = odk_dataset_cleanup async with entity: single_entity = await entity.getEntity( odk_id,