From 8439ddef87e73664893c2f6b949712546d225903 Mon Sep 17 00:00:00 2001 From: Adrian Hannah Date: Mon, 3 Aug 2026 10:41:10 -0400 Subject: [PATCH 1/2] fix: uppercase JAMF object IDs "normalize" object IDs at the collector level. ref: BED-8973 --- src/openhound_jamf/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openhound_jamf/graph.py b/src/openhound_jamf/graph.py index 05c412e..7a9688c 100644 --- a/src/openhound_jamf/graph.py +++ b/src/openhound_jamf/graph.py @@ -30,7 +30,7 @@ def guid( node_type: str, tenant: str, ) -> str: - return BaseNode.guid(id, node_type, tenant) + return BaseNode.guid(id, node_type, tenant).upper() def __post_init__(self): self.id = self.guid( From b7a01d2e48644e0f44260fe17d1e63e748edb4f7 Mon Sep 17 00:00:00 2001 From: Adrian Hannah Date: Mon, 3 Aug 2026 10:48:18 -0400 Subject: [PATCH 2/2] test: cover uppercase JAMF object IDs Add unit coverage for uppercase object ID ref: BED-8973 --- tests/test_graph.py | 59 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/test_graph.py diff --git a/tests/test_graph.py b/tests/test_graph.py new file mode 100644 index 0000000..1183913 --- /dev/null +++ b/tests/test_graph.py @@ -0,0 +1,59 @@ +from typing import cast +from unittest.mock import MagicMock + +from openhound.core.models.entries_dataclass import EdgePath +from openhound.core.models.entries_dataclass import Node as BaseNode + +from openhound_jamf.graph import JAMFNode +from openhound_jamf.kinds import edges as ek +from openhound_jamf.kinds import nodes as nk +from openhound_jamf.lookup import JamfLookup +from openhound_jamf.models.account import Account + + +TENANT_ID = "Tenant.Example.com" + + +def _make_account() -> Account: + lookup = MagicMock(spec=JamfLookup) + lookup.tenant_id.return_value = TENANT_ID + account = Account( + id=7, + name="alice", + full_name="Alice Smith", + email="Stored.User@Example.com", + enabled="Enabled", + access_level="Full Access", + privilege_set="Administrator", + directory_user=False, + ) + account._lookup = cast(JamfLookup, lookup) + return account + + +def test_guid_uppercases_existing_uuid(): + """Keep existing graph identities when ingest stops normalizing ObjectIDs.""" + assert ( + JAMFNode.guid("mixedCaseSourceId", nk.ACCOUNT, TENANT_ID) + == BaseNode.guid("mixedCaseSourceId", nk.ACCOUNT, TENANT_ID).upper() + ) + + +def test_node_id_is_uppercase(): + """Ensure emitted JAMF nodes satisfy the uppercase ObjectID requirement.""" + node_id = _make_account().as_node.id + + assert node_id == node_id.upper() + + +def test_id_edge_paths_reference_uppercase_node_ids(): + """Ensure uppercasing ObjectIDs does not disconnect edge endpoints.""" + account = _make_account() + admin_edge = next(edge for edge in account.edges if edge.kind == ek.ADMIN_TO) + edge_start = cast(EdgePath, admin_edge.start) + edge_end = cast(EdgePath, admin_edge.end) + + assert (edge_start.value, edge_end.value) == ( + account.as_node.id, + account.tenant_node_id, + )