Skip to content

Commit 4c7d314

Browse files
authored
test: track test_sync_members.py, fix stale assertion for known_new optimization (#289)
Removed /tests/micro-tests/test_sync_members.py from .gitignore. It was excluded 5 months ago (b04bd89, "v6-prep...") bundled with genuinely scratch exclusions (.ai-sandbox/, convert_defaults.py) under "exclude local scratch/test files" -- but its content is no longer scratch: it's the only test anywhere in the suite exercising TermProcessor's collection-sync logic (sync_members/_sync_term_memberships). Everything else that mentions sync_members is a single incidental comment in test_supply_chain_processor.py, not real coverage. Being gitignored meant nobody but this checkout ever benefited from it -- proven directly: it's exactly the test that would have caught (and, once fixed here, now correctly validates) the known_new=True optimization PR #287 (perf(dr-egeria): skip unnecessary as-is relationship fetches in sync_members) landed in this same code path. Also fixed the one assertion the optimization made stale: for a just-created term (known_new=True), _sync_term_memberships passes a plain empty set instead of the lazy _async_get_related_elements fetcher, since a brand-new element can't have existing CollectionMembership relationships -- deliberately and correctly skipping that call. The test previously asserted the old fetch still happened (assert_called_with(...)); now asserts assert_not_called(), which actually strengthens it as a regression guard for the optimization itself rather than just checking the resulting add/remove calls. Full pytest tests/micro-tests/ green. Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
1 parent 936d5ab commit 4c7d314

2 files changed

Lines changed: 158 additions & 1 deletion

File tree

‎.gitignore‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ dmypy.json
177177
/pyegeria/http clients/
178178
/outbox/
179179
/.ai-sandbox/
180-
/tests/micro-tests/test_sync_members.py
181180
/convert_defaults.py
182181
/sample-data/egeria-outbox/
183182
/.ai/
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import asyncio
2+
from unittest.mock import AsyncMock, MagicMock
3+
4+
from md_processing.v2.glossary import TermProcessor
5+
from md_processing.v2.extraction import DrECommand
6+
7+
async def test_glossary_term_processing():
8+
# Mock Egeria Client
9+
client = AsyncMock()
10+
11+
# Mock get_related_elements to return nothing initially
12+
client._async_get_related_elements.return_value = []
13+
14+
# Mock create/update calls
15+
client._async_create_glossary_term.return_value = "00000000-0000-0000-0000-000000000001"
16+
client._async_update_glossary_term.return_value = None
17+
18+
# 1. Test Create with Multiple Glossaries and Folders
19+
cmd = DrECommand(
20+
verb="Create",
21+
object_type="Glossary Term",
22+
attributes={
23+
"Display Name": {"value": "TestTerm"},
24+
"Qualified Name": {"value": "QN123"},
25+
"Glossary Name": {"value": ["GlossaryA", "GlossaryB"], "guid_list": ["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"]},
26+
"Folders": {"value": ["Folder1"], "guid_list": ["f0000000-0000-0000-0000-000000000001"]}
27+
},
28+
raw_block="Create Glossary Term..."
29+
)
30+
31+
processor = TermProcessor(client, cmd)
32+
processor.parsed_output = {
33+
"attributes": cmd.attributes,
34+
"qualified_name": cmd.attributes["Qualified Name"]["value"],
35+
"valid": True
36+
}
37+
processor.render_result_markdown = AsyncMock(return_value="# Result")
38+
39+
await processor.apply_changes()
40+
41+
# Verify create called
42+
client._async_create_glossary_term.assert_called_once()
43+
44+
# A brand-new term (known_new=True in _sync_term_memberships,
45+
# md_processing/v2/glossary.py) cannot have any existing
46+
# CollectionMembership relationships, so the expensive "what does this
47+
# element currently belong to" fetch is deliberately skipped entirely
48+
# for Create -- confirm the optimization actually skips it, not just
49+
# that sync still produces the right adds (below).
50+
client._async_get_related_elements.assert_not_called()
51+
52+
# Verify add_to_collection called for each
53+
add_calls = [call.args for call in client._async_add_to_collection.call_args_list]
54+
assert ("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "00000000-0000-0000-0000-000000000001") in add_calls
55+
assert ("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "00000000-0000-0000-0000-000000000001") in add_calls
56+
assert ("f0000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000001") in add_calls
57+
58+
print("Test Create: PASSED")
59+
60+
# 2. Test Update with Merge Update = False (Synchronize)
61+
client.reset_mock()
62+
# Now term is in A, B, 1
63+
client._async_get_related_elements.return_value = [
64+
{"elementHeader": {"guid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}},
65+
{"elementHeader": {"guid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"}},
66+
{"elementHeader": {"guid": "f0000000-0000-0000-0000-000000000001"}}
67+
]
68+
69+
cmd_update = DrECommand(
70+
verb="Update",
71+
object_type="Glossary Term",
72+
attributes={
73+
"Display Name": {"value": "TestTerm"},
74+
"Qualified Name": {"value": "QN123"},
75+
"Glossary Name": {"value": ["GlossaryA", "GlossaryC"], "guid_list": ["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "cccccccc-cccc-cccc-cccc-cccccccccccc"]},
76+
"Merge Update": {"value": False}
77+
},
78+
raw_block="Update Glossary Term..."
79+
)
80+
81+
processor_update = TermProcessor(client, cmd_update)
82+
processor_update.parsed_output = {
83+
"attributes": cmd_update.attributes,
84+
"qualified_name": cmd_update.attributes["Qualified Name"]["value"],
85+
"guid": "00000000-0000-0000-0000-000000000001",
86+
"valid": True
87+
}
88+
processor_update.render_result_markdown = AsyncMock(return_value="# Result")
89+
90+
await processor_update.apply_changes()
91+
92+
# Verify update called with correct mergeUpdate flag
93+
client._async_update_glossary_term.assert_called_once()
94+
update_body = client._async_update_glossary_term.call_args.args[1]
95+
assert update_body["mergeUpdate"] is False
96+
97+
# Should ADD C and REMOVE B and 1
98+
add_calls = [call.args for call in client._async_add_to_collection.call_args_list]
99+
remove_calls = [call.args for call in client._async_remove_from_collection.call_args_list]
100+
101+
assert ("cccccccc-cccc-cccc-cccc-cccccccccccc", "00000000-0000-0000-0000-000000000001") in add_calls
102+
assert ("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "00000000-0000-0000-0000-000000000001") in remove_calls
103+
assert ("f0000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000001") in remove_calls
104+
assert ("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "00000000-0000-0000-0000-000000000001") not in add_calls # Already there
105+
106+
print("Test Update (Merge=False): PASSED")
107+
108+
# 3. Test Update with Merge Update = True (Additive)
109+
client.reset_mock()
110+
client._async_get_related_elements.return_value = [
111+
{"elementHeader": {"guid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}}
112+
]
113+
114+
cmd_merge = DrECommand(
115+
verb="Update",
116+
object_type="Glossary Term",
117+
attributes={
118+
"Display Name": {"value": "TestTerm"},
119+
"Qualified Name": {"value": "QN123"},
120+
"Glossary Name": {"value": ["GlossaryB"], "guid_list": ["bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"]},
121+
"Merge Update": {"value": True}
122+
},
123+
raw_block="Update Glossary Term..."
124+
)
125+
126+
processor_merge = TermProcessor(client, cmd_merge)
127+
processor_merge.parsed_output = {
128+
"attributes": cmd_merge.attributes,
129+
"qualified_name": cmd_merge.attributes["Qualified Name"]["value"],
130+
"guid": "00000000-0000-0000-0000-000000000001",
131+
"valid": True
132+
}
133+
processor_merge.render_result_markdown = AsyncMock(return_value="# Result")
134+
135+
await processor_merge.apply_changes()
136+
137+
# Verify update called with correct mergeUpdate flag
138+
client._async_update_glossary_term.assert_called_once()
139+
merge_body = client._async_update_glossary_term.call_args.args[1]
140+
assert merge_body["mergeUpdate"] is True
141+
142+
# Check that properties NOT in the command (like abbreviation) are NOT in the body
143+
# while identification fields (class, typeName) are preserved.
144+
assert "abbreviation" not in merge_body["properties"]
145+
assert "class" in merge_body["properties"]
146+
assert "typeName" in merge_body["properties"]
147+
148+
# Should ADD B and NOT remove A
149+
add_calls = [call.args for call in client._async_add_to_collection.call_args_list]
150+
remove_calls = [call.args for call in client._async_remove_from_collection.call_args_list]
151+
152+
assert ("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "00000000-0000-0000-0000-000000000001") in add_calls
153+
assert len(remove_calls) == 0
154+
155+
print("Test Update (Merge=True): PASSED")
156+
157+
if __name__ == "__main__":
158+
asyncio.run(test_glossary_term_processing())

0 commit comments

Comments
 (0)