|
| 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