diff --git a/PYEGERIA_ISSUES.md b/PYEGERIA_ISSUES.md index d43783c0..3554de94 100644 --- a/PYEGERIA_ISSUES.md +++ b/PYEGERIA_ISSUES.md @@ -1316,6 +1316,55 @@ changed beyond that one status correction and this note. --- +### ISSUE-116: `get_my_actors` always failed and `get_my_user_identities`/`get_my_roles` always returned nothing — same type-filter defect as ISSUE-115, hidden by tests that swallowed errors + +**Layer:** Pyegeria · **Status:** fixed 2026-10-02 · **Found:** 2026-10-02 +(removing the `except PyegeriaException: print(...)` pattern from +`tests/functional-tests/test_my_profile.py` after ISSUE-115). + +**What:** like `get_my_resources`, three more `MyProfile` queries passed a +rendering hint to `_async_get_results_body_request`, which sent it as +`metadataElementTypeName`. None of the endpoints' `.http` examples send a type +filter. Checked live for erinoverview, garygeeke and peterprofile, with and +without the filter: + +| Method | `_type` | With the filter | Without | +|---|---|---|---| +| `get_my_actors` | `ActorProfile` | `OMAG-REPOSITORY-HANDLER-404-001` "... is of type UserIdentity rather than type ActorProfile" | UserIdentity + PersonRole + GovernanceRole elements | +| `get_my_user_identities` | `UserIdentity` | no elements (even though every result is a UserIdentity) | the user's UserIdentity | +| `get_my_roles` | `GovernanceRole` | no elements (even for users with GovernanceRoles) | PersonRole + GovernanceRole elements | + +The last two failed silently: "No elements found" looked like a legitimate +empty result. + +**Fixed:** `filter_results_by_type=False` on all three, as for ISSUE-115. +`tests/micro-tests/test_my_profile_no_type_filter.py` (renamed from +`test_my_profile_get_my_resources_body.py`) now covers all four endpoints +offline. + +**Test file rewritten:** `tests/functional-tests/test_my_profile.py` no longer +catches `PyegeriaException`, and asserts non-empty results where every demo +persona has data. Besides this defect, the old file's swallowed errors hid: +- three tests calling `MyProfile.get_to_do`/`get_to_dos_by_type`/`update_to_do`, + which no longer exist (`AttributeError`, caught and printed). Replaced by + to-do lifecycle tests using `create_my_todo`, `get_asset_by_guid`, + `update_asset` and `delete_asset`. +- two tests using hard-coded actor/sponsor GUIDs absent from a fresh quick start. + They now create their own to-do/action and query by the user's profile GUID. +- `test_create_my_todo`'s teardown (ISSUE-45) never deleted anything: its + `{"class": "OpenMetadataDeleteRequestBody"}` body fails pyegeria validation, + so every run leaked a ToDo. To-dos are now deleted with `delete_asset`. + +**Noted, not changed:** `AssetMaker.get_assigned_actions`/ +`get_actions_for_sponsor`/`get_actions_for_requester` default +`activity_status_list` to `["IN_PROGRESS"]`, so a newly created (`REQUESTED`) +action is not returned unless callers pass the statuses explicitly. The `.http` +example uses `["REQUESTED", "WAITING", "IN_PROGRESS"]`, as does `MyProfile`'s +own `get_my_assigned_actions`. Changing the default would change behaviour for +existing callers, so the tests pass the list explicitly instead. + +--- + ### ISSUE-115: `MyProfile.get_my_resources` always failed — its rendering hint `"Resource"` was sent as `metadataElementTypeName` **Layer:** Pyegeria · **Status:** fixed 2026-10-02 · **Found:** 2026-10-02 @@ -1341,8 +1390,10 @@ env", so the 400 never failed the test. rendering hint only — the same fix as `get_collection_members` (`collection_manager.py`). Verified live: Gary Geeke's profile now returns its `ResourceList`-linked `Bookmarks::garygeeke` collection in JSON, DICT and LIST -formats. Covered by `tests/micro-tests/test_my_profile_get_my_resources_body.py`, -which captures the outgoing body (fails without the fix). +formats. Covered by `tests/micro-tests/test_my_profile_no_type_filter.py` +(originally `test_my_profile_get_my_resources_body.py`), which captures the +outgoing body (fails without the fix). Removing the swallowed-error pattern +from the functional tests then found three more instances: ISSUE-116. --- diff --git a/pyegeria/omvs/my_profile.py b/pyegeria/omvs/my_profile.py index 0a10ed51..0fcaea54 100644 --- a/pyegeria/omvs/my_profile.py +++ b/pyegeria/omvs/my_profile.py @@ -672,6 +672,11 @@ async def _async_get_my_actors( ) -> list | str: """Return the list of actors linked to the user's profile. Async version.""" url = f"{self.my_profile_command_root}/actors" + # filter_results_by_type=False: "ActorProfile" is only a rendering hint. The + # endpoint's .http example sends no type filter, and sending one made every + # call fail (OMAG-REPOSITORY-HANDLER-404-001 "... is of type UserIdentity + # rather than type ActorProfile"); the actors linked to a profile are user + # identities and roles, not ActorProfiles. ISSUE-116. return await self._async_get_results_body_request( url, _type="ActorProfile", @@ -682,6 +687,7 @@ async def _async_get_my_actors( output_format=output_format, report_spec=report_spec, body=body, + filter_results_by_type=False, **kwargs, ) @@ -723,6 +729,10 @@ async def _async_get_my_user_identities( ) -> list | str: """Return the list of user identities linked to the user's profile. Async version.""" url = f"{self.my_profile_command_root}/actors/user-identities" + # filter_results_by_type=False: "UserIdentity" is only a rendering hint. The + # endpoint's .http example sends no type filter, and with one the server + # silently returned no elements, even though every result is a + # UserIdentity. ISSUE-116. return await self._async_get_results_body_request( url, _type="UserIdentity", @@ -733,6 +743,7 @@ async def _async_get_my_user_identities( output_format=output_format, report_spec=report_spec, body=body, + filter_results_by_type=False, **kwargs, ) @@ -774,6 +785,10 @@ async def _async_get_my_roles( ) -> list | str: """Return the list of assigned roles linked to the user's profile. Async version.""" url = f"{self.my_profile_command_root}/actors/assigned-roles" + # filter_results_by_type=False: "GovernanceRole" is only a rendering hint. The + # endpoint's .http example sends no type filter, and with one the server + # silently returned no elements, even for users with GovernanceRoles. The + # roles linked to a profile include PersonRoles too. ISSUE-116. return await self._async_get_results_body_request( url, _type="GovernanceRole", @@ -784,6 +799,7 @@ async def _async_get_my_roles( output_format=output_format, report_spec=report_spec, body=body, + filter_results_by_type=False, **kwargs, ) diff --git a/tests/functional-tests/test_my_profile.py b/tests/functional-tests/test_my_profile.py index 72b47495..4907fcca 100644 --- a/tests/functional-tests/test_my_profile.py +++ b/tests/functional-tests/test_my_profile.py @@ -1,298 +1,219 @@ +""" +Functional tests for the MyProfile OMVS client against a live Egeria (e.g. the +egeria-workspaces quick start). Run explicitly; not part of the CI unit suite: + + pytest tests/functional-tests/test_my_profile.py + +These tests deliberately do not catch PyegeriaException: an Egeria error fails the +test. They used to catch it and print "failed as expected or due to env", which hid +real defects (ISSUE-115, ISSUE-116) and calls to methods that no longer exist. + +Some tests write to Egeria (blog/activity entries, to-dos, an action). To-dos and +actions are deleted again; activity entries are not, so use a disposable instance. +""" import json -import time from datetime import datetime + import pytest -import asyncio -from pyegeria.omvs.my_profile import MyProfile -from pyegeria.omvs.metadata_expert import MetadataExpert -from pyegeria.models import NewElementRequestBody -from pyegeria.core._exceptions import PyegeriaException, print_basic_exception -from rich.markdown import Markdown -from pyegeria import EgeriaTech from rich.console import Console -console = Console(width = 150) +from rich.markdown import Markdown + +from pyegeria import NO_ELEMENTS_FOUND +from pyegeria.omvs.my_profile import MyProfile + +console = Console(width=150) VIEW_SERVER = "qs-view-server" PLATFORM_URL = "https://localhost:9443" -# USER_ID = "jacquardnpa" USER_ID = "erinoverview" USER_PWD = "secret" +# AssetMaker's action queries default to ["IN_PROGRESS"]; a new to-do is REQUESTED +OPEN_STATUSES = ["REQUESTED", "WAITING", "IN_PROGRESS"] + + +def show(label: str, response) -> None: + print(f"\n{label}: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\n{label}: {response}") + + +def guids(response) -> list[str]: + """GUIDs of the elements in a JSON result; [] for the "No elements found" string.""" + if isinstance(response, list): + return [element["elementHeader"]["guid"] for element in response] + assert response == NO_ELEMENTS_FOUND, f"Unexpected response: {response}" + return [] + + +def assert_list_or_none_found(response) -> None: + assert isinstance(response, list) or response == NO_ELEMENTS_FOUND, f"Unexpected response: {response}" + + class TestMyProfile: @pytest.fixture def profile_client(self): client = MyProfile(VIEW_SERVER, PLATFORM_URL, USER_ID, USER_PWD) - # Try to get a token if needed, though ServerClient usually handles it - try: - client.create_egeria_bearer_token(USER_ID, USER_PWD) - except Exception: - pass - return client + client.create_egeria_bearer_token(USER_ID, USER_PWD) + yield client + client.close_session() - def test_get_my_profile(self, profile_client): - try: - profile_client = MyProfile(VIEW_SERVER, PLATFORM_URL, USER_ID, USER_PWD) - token = profile_client.create_egeria_bearer_token(USER_ID, USER_PWD) + @pytest.fixture + def profile_guid(self, profile_client) -> str: + profile = profile_client.get_my_profile(output_format="JSON", graph_query_depth=0) + if isinstance(profile, list): + profile = profile[0] + return profile["elementHeader"]["guid"] - profile = profile_client.get_my_profile(output_format="JSON", report_spec="My-User-MD", graph_query_depth=10) - if isinstance(profile, dict | list): - print(json.dumps(profile, indent=2)) - if isinstance(profile, str): - console.print(Markdown(profile)) + @pytest.fixture + def todo_guid(self, profile_client): + """A to-do created for the test user, deleted again afterwards.""" + guid = profile_client.create_my_todo(f"test-todo-{datetime.now().isoformat()}", "REQUESTED", + "Functional test to-do", "Testing MyProfile", 3) + assert isinstance(guid, str) and guid + yield guid + profile_client.delete_asset(guid) + + # --- The user's profile and what is linked to it --- - except Exception as e: - print_basic_exception(e) - pytest.fail(f"get_my_profile failed with unexpected exception: {e}") + def test_get_my_profile(self, profile_client): + profile = profile_client.get_my_profile(output_format="JSON", report_spec="My-User-MD", graph_query_depth=10) + assert profile and isinstance(profile, (dict, list)) + if isinstance(profile, str): + console.print(Markdown(profile)) + else: + print(json.dumps(profile, indent=2)) def test_get_my_actors(self, profile_client): - try: - response = profile_client.get_my_actors() - assert isinstance(response, (list, dict, str)) - print(f"\nRetrieved actors: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved actors: {response}") - except PyegeriaException as e: - print(f"get_my_actors failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_actors failed with unexpected exception: {e}") + # Every demo persona has a user identity and roles linked to their profile + response = profile_client.get_my_actors() + show("Retrieved actors", response) + assert isinstance(response, list) and response def test_get_my_user_identities(self, profile_client): - try: - response = profile_client.get_my_user_identities(output_format="DICT", report_spec="Referenceable") - assert isinstance(response, (list, dict, str)) - print(f"\nRetrieved user identities: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved user identities: {response}") - except PyegeriaException as e: - print(f"get_my_user_identities failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_user_identities failed with unexpected exception: {e}") + response = profile_client.get_my_user_identities(output_format="JSON") + show("Retrieved user identities", response) + assert isinstance(response, list) and response + assert {e["elementHeader"]["type"]["typeName"] for e in response} == {"UserIdentity"} + + def test_get_my_user_identities_as_dict(self, profile_client): + response = profile_client.get_my_user_identities(output_format="DICT", report_spec="Referenceable") + show("Retrieved user identities", response) + assert isinstance(response, list) and response def test_get_my_roles(self, profile_client): - try: - response = profile_client.get_my_roles() - assert isinstance(response, (list, dict, str)) - print(f"\nRetrieved roles: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved roles: {response}") - except PyegeriaException as e: - print(f"get_my_roles failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_roles failed with unexpected exception: {e}") + response = profile_client.get_my_roles() + show("Retrieved roles", response) + assert isinstance(response, list) and response def test_get_my_resources(self, profile_client): - try: - response = profile_client.get_my_resources() - assert isinstance(response, (list, dict, str)) - print(f"\nRetrieved resources: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved resources: {response}") - except PyegeriaException as e: - print(f"get_my_resources failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_resources failed with unexpected exception: {e}") + # Resources (e.g. a bookmarks collection) are optional, so none is fine + response = profile_client.get_my_resources() + show("Retrieved resources", response) + assert_list_or_none_found(response) def test_get_my_assigned_actions(self, profile_client): - try: - response = profile_client.get_my_assigned_actions() - assert isinstance(response, (list, dict, str)) - print(f"\nRetrieved assigned actions: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved assigned actions: {response}") - except PyegeriaException as e: - print(f"get_my_assigned_actions failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_assigned_actions failed with unexpected exception: {e}") + response = profile_client.get_my_assigned_actions() + show("Retrieved assigned actions", response) + assert_list_or_none_found(response) def test_get_my_sponsored_actions(self, profile_client): - try: - response = profile_client.get_my_sponsored_actions() - assert isinstance(response, (list, dict, str)) - print(f"\nRetrieved sponsored actions: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved sponsored actions: {response}") - except PyegeriaException as e: - print(f"get_my_sponsored_actions failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_sponsored_actions failed with unexpected exception: {e}") + response = profile_client.get_my_sponsored_actions() + show("Retrieved sponsored actions", response) + assert_list_or_none_found(response) def test_get_my_requested_actions(self, profile_client): - try: - response = profile_client.get_my_requested_actions() - assert isinstance(response, (list, dict, str)) - print(f"\nRetrieved requested actions: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved requested actions: {response}") - except PyegeriaException as e: - print(f"get_my_requested_actions failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_requested_actions failed with unexpected exception: {e}") - - def test_activity_logging(self, profile_client): - try: - # body = { - # "class": "NewAttachmentRequestBody", - # "properties": { - # "class": "NotificationProperties", - # "qualifiedName": "Activity::TestActivity2", - # "displayName": "A new Test Activity", - # "situation": "Testing activity logging", - # "description": "This is a test notification", - # } - # } - # guid = profile_client.log_my_activity(body=body) - # assert isinstance(guid, str) - # print(f"\nLogged activity GUID: {guid}") - # body = { - # "class": "NewAttachmentRequestBody", - # "properties": { - # "class": "NotificationProperties", - # "qualifiedName": "JournalActivity::TestActivity2", - # "displayName": "A new Test Activity", - # "situation": "Testing activity logging", - # "description": "This is a test notification", - # } - # } - # guid = profile_client.journal_my_activity(body=body) - # assert isinstance(guid, str) - # print(f"\nJournaled activity GUID: {guid}") - body = { - "class": "NewAttachmentRequestBody", - "properties": { - "class": "NotificationProperties", - "qualifiedName": f"Blog::Blog-{datetime.now().isoformat()}", - "displayName": "A new Test Activity", - "situation": "Testing activity logging", - "description": "This is a test notification", - } + response = profile_client.get_my_requested_actions() + show("Retrieved requested actions", response) + assert_list_or_none_found(response) + + # --- Activity entries (not cleaned up) --- + + def test_blog_my_activity(self, profile_client): + body = { + "class": "NewAttachmentRequestBody", + "properties": { + "class": "NotificationProperties", + "qualifiedName": f"Blog::Blog-{datetime.now().isoformat()}", + "displayName": "A new Test Activity", + "situation": "Testing activity logging", + "description": "This is a test notification", } - response = profile_client.blog_my_activity(body=body) - assert isinstance(response, str) - print(f"\nBlogged activity GUID: {response}") + } + response = profile_client.blog_my_activity(body=body) + show("Blogged activity GUID", response) + assert isinstance(response, str) and response - except PyegeriaException as e: - print_basic_exception(e) - pytest.fail(f"activity logging failed with PyegeriaException: {e}") - except Exception as e: - pytest.fail(f"activity logging failed with unexpected exception: {e}") + def test_log_my_activity(self, profile_client): + body = { + "class": "NewAttachmentRequestBody", + "properties": { + "class": "ActivityEntryProperties", + "qualifiedName": f"TestActivity-{datetime.now().isoformat()}", + "displayName": "A new Test Activity", + "situation": "Testing activity logging", + "description": "This is a test notification", + } + } + response = profile_client.log_my_activity(body=body) + show("Logged activity GUID", response) + assert isinstance(response, str) and response def test_get_my_entries(self, profile_client): - try: - response = profile_client.get_my_entries() - assert isinstance(response, (list, dict, str)) - if isinstance(response, list | dict): - print(f"\nRetrieved to-dos: {json.dumps(response, indent=2)}" if isinstance(response, (list, - dict)) else f"\nRetrieved to-dos: {response}") - else: - print(f"\nRetrieved to-dos: {response}") - except PyegeriaException as e: - print(f"get_my_to_dos failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_to_dos failed with unexpected exception: {e}") - - def test_get_to_dos(self, profile_client): - try: - response = profile_client.get_my_to_dos(output_format="JSON",report_spec="My-User-ToDos") - assert isinstance(response, (list, dict, str)) - if isinstance(response, list|dict): - print(f"\nRetrieved to-dos: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nRetrieved to-dos: {response}") - else: - print(f"\nRetrieved to-dos: {response}") - except PyegeriaException as e: - print(f"get_my_to_dos failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_to_dos failed with unexpected exception: {e}") - - def test_create_my_todo(self,profile_client): - # ISSUE-45: this test used to create a real ToDo on the live server and - # never delete it, silently accumulating orphaned ToDo entities across - # repeated runs. Teardown added below, matching the delete pattern used - # elsewhere in this file's own live verification steps. - response = None - try: - - response = profile_client.create_my_todo("do-my-backup","REQUESTED","do-backup", - "Weekly backup needed",3) - - assert isinstance(response, (list, dict, str)) - print(f"\nCreated to-do: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nCreated to-do: {response}") - except PyegeriaException as e: - print(f"create_my_todo failed as expected or due to env: {e}") - print_basic_exception(e) - except Exception as e: - pytest.fail(f"create_my_todo failed with unexpected exception: {e}") + response = profile_client.get_my_entries() + show("Retrieved entries", response) + assert isinstance(response, list) + + # --- To-dos and actions (created and deleted by each test) --- + + def test_get_my_to_dos_includes_new_todo(self, profile_client, todo_guid): + response = profile_client.get_my_to_dos(output_format="JSON", report_spec="My-User-ToDos") + assert todo_guid in guids(response) + + def test_get_todo_by_guid(self, profile_client, todo_guid): + todo = profile_client.get_asset_by_guid(todo_guid, graph_query_depth=0) + assert todo["elementHeader"]["type"]["typeName"] == "ToDo" + assert todo["properties"]["activityStatus"] == "REQUESTED" + assert todo["properties"]["priority"] == 3 + + def test_update_todo_status(self, profile_client, todo_guid): + profile_client.update_asset(todo_guid, { + "class": "UpdateElementRequestBody", + "mergeUpdate": True, + "properties": {"class": "ToDoProperties", "activityStatus": "WAITING", "priority": 1}, + }) + todo = profile_client.get_asset_by_guid(todo_guid, graph_query_depth=0) + assert todo["properties"]["activityStatus"] == "WAITING" + assert todo["properties"]["priority"] == 1 + + def test_new_todo_is_assigned_to_and_requested_by_me(self, profile_client, profile_guid, todo_guid): + # create_my_todo makes the user's profile both the assignee and the originator + assigned = profile_client.get_assigned_actions(profile_guid, activity_status_list=OPEN_STATUSES) + requested = profile_client.get_actions_for_requester(profile_guid, activity_status_list=OPEN_STATUSES) + assert todo_guid in guids(assigned) + assert todo_guid in guids(requested) + + def test_get_actions_for_sponsor(self, profile_client, profile_guid): + action_guid = profile_client.create_action({ + "class": "ActionRequestBody", + "isOwnAnchor": True, + "properties": { + "class": "ToDoProperties", + "qualifiedName": f"Todo::sponsored-test-{datetime.now().isoformat()}", + "displayName": "Sponsored functional test to-do", + "activityStatus": "REQUESTED", + }, + "actionSponsorGUID": profile_guid, + }) + try: + sponsored = profile_client.get_actions_for_sponsor(profile_guid, activity_status_list=OPEN_STATUSES) + assert action_guid in guids(sponsored) finally: - if isinstance(response, str) and response: - try: - me_client = MetadataExpert(VIEW_SERVER, PLATFORM_URL, USER_ID, USER_PWD) - me_client.create_egeria_bearer_token(USER_ID, USER_PWD) - me_client.delete_metadata_element( - response, body={"class": "OpenMetadataDeleteRequestBody"} - ) - print(f"\nDeleted test to-do {response}") - except Exception as e: - print(f"\nTeardown: failed to delete test to-do {response}: {e}") - - def test_get_assigned_actions_for_actor(self, profile_client): - try: - actor_guid = "9a304921-6844-4ea8-b513-76620ca15a99" # peter - response = profile_client.get_assigned_actions(actor_guid) - assert isinstance(response, (list, dict, str)) - print(f"\nAssigned actions: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nAssigned actions: {response}") - except PyegeriaException as e: - print(f"get_assigned_actions failed as expected or due to env: {e}") - except Exception as e: - print(f"get_assigned_actions raised: {type(e).__name__}: {e}") - - def test_get_actions_for_sponsor(self, profile_client): - try: - element_guid = "a588fb08-ae09-4415-bd5d-991882ceacba" - response = profile_client.get_actions_for_sponsor(element_guid) - assert isinstance(response, (list, dict, str)) - print(f"\nSponsored actions: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nSponsored actions: {response}") - except PyegeriaException as e: - print(f"get_actions_for_sponsor failed as expected or due to env: {e}") - except Exception as e: - print(f"get_actions_for_sponsor raised: {type(e).__name__}: {e}") - - def test_get_to_do_by_guid(self, profile_client): - try: - todo_guid = "91f6712a-a5ee-4c05-ac18-f7fd996c87ca" - response = profile_client.get_to_do(todo_guid) - assert isinstance(response, (list, dict, str)) - print(f"\nTo-do: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nTo-do: {response}") - except PyegeriaException as e: - print(f"get_to_do failed as expected or due to env: {e}") - except Exception as e: - print(f"get_to_do raised: {type(e).__name__}: {e}") - - def test_get_to_dos_by_type(self, profile_client): - try: - response = profile_client.get_to_dos_by_type("holiday", "OPEN") - assert isinstance(response, (list, dict, str)) - print(f"\nTo-dos by type: {json.dumps(response, indent=2)}" if isinstance(response, (list, dict)) else f"\nTo-dos by type: {response}") - except PyegeriaException as e: - print(f"get_to_dos_by_type failed as expected or due to env: {e}") - except Exception as e: - print(f"get_to_dos_by_type raised: {type(e).__name__}: {e}") - - def test_update_todo_status(self, profile_client): - try: - todo_guid = "d6d4f540-a28f-4312-9c24-d3774b3f06a1" - body = {"class": "ToDoProperties", "toDoStatus": "WAITING", "priority": 1} - profile_client.update_to_do(todo_guid, body, is_merge_update=True) - print(f"\nUpdated to-do {todo_guid} status to WAITING.") - except PyegeriaException as e: - print(f"update_to_do failed as expected or due to env: {e}") - except Exception as e: - print(f"update_to_do raised: {type(e).__name__}: {e}") - - def test_log_my_activity(self, profile_client): - try: - body = { - "class": "NewAttachmentRequestBody", - "properties": { - "class": "ActivityEntryProperties", - "qualifiedName": f"TestActivity-{datetime.now().isoformat()}", - "displayName": "A new Test Activity", - "situation": "Testing activity logging", - "description": "This is a test notification", - } - } - response = profile_client.log_my_activity(body=body) - assert isinstance(response, str) - print(f"\nLogged activity GUID: {response}") - except PyegeriaException as e: - print(f"get_my_to_dos failed as expected or due to env: {e}") - except Exception as e: - pytest.fail(f"get_my_to_dos failed with unexpected exception: {e}") + profile_client.delete_asset(action_guid) + def test_deleted_todo_is_gone(self, profile_client): + guid = profile_client.create_my_todo(f"test-todo-{datetime.now().isoformat()}", "REQUESTED") + profile_client.delete_asset(guid) + response = profile_client.get_my_to_dos(output_format="JSON") + assert guid not in guids(response) if __name__ == "__main__": diff --git a/tests/micro-tests/test_my_profile_get_my_resources_body.py b/tests/micro-tests/test_my_profile_no_type_filter.py similarity index 50% rename from tests/micro-tests/test_my_profile_get_my_resources_body.py rename to tests/micro-tests/test_my_profile_no_type_filter.py index 49a491ef..c1bea642 100644 --- a/tests/micro-tests/test_my_profile_get_my_resources_body.py +++ b/tests/micro-tests/test_my_profile_no_type_filter.py @@ -1,18 +1,33 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the ODPi Egeria project. """ -ISSUE-115: MyProfile.get_my_resources sent its rendering hint "Resource" as -metadataElementTypeName, which isn't an Egeria type, so every call failed with -OMAG-COMMON-400-018. The endpoint's .http example (Egeria-api-my-profile.http, -getMyResources) sends a plain ResultsRequestBody with no type filter. +ISSUE-115 / ISSUE-116: four MyProfile "linked to my profile" queries sent their +rendering hint (the _type passed to _async_get_results_body_request) as +metadataElementTypeName. None of these endpoints' .http examples +(Egeria-api-my-profile.http) send a type filter, and with one the server either +rejected the call or silently returned nothing: + +- get_my_resources "Resource" -> OMAG-COMMON-400-018, not an Egeria type +- get_my_actors "ActorProfile" -> OMAG-REPOSITORY-HANDLER-404-001 +- get_my_user_identities "UserIdentity" -> "No elements found" for every user +- get_my_roles "GovernanceRole" -> "No elements found" for every user Captures the outgoing request instead of calling a server. """ import json from unittest.mock import AsyncMock, MagicMock, patch +import pytest + from pyegeria import MyProfile +ENDPOINTS = [ + ("_async_get_my_resources", "/my-profile/assigned-resources?includeUserIds=true&includeRoles=true"), + ("_async_get_my_actors", "/my-profile/actors"), + ("_async_get_my_user_identities", "/my-profile/actors/user-identities"), + ("_async_get_my_roles", "/my-profile/actors/assigned-roles"), +] + def _client_capturing_requests() -> tuple[MyProfile, AsyncMock]: # The constructor pings the platform (check_connection()) before any mock can be @@ -25,24 +40,26 @@ def _client_capturing_requests() -> tuple[MyProfile, AsyncMock]: return client, client._async_make_request -async def test_get_my_resources_sends_no_type_filter(): +@pytest.mark.parametrize("method, path", ENDPOINTS) +async def test_sends_no_type_filter(method, path): client, make_request = _client_capturing_requests() - result = await client._async_get_my_resources(output_format="JSON") + result = await getattr(client, method)(output_format="JSON") verb, url, json_body = make_request.call_args.args body = json.loads(json_body) assert verb == "POST" - assert url.endswith("/api/open-metadata/my-profile/assigned-resources?includeUserIds=true&includeRoles=true") + assert url.endswith("/api/open-metadata" + path) assert body["class"] == "ResultsRequestBody" assert "metadataElementTypeName" not in body assert result == [{"elementHeader": {"guid": "g1"}, "properties": {}}] -async def test_get_my_resources_caller_body_is_sent_unchanged(): +@pytest.mark.parametrize("method, path", ENDPOINTS) +async def test_caller_body_is_sent_unchanged(method, path): client, make_request = _client_capturing_requests() - await client._async_get_my_resources(body={"class": "ResultsRequestBody", "pageSize": 5}) + await getattr(client, method)(body={"class": "ResultsRequestBody", "pageSize": 5}) body = json.loads(make_request.call_args.args[2]) assert body["pageSize"] == 5