192 lines
8.4 KiB
Python
192 lines
8.4 KiB
Python
"""Unit tests for the generic UDB tree builder (`_buildTree.py`).
|
|
|
|
Most node-level behavior moved into the polymorphic class hierarchy
|
|
(`udbNodes.py`) and has its own dedicated tests in `test_udbNodes.py`.
|
|
This file covers the orchestrator (`getChildrenForParents`) and the
|
|
remaining lookup helpers.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from modules.serviceCenter.services.serviceKnowledge import _buildTree
|
|
|
|
|
|
class TestKeyCoding(unittest.TestCase):
|
|
def test_encode_decode_roundtrip(self):
|
|
key = _buildTree._encode("ds", "conn-1", "sharepointFolder", "/sites/x")
|
|
kind, parts = _buildTree._decode(key)
|
|
self.assertEqual(kind, "ds")
|
|
self.assertEqual(parts, ["conn-1", "sharepointFolder", "/sites/x"])
|
|
|
|
def test_top_level_kinds(self):
|
|
self.assertEqual(_buildTree._decode("conn|abc")[0], "conn")
|
|
self.assertEqual(_buildTree._decode("mgrp|m1")[0], "mgrp")
|
|
self.assertEqual(_buildTree._decode("feat|m1|trustee|fi-1")[1], ["m1", "trustee", "fi-1"])
|
|
|
|
|
|
class TestRecordLookup(unittest.TestCase):
|
|
def test_finds_ds_record_by_normalised_path(self):
|
|
rec = {"id": "x", "connectionId": "c", "sourceType": "msft", "path": "/folder"}
|
|
self.assertEqual(_buildTree._findDsRecord([rec], "c", "msft", "/folder/").get("id"), "x")
|
|
self.assertIsNone(_buildTree._findDsRecord([rec], "c", "msft", "/other"))
|
|
|
|
def test_finds_fds_record_with_matching_filter(self):
|
|
rec = {"id": "f", "featureInstanceId": "fi1", "tableName": "Pos",
|
|
"recordFilter": {"id": "5"}}
|
|
self.assertEqual(_buildTree._findFdsRecord([rec], "fi1", "Pos", {"id": "5"}).get("id"), "f")
|
|
self.assertIsNone(_buildTree._findFdsRecord([rec], "fi1", "Pos", {"id": "99"}))
|
|
|
|
def test_fds_record_with_none_filter_matches_only_none(self):
|
|
rec = {"id": "f", "featureInstanceId": "fi1", "tableName": "*", "recordFilter": None}
|
|
self.assertEqual(_buildTree._findFdsRecord([rec], "fi1", "*", None).get("id"), "f")
|
|
self.assertIsNone(_buildTree._findFdsRecord([rec], "fi1", "*", {"id": "1"}))
|
|
|
|
|
|
class TestWiredTableFieldAggregation(unittest.TestCase):
|
|
"""`_wireTableFieldsAsLogicalChildren` rewraps `FdsTableNode.getEffectiveFlag`
|
|
so the table aggregates with its declared field children. The aggregate
|
|
must respect the new FdsField inheritance: if all fields inherit from the
|
|
table (no list entries), the table stays non-mixed."""
|
|
|
|
def _buildTableWithFields(self, *, tableNeutralize, neutralizeFields, fieldNames):
|
|
from modules.serviceCenter.services.serviceKnowledge.udbNodes import (
|
|
FdsTableNode, FdsFieldNode,
|
|
)
|
|
rec = {
|
|
"id": "fds-tbl", "featureInstanceId": "fi1", "tableName": "Pos",
|
|
"recordFilter": None,
|
|
"neutralize": tableNeutralize,
|
|
"neutralizeFields": neutralizeFields,
|
|
}
|
|
tableNode = FdsTableNode("fi1", "trustee", "Pos", "key", "Positions",
|
|
"feat|m1|trustee|fi1", rec=rec, hasFields=True)
|
|
fields = [
|
|
FdsFieldNode("fi1", "Pos", name, tableNode.key,
|
|
tableRec=rec, featureCode="trustee")
|
|
for name in fieldNames
|
|
]
|
|
tableNode._logicalFieldChildren = fields # type: ignore[attr-defined]
|
|
_buildTree._wireTableFieldsAsLogicalChildren(tableNode)
|
|
return tableNode, [rec]
|
|
|
|
def test_table_true_no_overrides_stays_true(self):
|
|
"""Regression: toggling a table to True must NOT leave it 'mixed'
|
|
because the declared field children should inherit the table value."""
|
|
tableNode, allFds = self._buildTableWithFields(
|
|
tableNeutralize=True, neutralizeFields=None,
|
|
fieldNames=["amount", "currency"],
|
|
)
|
|
self.assertTrue(tableNode.getEffectiveFlag("neutralize", [], allFds, "aggregate"))
|
|
|
|
def test_table_false_with_override_is_mixed(self):
|
|
tableNode, allFds = self._buildTableWithFields(
|
|
tableNeutralize=False, neutralizeFields=["amount"],
|
|
fieldNames=["amount", "currency"],
|
|
)
|
|
self.assertEqual(
|
|
tableNode.getEffectiveFlag("neutralize", [], allFds, "aggregate"),
|
|
"mixed",
|
|
)
|
|
|
|
def test_table_inherit_no_overrides_walks_default(self):
|
|
"""Implicit table + no overrides + no workspace -> default False."""
|
|
tableNode, allFds = self._buildTableWithFields(
|
|
tableNeutralize=None, neutralizeFields=None,
|
|
fieldNames=["amount", "currency"],
|
|
)
|
|
self.assertFalse(tableNode.getEffectiveFlag("neutralize", [], allFds, "aggregate"))
|
|
|
|
|
|
class TestGetChildrenForParents(unittest.TestCase):
|
|
"""End-to-end orchestrator tests with mocked dependencies. The
|
|
orchestrator returns serialised node dicts produced by
|
|
`UdbNode.toDict(...)`, so the keys/kinds/parentKey wiring is what
|
|
matters here -- not the inheritance arithmetic (covered in
|
|
test_udbNodes.py)."""
|
|
|
|
def _runAsync(self, coro):
|
|
return asyncio.run(coro)
|
|
|
|
def test_unknown_parent_key_returns_empty_list(self):
|
|
with patch("modules.interfaces.interfaceDbApp.getRootInterface") as mockRoot:
|
|
rootIf = MagicMock()
|
|
rootIf.db.getRecordset.return_value = []
|
|
mockRoot.return_value = rootIf
|
|
|
|
ctx = MagicMock()
|
|
ctx.user.id = "u1"
|
|
ctx.mandateId = "m1"
|
|
|
|
result = self._runAsync(
|
|
_buildTree.getChildrenForParents(["bogus|key"], ctx)
|
|
)
|
|
self.assertEqual(result["bogus|key"], [])
|
|
|
|
def test_top_level_emits_personal_root_first(self):
|
|
with patch("modules.interfaces.interfaceDbApp.getRootInterface") as mockRoot, \
|
|
patch("modules.serviceCenter.services.serviceKnowledge._buildTree._personalRootChildrenNodes", return_value=[]), \
|
|
patch("modules.security.rbacCatalog.getCatalogService"):
|
|
rootIf = MagicMock()
|
|
rootIf.db.getRecordset.return_value = []
|
|
rootIf.getUserMandates.return_value = []
|
|
mockRoot.return_value = rootIf
|
|
|
|
ctx = MagicMock()
|
|
ctx.user.id = "u1"
|
|
ctx.mandateId = "m1"
|
|
|
|
result = self._runAsync(
|
|
_buildTree.getChildrenForParents([None], ctx)
|
|
)
|
|
children = result["__root__"]
|
|
self.assertGreaterEqual(len(children), 1)
|
|
personalRoot = children[0]
|
|
self.assertEqual(personalRoot["key"], "personalRoot")
|
|
self.assertEqual(personalRoot["kind"], "synthRoot")
|
|
self.assertIsNone(personalRoot["parentKey"])
|
|
self.assertTrue(personalRoot["hasChildren"])
|
|
self.assertTrue(personalRoot["defaultExpanded"])
|
|
|
|
def test_top_level_emits_mandate_groups_inline(self):
|
|
with patch("modules.interfaces.interfaceDbApp.getRootInterface") as mockRoot, \
|
|
patch("modules.security.rbacCatalog.getCatalogService") as mockCatalog, \
|
|
patch("modules.serviceCenter.services.serviceKnowledge._buildTree._personalRootChildrenNodes", return_value=[]):
|
|
rootIf = MagicMock()
|
|
rootIf.db.getRecordset.return_value = []
|
|
userMandate = MagicMock()
|
|
userMandate.mandateId = "m1"
|
|
rootIf.getUserMandates.return_value = [userMandate]
|
|
featureInst = MagicMock()
|
|
featureInst.id = "fi-1"
|
|
featureInst.featureCode = "trustee"
|
|
featureInst.enabled = True
|
|
rootIf.getFeatureInstancesByMandate.return_value = [featureInst]
|
|
featureAccess = MagicMock(enabled=True)
|
|
rootIf.getFeatureAccess.return_value = featureAccess
|
|
mockRoot.return_value = rootIf
|
|
|
|
catalog = MagicMock()
|
|
catalog.getFeaturesWithDataObjects.return_value = ["trustee"]
|
|
mockCatalog.return_value = catalog
|
|
|
|
ctx = MagicMock()
|
|
ctx.user.id = "u1"
|
|
ctx.mandateId = None
|
|
|
|
result = self._runAsync(_buildTree.getChildrenForParents([None], ctx))
|
|
byKey = {c["key"]: c for c in result["__root__"]}
|
|
self.assertIn("personalRoot", byKey)
|
|
self.assertIn("mgrp|m1", byKey)
|
|
mgroup = byKey["mgrp|m1"]
|
|
self.assertEqual(mgroup["kind"], "mandateGroup")
|
|
self.assertIsNone(mgroup["parentKey"])
|
|
self.assertEqual(mgroup["mandateId"], "m1")
|
|
self.assertTrue(mgroup["defaultExpanded"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|