From 1715fddfc2c704c98f7f7c2debbf36ed4e6c4f1e Mon Sep 17 00:00:00 2001 From: lindsay stevens Date: Thu, 18 Apr 2024 00:03:43 +1000 Subject: [PATCH] add: e2e tests for entities and entity lists - fix: entity.py `create()` should send JSON not form-encoded data - fix: projects.py typo in comment - chg: readme.md make description generic rather than listing everything - add: test entity_lists.py creation of test fixture with properties - add: e2e test cases for entities and entity_lists functionality --- README.md | 2 +- pyodk/_endpoints/entities.py | 2 +- pyodk/_endpoints/projects.py | 2 +- tests/test_client.py | 35 ++++++++++++++++++++++++++ tests/utils/entity_lists.py | 48 ++++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 tests/utils/entity_lists.py diff --git a/README.md b/README.md index 8745dbc..2404367 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ For interactive testing, debugging, or sanity checking workflows, end-to-end tes 1. Create a test project in Central. 2. Create a test user in Central. It can be a site-wide Administrator. If it is not an Administrator, assign the user to the project with "Project Manager" privileges, so that forms and submissions in the test project can be uploaded and modified. 3. Save the user's credentials and the project ID in a `.pyodk_config.toml` (or equivalent) as described in the above section titled "Configure". -4. When the tests in `test_client.py` are run, the test setup method should automatically create a few forms and submissions for testing with. At a minimum these allow the tests to pass, but can also be used to interactively test or debug. +4. When the tests in `test_client.py` are run, the test setup method should automatically create a few fixtures for testing with. At a minimum these allow the tests to pass, but can also be used to interactively test or debug. ## Release diff --git a/pyodk/_endpoints/entities.py b/pyodk/_endpoints/entities.py index c06a90a..ad80a22 100644 --- a/pyodk/_endpoints/entities.py +++ b/pyodk/_endpoints/entities.py @@ -131,7 +131,7 @@ def create( method="POST", url=self.session.urlformat(self.urls.post, project_id=pid, el_name=eln), logger=log, - data=req_data, + json=req_data, ) data = response.json() return Entity(**data) diff --git a/pyodk/_endpoints/projects.py b/pyodk/_endpoints/projects.py index 8e4159a..f390d2d 100644 --- a/pyodk/_endpoints/projects.py +++ b/pyodk/_endpoints/projects.py @@ -128,7 +128,7 @@ def create_app_users( # The "App User" role_id should always be "2", so no need to look it up by name. # Ref: "https://github.com/getodk/central-backend/blob/9db0d792cf4640ec7329722984 # cebdee3687e479/lib/model/migrations/20181212-01-add-roles.js" - # See also roles data in `tests/resorces/projects_data.py`. + # See also roles data in `tests/resources/projects_data.py`. if forms is not None: for user in users: for form_id in forms: diff --git a/tests/test_client.py b/tests/test_client.py index 777195d..6e00f0a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -5,6 +5,7 @@ from tests.resources import RESOURCES, forms_data, submissions_data from tests.utils import utils +from tests.utils.entity_lists import create_new_or_get_entity_list from tests.utils.forms import ( create_new_form__md, create_new_form__xml, @@ -71,6 +72,22 @@ def create_test_submissions(client: Client | None = None) -> Client: return client +def create_test_entity_lists(client: Client | None = None) -> Client: + """ + Create test entity lists, if they don't already exist. + :param client: Client instance to use for API calls. + :return: The original client instance, or a new one if none was provided. + """ + if client is None: + client = Client() + create_new_or_get_entity_list( + client=client, + entity_list_name="pyodk_test_eln", + entity_props=["test_label", "another_prop"], + ) + return client + + @skip class TestUsage(TestCase): """Tests for experimenting with usage scenarios / general debugging / integration.""" @@ -82,6 +99,7 @@ def setUpClass(cls): cls.client = Client() create_test_forms(client=cls.client) create_test_submissions(client=cls.client) + create_test_entity_lists(client=cls.client) def test_direct(self): projects = self.client.projects.list() @@ -216,3 +234,20 @@ def test_submission_edit__non_ascii(self): instance_id=iid, comment=f"pyODK edit {now}", ) + + def test_entities__create_and_query(self): + """Should create a new entity, and query it afterwards via list() or get_table().""" + self.client.entities.default_entity_list_name = "pyodk_test_eln" + entity = self.client.entities.create( + label="test_label", + data={"test_label": "test_value", "another_prop": "another_value"}, + ) + entity_list = self.client.entities.list() + self.assertIn(entity, entity_list) + entity_data = self.client.entities.get_table(select="__id") + self.assertIn(entity.uuid, [d["__id"] for d in entity_data["value"]]) + + def test_entity_lists__list(self): + """Should return a list of entities""" + observed = self.client.entity_lists.list() + self.assertGreater(len(observed), 0) diff --git a/tests/utils/entity_lists.py b/tests/utils/entity_lists.py new file mode 100644 index 0000000..d6039b1 --- /dev/null +++ b/tests/utils/entity_lists.py @@ -0,0 +1,48 @@ +from pyodk._endpoints.entity_lists import EntityList, log +from pyodk.client import Client +from pyodk.errors import PyODKError + + +def create_new_or_get_entity_list( + client: Client, entity_list_name: str, entity_props: list[str] +) -> EntityList: + """ + Create a new entity list, or get the entity list metadata. + + :param client: Client instance to use for API calls. + :param entity_list_name: Name of the entity list. + :param entity_props: Properties to add to the entity list. + """ + try: + entity_list = client.session.response_or_error( + method="POST", + url=client.session.urlformat( + "projects/{pid}/datasets", + pid=client.project_id, + ), + logger=log, + json={"name": entity_list_name}, + ) + except PyODKError: + entity_list = client.session.get( + url=client.session.urlformat( + "projects/{pid}/datasets/{eln}", + pid=client.project_id, + eln=entity_list_name, + ), + ) + try: + for prop in entity_props: + client.session.response_or_error( + method="GET", + url=client.session.urlformat( + "projects/{pid}/datasets/{eln}/properties", + pid=client.project_id, + eln=entity_list_name, + ), + logger=log, + json={"name": prop}, + ) + except PyODKError: + pass + return EntityList(**entity_list.json())