709 lines
32 KiB
Python
709 lines
32 KiB
Python
# Copyright (c) 2026 PowerOn AG
|
|
# All rights reserved.
|
|
"""Unit tests for `_inheritFlags` cascade-inherit helpers.
|
|
|
|
Verifies:
|
|
- getEffectiveFlag mode='walk': walks ancestors via path-prefix matching
|
|
- getEffectiveFlag mode='aggregate': returns 'mixed' when subtree diverges
|
|
- cascadeResetDescendants: bottom-up reset returning List[str]
|
|
- cascadeResetDescendantsFds: same for FeatureDataSource
|
|
- collectAncestorChain / collectAncestorChainFds: ancestor discovery
|
|
- buildEffectiveByConnection / buildEffectiveByWorkspaceFds: batch compute
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from typing import List
|
|
from unittest.mock import MagicMock
|
|
|
|
from modules.serviceCenter.core import flagResolution as _inheritFlags
|
|
|
|
|
|
def _ds(idVal: str, path: str, **flags) -> dict:
|
|
"""Build a DataSource dict with sensible defaults for a fixture."""
|
|
base = {
|
|
"id": idVal,
|
|
"connectionId": "conn-1",
|
|
"sourceType": "sharepointFolder",
|
|
"path": path,
|
|
"neutralize": None,
|
|
"ragIndexEnabled": None,
|
|
}
|
|
base.update(flags)
|
|
return base
|
|
|
|
|
|
def _fds(idVal: str, *, tableName: str, recordFilter=None, featureInstanceId="fi-1", **flags) -> dict:
|
|
"""Build a FeatureDataSource dict fixture.
|
|
|
|
FDS records no longer carry `userId`, `workspaceInstanceId`, or
|
|
`scope`; visibility/edit-permission live on the feature instance
|
|
via RBAC. Tests should only set neutralize/ragIndexEnabled.
|
|
"""
|
|
base = {
|
|
"id": idVal,
|
|
"featureInstanceId": featureInstanceId,
|
|
"tableName": tableName,
|
|
"recordFilter": recordFilter,
|
|
"neutralize": None,
|
|
"ragIndexEnabled": None,
|
|
}
|
|
base.update(flags)
|
|
return base
|
|
|
|
|
|
# ===========================================================================
|
|
# DataSource: getEffectiveFlag mode='walk'
|
|
# ===========================================================================
|
|
|
|
class TestEffectiveFlagWalk(unittest.TestCase):
|
|
def test_explicit_own_value_wins(self):
|
|
root = _ds("r", "/", neutralize=False)
|
|
leaf = _ds("l", "/folder/sub", neutralize=True)
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [root, leaf]))
|
|
|
|
def test_inherits_from_root_when_own_is_none(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
leaf = _ds("l", "/folder/sub")
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [root, leaf]))
|
|
|
|
def test_default_false_when_chain_empty(self):
|
|
leaf = _ds("l", "/folder/sub")
|
|
self.assertFalse(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [leaf]))
|
|
|
|
def test_nearest_ancestor_wins_over_distant(self):
|
|
root = _ds("r", "/", neutralize=False)
|
|
mid = _ds("m", "/folder", neutralize=True)
|
|
leaf = _ds("l", "/folder/sub")
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [root, mid, leaf]))
|
|
|
|
def test_different_connection_ignored(self):
|
|
otherConn = _ds("o", "/", connectionId="conn-2", neutralize=True)
|
|
leaf = _ds("l", "/folder")
|
|
self.assertFalse(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [otherConn, leaf]))
|
|
|
|
def test_different_sourcetype_ignored(self):
|
|
otherType = _ds("o", "/", sourceType="outlookFolder", neutralize=True)
|
|
leaf = _ds("l", "/folder")
|
|
self.assertFalse(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [otherType, leaf]))
|
|
|
|
def test_path_separator_required(self):
|
|
notAncestor = _ds("a", "/foo", neutralize=True)
|
|
leaf = _ds("l", "/foobar")
|
|
self.assertFalse(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [notAncestor, leaf]))
|
|
|
|
def test_root_is_ancestor_of_everything(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
leaf = _ds("l", "/anything/anywhere")
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [root, leaf]))
|
|
|
|
def test_unknown_flag_raises(self):
|
|
leaf = _ds("l", "/")
|
|
with self.assertRaises(ValueError):
|
|
_inheritFlags.getEffectiveFlag(leaf, "unknownFlag", [leaf])
|
|
|
|
def test_explicit_false_overrides_inherited_true(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
leaf = _ds("l", "/folder", neutralize=False)
|
|
self.assertFalse(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [root, leaf]))
|
|
|
|
def test_connection_root_inherits_cross_sourcetype(self):
|
|
connRoot = _ds("conn", "/", sourceType="msft", neutralize=True)
|
|
spService = _ds("sp", "/", sourceType="sharepointFolder")
|
|
olService = _ds("ol", "/", sourceType="outlookFolder")
|
|
allDs = [connRoot, spService, olService]
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(spService, "neutralize", allDs))
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(olService, "neutralize", allDs))
|
|
|
|
def test_same_sourcetype_ancestor_wins_over_connection_root(self):
|
|
connRoot = _ds("conn", "/", sourceType="msft", neutralize=True)
|
|
spRoot = _ds("sp", "/", sourceType="sharepointFolder", neutralize=False)
|
|
spLeaf = _ds("spl", "/sites/x", sourceType="sharepointFolder")
|
|
self.assertFalse(_inheritFlags.getEffectiveFlag(spLeaf, "neutralize", [connRoot, spRoot, spLeaf]))
|
|
|
|
def test_connection_root_does_not_self_inherit(self):
|
|
connRoot = _ds("conn", "/", sourceType="msft")
|
|
self.assertFalse(_inheritFlags.getEffectiveFlag(connRoot, "neutralize", [connRoot]))
|
|
|
|
|
|
# ===========================================================================
|
|
# DataSource: getEffectiveFlag mode='aggregate'
|
|
# ===========================================================================
|
|
|
|
class TestEffectiveFlagAggregate(unittest.TestCase):
|
|
def test_leaf_without_descendants_returns_concrete(self):
|
|
leaf = _ds("l", "/folder", neutralize=True)
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(leaf, "neutralize", [leaf], mode="aggregate"))
|
|
|
|
def test_all_descendants_same_returns_concrete(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
child1 = _ds("c1", "/a", neutralize=True)
|
|
child2 = _ds("c2", "/b") # inherits True from root
|
|
allDs = [root, child1, child2]
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(root, "neutralize", allDs, mode="aggregate"))
|
|
|
|
def test_divergent_descendants_returns_mixed(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
child1 = _ds("c1", "/a", neutralize=False)
|
|
child2 = _ds("c2", "/b") # inherits True from root
|
|
allDs = [root, child1, child2]
|
|
self.assertEqual(_inheritFlags.getEffectiveFlag(root, "neutralize", allDs, mode="aggregate"), "mixed")
|
|
|
|
def test_connection_root_aggregate_cross_sourcetype(self):
|
|
connRoot = _ds("conn", "/", sourceType="msft", neutralize=True)
|
|
spExplicit = _ds("sp", "/", sourceType="sharepointFolder", neutralize=False)
|
|
olInherit = _ds("ol", "/", sourceType="outlookFolder") # inherits True
|
|
allDs = [connRoot, spExplicit, olInherit]
|
|
self.assertEqual(
|
|
_inheritFlags.getEffectiveFlag(connRoot, "neutralize", allDs, mode="aggregate"),
|
|
"mixed",
|
|
)
|
|
|
|
def test_mid_level_aggregate_only_considers_own_subtree(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
mid = _ds("m", "/folder", neutralize=True)
|
|
midChild = _ds("mc", "/folder/sub", neutralize=True)
|
|
sibling = _ds("s", "/other", neutralize=False) # not under mid
|
|
allDs = [root, mid, midChild, sibling]
|
|
# mid's subtree is just midChild(True) + mid(True) = uniform
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(mid, "neutralize", allDs, mode="aggregate"))
|
|
# root's subtree includes sibling(False) = mixed
|
|
self.assertEqual(
|
|
_inheritFlags.getEffectiveFlag(root, "neutralize", allDs, mode="aggregate"),
|
|
"mixed",
|
|
)
|
|
|
|
def test_walk_mode_never_returns_mixed(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
child = _ds("c", "/a", neutralize=False)
|
|
allDs = [root, child]
|
|
self.assertTrue(_inheritFlags.getEffectiveFlag(root, "neutralize", allDs, mode="walk"))
|
|
|
|
|
|
# ===========================================================================
|
|
# DataSource: cascadeResetDescendants (bottom-up, List[str])
|
|
# ===========================================================================
|
|
|
|
class TestCascadeReset(unittest.TestCase):
|
|
def _makeRootIf(self, dataSources: List[dict]):
|
|
rootIf = MagicMock()
|
|
rootIf.db.getRecordset = MagicMock(return_value=dataSources)
|
|
modified = []
|
|
|
|
def _modify(model, recordId, fields):
|
|
modified.append((recordId, fields))
|
|
rootIf.db.recordModify = MagicMock(side_effect=_modify)
|
|
return rootIf, modified
|
|
|
|
def test_returns_list_of_ids(self):
|
|
parent = _ds("p", "/sites", neutralize=True)
|
|
child = _ds("c1", "/sites/folder1", neutralize=False)
|
|
rootIf, _ = self._makeRootIf([parent, child])
|
|
result = _inheritFlags.cascadeResetDescendants(rootIf, parent, "neutralize")
|
|
self.assertIsInstance(result, list)
|
|
self.assertEqual(result, ["c1"])
|
|
|
|
def test_resets_only_explicit_descendants(self):
|
|
parent = _ds("p", "/sites", neutralize=True)
|
|
explicitChild = _ds("c1", "/sites/folder1", neutralize=False)
|
|
inheritChild = _ds("c2", "/sites/folder2")
|
|
sibling = _ds("s", "/other", neutralize=True)
|
|
rootIf, modified = self._makeRootIf([parent, explicitChild, inheritChild, sibling])
|
|
|
|
result = _inheritFlags.cascadeResetDescendants(rootIf, parent, "neutralize")
|
|
|
|
self.assertEqual(result, ["c1"])
|
|
self.assertEqual(modified, [("c1", {"neutralize": None})])
|
|
|
|
def test_bottom_up_order(self):
|
|
"""Deepest items are reset first."""
|
|
parent = _ds("p", "/", neutralize=True)
|
|
level1 = _ds("l1", "/a", neutralize=False)
|
|
level2 = _ds("l2", "/a/b", neutralize=False)
|
|
level3 = _ds("l3", "/a/b/c", neutralize=False)
|
|
rootIf, modified = self._makeRootIf([parent, level1, level2, level3])
|
|
|
|
result = _inheritFlags.cascadeResetDescendants(rootIf, parent, "neutralize")
|
|
|
|
self.assertEqual(result, ["l3", "l2", "l1"])
|
|
|
|
def test_deep_cascade_through_null_items(self):
|
|
"""null items are skipped (no DB write) but cascade continues deeper."""
|
|
parent = _ds("p", "/", neutralize=True)
|
|
nullChild = _ds("n", "/a") # null — no write, but not a barrier
|
|
deepExplicit = _ds("d", "/a/b", neutralize=False)
|
|
rootIf, modified = self._makeRootIf([parent, nullChild, deepExplicit])
|
|
|
|
result = _inheritFlags.cascadeResetDescendants(rootIf, parent, "neutralize")
|
|
|
|
self.assertEqual(result, ["d"])
|
|
self.assertEqual(modified, [("d", {"neutralize": None})])
|
|
|
|
def test_does_not_modify_parent(self):
|
|
parent = _ds("p", "/", neutralize=True)
|
|
child = _ds("c", "/a", neutralize=False)
|
|
rootIf, modified = self._makeRootIf([parent, child])
|
|
_inheritFlags.cascadeResetDescendants(rootIf, parent, "neutralize")
|
|
self.assertNotIn("p", [m[0] for m in modified])
|
|
|
|
def test_connection_root_cascades_cross_sourcetype(self):
|
|
connRoot = _ds("conn", "/", sourceType="msft", neutralize=True)
|
|
spExplicit = _ds("sp", "/", sourceType="sharepointFolder", neutralize=False)
|
|
olInherit = _ds("ol", "/", sourceType="outlookFolder")
|
|
spLeaf = _ds("sp-leaf", "/sites/x", sourceType="sharepointFolder", neutralize=True)
|
|
rootIf, modified = self._makeRootIf([connRoot, spExplicit, olInherit, spLeaf])
|
|
|
|
result = _inheritFlags.cascadeResetDescendants(rootIf, connRoot, "neutralize")
|
|
|
|
self.assertEqual(set(result), {"sp", "sp-leaf"})
|
|
# sp-leaf is deeper, should come first
|
|
self.assertEqual(result[0], "sp-leaf")
|
|
|
|
def test_does_not_cross_sourcetype_for_non_authority(self):
|
|
parent = _ds("p", "/", neutralize=True, sourceType="sharepointFolder")
|
|
otherType = _ds("o", "/anything", neutralize=False, sourceType="outlookFolder")
|
|
rootIf, modified = self._makeRootIf([parent, otherType])
|
|
result = _inheritFlags.cascadeResetDescendants(rootIf, parent, "neutralize")
|
|
self.assertEqual(result, [])
|
|
|
|
def test_unknown_flag_raises(self):
|
|
parent = _ds("p", "/", neutralize=True)
|
|
rootIf, _ = self._makeRootIf([parent])
|
|
with self.assertRaises(ValueError):
|
|
_inheritFlags.cascadeResetDescendants(rootIf, parent, "unknownFlag")
|
|
|
|
|
|
# ===========================================================================
|
|
# DataSource: collectAncestorChain
|
|
# ===========================================================================
|
|
|
|
class TestCollectAncestorChain(unittest.TestCase):
|
|
def test_returns_nearest_first(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
mid = _ds("m", "/a")
|
|
leaf = _ds("l", "/a/b")
|
|
chain = _inheritFlags.collectAncestorChain(leaf, [root, mid, leaf])
|
|
self.assertEqual([_inheritFlags._getRecordValue(c, "id") for c in chain], ["m", "r"])
|
|
|
|
def test_connection_root_is_last(self):
|
|
connRoot = _ds("conn", "/", sourceType="msft")
|
|
spRoot = _ds("sp", "/", sourceType="sharepointFolder")
|
|
spLeaf = _ds("spl", "/sub", sourceType="sharepointFolder")
|
|
chain = _inheritFlags.collectAncestorChain(spLeaf, [connRoot, spRoot, spLeaf])
|
|
ids = [_inheritFlags._getRecordValue(c, "id") for c in chain]
|
|
self.assertEqual(ids, ["sp", "conn"])
|
|
|
|
def test_root_has_no_ancestors(self):
|
|
root = _ds("r", "/")
|
|
chain = _inheritFlags.collectAncestorChain(root, [root])
|
|
self.assertEqual(chain, [])
|
|
|
|
|
|
# ===========================================================================
|
|
# DataSource: buildEffectiveByConnection
|
|
# ===========================================================================
|
|
|
|
class TestBuildEffectiveByConnection(unittest.TestCase):
|
|
def test_walk_mode(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
child = _ds("c", "/a", neutralize=False)
|
|
leaf = _ds("l", "/a/b") # inherits False from child
|
|
result = _inheritFlags.buildEffectiveByConnection([root, child, leaf], "neutralize", mode="walk")
|
|
self.assertEqual(result, {"r": True, "c": False, "l": False})
|
|
|
|
def test_aggregate_mode(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
child = _ds("c", "/a", neutralize=False)
|
|
leaf = _ds("l", "/a/b") # inherits False from child
|
|
result = _inheritFlags.buildEffectiveByConnection([root, child, leaf], "neutralize", mode="aggregate")
|
|
self.assertEqual(result["r"], "mixed")
|
|
self.assertEqual(result["c"], False)
|
|
self.assertEqual(result["l"], False)
|
|
|
|
|
|
# ===========================================================================
|
|
# FeatureDataSource: getEffectiveFlagFds
|
|
# ===========================================================================
|
|
|
|
class TestFdsEffectiveFlagWalk(unittest.TestCase):
|
|
def test_own_explicit_wins(self):
|
|
ws = _fds("ws", tableName="*", neutralize=False)
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"}, neutralize=True)
|
|
self.assertTrue(_inheritFlags.getEffectiveFlagFds(rec, "neutralize", [ws, rec]))
|
|
|
|
def test_inherits_from_table_wildcard(self):
|
|
tbl = _fds("t", tableName="Pos", neutralize=True)
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"})
|
|
self.assertTrue(_inheritFlags.getEffectiveFlagFds(rec, "neutralize", [tbl, rec]))
|
|
|
|
def test_table_wildcard_beats_workspace_wildcard(self):
|
|
ws = _fds("ws", tableName="*", neutralize=False)
|
|
tbl = _fds("t", tableName="Pos", neutralize=True)
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"})
|
|
self.assertTrue(_inheritFlags.getEffectiveFlagFds(rec, "neutralize", [ws, tbl, rec]))
|
|
|
|
def test_workspace_wildcard_inherits_when_no_table(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"})
|
|
self.assertTrue(_inheritFlags.getEffectiveFlagFds(rec, "neutralize", [ws, rec]))
|
|
|
|
def test_default_false_when_chain_empty(self):
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"})
|
|
self.assertFalse(_inheritFlags.getEffectiveFlagFds(rec, "neutralize", [rec]))
|
|
|
|
def test_unknown_flag_raises(self):
|
|
rec = _fds("r", tableName="*")
|
|
with self.assertRaises(ValueError):
|
|
_inheritFlags.getEffectiveFlagFds(rec, "doesNotExist", [rec])
|
|
|
|
|
|
class TestFdsEffectiveFlagAggregate(unittest.TestCase):
|
|
def test_leaf_without_descendants(self):
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"}, neutralize=True)
|
|
self.assertTrue(_inheritFlags.getEffectiveFlagFds(rec, "neutralize", [rec], mode="aggregate"))
|
|
|
|
def test_all_descendants_same(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tbl = _fds("t", tableName="Pos") # inherits True
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"}) # inherits True
|
|
allFds = [ws, tbl, rec]
|
|
self.assertTrue(_inheritFlags.getEffectiveFlagFds(ws, "neutralize", allFds, mode="aggregate"))
|
|
|
|
def test_divergent_descendants_returns_mixed(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tbl = _fds("t", tableName="Pos", neutralize=False)
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"}) # inherits False from tbl
|
|
allFds = [ws, tbl, rec]
|
|
self.assertEqual(
|
|
_inheritFlags.getEffectiveFlagFds(ws, "neutralize", allFds, mode="aggregate"),
|
|
"mixed",
|
|
)
|
|
|
|
def test_table_aggregate_own_subtree_only(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tblA = _fds("tA", tableName="A", neutralize=True)
|
|
recA = _fds("rA", tableName="A", recordFilter={"id": "1"}, neutralize=True)
|
|
tblB = _fds("tB", tableName="B", neutralize=False)
|
|
allFds = [ws, tblA, recA, tblB]
|
|
# tblA subtree: all True
|
|
self.assertTrue(_inheritFlags.getEffectiveFlagFds(tblA, "neutralize", allFds, mode="aggregate"))
|
|
# ws subtree: mixed (tblB is False)
|
|
self.assertEqual(
|
|
_inheritFlags.getEffectiveFlagFds(ws, "neutralize", allFds, mode="aggregate"),
|
|
"mixed",
|
|
)
|
|
|
|
|
|
# ===========================================================================
|
|
# FeatureDataSource: cascadeResetDescendantsFds (bottom-up, List[str])
|
|
# ===========================================================================
|
|
|
|
class TestFdsCascadeReset(unittest.TestCase):
|
|
def _makeRootIf(self, fdses):
|
|
rootIf = MagicMock()
|
|
rootIf.db.getRecordset = MagicMock(return_value=fdses)
|
|
modified = []
|
|
|
|
def _modify(model, recordId, fields):
|
|
modified.append((recordId, fields))
|
|
rootIf.db.recordModify = MagicMock(side_effect=_modify)
|
|
return rootIf, modified
|
|
|
|
def test_returns_list_of_ids(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tbl = _fds("t", tableName="Pos", neutralize=False)
|
|
rootIf, _ = self._makeRootIf([ws, tbl])
|
|
result = _inheritFlags.cascadeResetDescendantsFds(rootIf, ws, "neutralize")
|
|
self.assertIsInstance(result, list)
|
|
self.assertEqual(result, ["t"])
|
|
|
|
def test_workspace_cascades_to_all_explicit_descendants(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tblExplicit = _fds("t", tableName="Pos", neutralize=False)
|
|
tblInherit = _fds("t2", tableName="Other")
|
|
recExplicit = _fds("r", tableName="Pos", recordFilter={"id": "1"}, neutralize=True)
|
|
rootIf, modified = self._makeRootIf([ws, tblExplicit, tblInherit, recExplicit])
|
|
|
|
result = _inheritFlags.cascadeResetDescendantsFds(rootIf, ws, "neutralize")
|
|
|
|
self.assertEqual(set(result), {"t", "r"})
|
|
# record is deeper (depth 2) than table (depth 1), should come first
|
|
self.assertEqual(result[0], "r")
|
|
|
|
def test_table_cascades_only_to_same_table_records(self):
|
|
tbl = _fds("t", tableName="Pos", neutralize=True)
|
|
recSame = _fds("r1", tableName="Pos", recordFilter={"id": "1"}, neutralize=False)
|
|
recOther = _fds("r2", tableName="Other", recordFilter={"id": "1"}, neutralize=False)
|
|
rootIf, modified = self._makeRootIf([tbl, recSame, recOther])
|
|
|
|
result = _inheritFlags.cascadeResetDescendantsFds(rootIf, tbl, "neutralize")
|
|
|
|
self.assertEqual(result, ["r1"])
|
|
self.assertEqual(modified, [("r1", {"neutralize": None})])
|
|
|
|
def test_record_has_no_cascade(self):
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"}, neutralize=True)
|
|
rootIf, modified = self._makeRootIf([rec])
|
|
result = _inheritFlags.cascadeResetDescendantsFds(rootIf, rec, "neutralize")
|
|
self.assertEqual(result, [])
|
|
|
|
def test_unknown_flag_raises(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
rootIf, _ = self._makeRootIf([ws])
|
|
with self.assertRaises(ValueError):
|
|
_inheritFlags.cascadeResetDescendantsFds(rootIf, ws, "doesNotExist")
|
|
|
|
|
|
|
|
# ===========================================================================
|
|
# FeatureDataSource: collectAncestorChainFds
|
|
# ===========================================================================
|
|
|
|
class TestCollectAncestorChainFds(unittest.TestCase):
|
|
def test_record_has_table_then_workspace(self):
|
|
ws = _fds("ws", tableName="*")
|
|
tbl = _fds("t", tableName="Pos")
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"})
|
|
chain = _inheritFlags.collectAncestorChainFds(rec, [ws, tbl, rec])
|
|
ids = [c["id"] for c in chain]
|
|
self.assertEqual(ids, ["t", "ws"])
|
|
|
|
def test_table_has_only_workspace(self):
|
|
ws = _fds("ws", tableName="*")
|
|
tbl = _fds("t", tableName="Pos")
|
|
chain = _inheritFlags.collectAncestorChainFds(tbl, [ws, tbl])
|
|
self.assertEqual([c["id"] for c in chain], ["ws"])
|
|
|
|
def test_workspace_has_no_ancestors(self):
|
|
ws = _fds("ws", tableName="*")
|
|
chain = _inheritFlags.collectAncestorChainFds(ws, [ws])
|
|
self.assertEqual(chain, [])
|
|
|
|
|
|
# ===========================================================================
|
|
# FeatureDataSource: buildEffectiveByWorkspaceFds
|
|
# ===========================================================================
|
|
|
|
class TestBuildEffectiveByWorkspaceFds(unittest.TestCase):
|
|
def test_walk_mode(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tbl = _fds("t", tableName="Pos", neutralize=False)
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"}) # inherits False from tbl
|
|
result = _inheritFlags.buildEffectiveByWorkspaceFds([ws, tbl, rec], "neutralize", mode="walk")
|
|
self.assertEqual(result, {"ws": True, "t": False, "r": False})
|
|
|
|
def test_aggregate_mode(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tbl = _fds("t", tableName="Pos", neutralize=False)
|
|
rec = _fds("r", tableName="Pos", recordFilter={"id": "1"})
|
|
result = _inheritFlags.buildEffectiveByWorkspaceFds([ws, tbl, rec], "neutralize", mode="aggregate")
|
|
self.assertEqual(result["ws"], "mixed")
|
|
self.assertEqual(result["t"], False)
|
|
self.assertEqual(result["r"], False)
|
|
|
|
|
|
# ===========================================================================
|
|
# resolveEffectiveForPath (with and without own record)
|
|
# ===========================================================================
|
|
|
|
class TestResolveEffectiveForPath(unittest.TestCase):
|
|
def test_with_exact_record(self):
|
|
root = _ds("r", "/", neutralize=True, ragIndexEnabled=False)
|
|
leaf = _ds("l", "/folder/sub", neutralize=False)
|
|
allDs = [root, leaf]
|
|
result = _inheritFlags.resolveEffectiveForPath("conn-1", "sharepointFolder", "/folder/sub", allDs)
|
|
self.assertEqual(result["effectiveNeutralize"], False)
|
|
self.assertNotIn("effectiveScope", result)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], False)
|
|
|
|
def test_without_record_inherits_from_ancestor(self):
|
|
root = _ds("r", "/", neutralize=True, ragIndexEnabled=True)
|
|
allDs = [root]
|
|
result = _inheritFlags.resolveEffectiveForPath("conn-1", "sharepointFolder", "/deep/path/file.txt", allDs)
|
|
self.assertEqual(result["effectiveNeutralize"], True)
|
|
self.assertNotIn("effectiveScope", result)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], True)
|
|
|
|
def test_without_record_inherits_from_closest_ancestor(self):
|
|
root = _ds("r", "/", neutralize=True, ragIndexEnabled=True)
|
|
mid = _ds("m", "/folder", neutralize=False, ragIndexEnabled=False)
|
|
allDs = [root, mid]
|
|
result = _inheritFlags.resolveEffectiveForPath("conn-1", "sharepointFolder", "/folder/sub/file.txt", allDs)
|
|
self.assertEqual(result["effectiveNeutralize"], False)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], False)
|
|
|
|
def test_without_record_no_ancestors_returns_defaults(self):
|
|
allDs: list = []
|
|
result = _inheritFlags.resolveEffectiveForPath("conn-1", "sharepointFolder", "/path", allDs)
|
|
self.assertEqual(result["effectiveNeutralize"], False)
|
|
self.assertNotIn("effectiveScope", result)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], False)
|
|
|
|
def test_connection_root_covers_service_subtree(self):
|
|
connRoot = _ds("cr", "/", neutralize=True, sourceType="msft")
|
|
allDs = [connRoot]
|
|
result = _inheritFlags.resolveEffectiveForPath("conn-1", "sharepointFolder", "/sites/intranet", allDs)
|
|
self.assertEqual(result["effectiveNeutralize"], True)
|
|
|
|
def test_exact_record_with_aggregate_mixed(self):
|
|
root = _ds("r", "/", neutralize=True)
|
|
leaf = _ds("l", "/sub", neutralize=False)
|
|
allDs = [root, leaf]
|
|
result = _inheritFlags.resolveEffectiveForPath("conn-1", "sharepointFolder", "/", allDs, mode="aggregate")
|
|
self.assertEqual(result["effectiveNeutralize"], "mixed")
|
|
|
|
|
|
class TestResolveEffectiveForFds(unittest.TestCase):
|
|
"""FDS records carry only `neutralize` + `ragIndexEnabled`. No scope.
|
|
|
|
`resolveEffectiveForFds` therefore returns a two-key dict; tests
|
|
must not assert anything about `effectiveScope` on FDS results.
|
|
"""
|
|
|
|
def test_with_exact_record(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True)
|
|
tbl = _fds("t", tableName="Pos", neutralize=False)
|
|
allFds = [ws, tbl]
|
|
result = _inheritFlags.resolveEffectiveForFds("fi-1", "Pos", None, allFds)
|
|
self.assertEqual(result["effectiveNeutralize"], False)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], False)
|
|
self.assertNotIn("effectiveScope", result)
|
|
|
|
def test_without_record_inherits_from_feature_wildcard(self):
|
|
ws = _fds("ws", tableName="*", neutralize=True, ragIndexEnabled=True)
|
|
allFds = [ws]
|
|
result = _inheritFlags.resolveEffectiveForFds("fi-1", "Unknown", None, allFds)
|
|
self.assertEqual(result["effectiveNeutralize"], True)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], True)
|
|
|
|
def test_without_record_no_ancestors_returns_defaults(self):
|
|
allFds: list = []
|
|
result = _inheritFlags.resolveEffectiveForFds("fi-1", "Pos", None, allFds)
|
|
self.assertEqual(result["effectiveNeutralize"], False)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], False)
|
|
|
|
def test_rag_inherits_when_table_overrides_neutralize_only(self):
|
|
"""Tables that override only neutralize must still inherit RAG from parent."""
|
|
ws = _fds("ws", tableName="*", ragIndexEnabled=True)
|
|
tbl = _fds("t", tableName="Pos", neutralize=False)
|
|
allFds = [ws, tbl]
|
|
result = _inheritFlags.resolveEffectiveForFds("fi-1", "Pos", None, allFds)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], True)
|
|
|
|
def test_rag_aggregate_mixed_when_descendants_diverge(self):
|
|
ws = _fds("ws", tableName="*", ragIndexEnabled=True)
|
|
tbl = _fds("t", tableName="Pos", ragIndexEnabled=False)
|
|
allFds = [ws, tbl]
|
|
result = _inheritFlags.resolveEffectiveForFds("fi-1", "*", None, allFds, mode="aggregate")
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], "mixed")
|
|
|
|
def test_inheritable_flags_and_fds_flags(self):
|
|
self.assertIn("ragIndexEnabled", _inheritFlags._INHERITABLE_FDS_FLAGS)
|
|
self.assertIn("neutralize", _inheritFlags._INHERITABLE_FDS_FLAGS)
|
|
self.assertNotIn("scope", _inheritFlags._INHERITABLE_FDS_FLAGS)
|
|
self.assertNotIn("scope", _inheritFlags._INHERITABLE_FLAGS)
|
|
|
|
|
|
# ===========================================================================
|
|
# FDS cascade resets RAG (in addition to neutralize)
|
|
# ===========================================================================
|
|
|
|
class TestCascadeResetFdsRag(unittest.TestCase):
|
|
def test_cascade_resets_rag_on_descendants(self):
|
|
ws = _fds("ws", tableName="*")
|
|
tbl = _fds("t", tableName="Pos", ragIndexEnabled=False)
|
|
allFds = [ws, tbl]
|
|
rootIf = MagicMock()
|
|
rootIf.db.getRecordset.return_value = allFds
|
|
rootIf.db.recordModify = MagicMock()
|
|
result = _inheritFlags.cascadeResetDescendantsFds(rootIf, ws, "ragIndexEnabled")
|
|
self.assertIn("t", result)
|
|
rootIf.db.recordModify.assert_called()
|
|
|
|
|
|
# ===========================================================================
|
|
# Path normalization
|
|
# ===========================================================================
|
|
|
|
class TestPathNormalization(unittest.TestCase):
|
|
def test_empty_path_normalises_to_root(self):
|
|
self.assertEqual(_inheritFlags.normalisePath(""), "/")
|
|
self.assertEqual(_inheritFlags.normalisePath(None), "/")
|
|
|
|
def test_trailing_slash_stripped(self):
|
|
self.assertEqual(_inheritFlags.normalisePath("/foo/"), "/foo")
|
|
self.assertEqual(_inheritFlags.normalisePath("/"), "/")
|
|
|
|
def test_leading_slash_added(self):
|
|
self.assertEqual(_inheritFlags.normalisePath("foo/bar"), "/foo/bar")
|
|
|
|
|
|
# ===========================================================================
|
|
# Virtual coordinates (no DB record) must support aggregate mode (mixed)
|
|
# ===========================================================================
|
|
|
|
class TestVirtualCoordAggregate(unittest.TestCase):
|
|
"""After the spec-recovery fix, resolveEffectiveForPath/Fds with
|
|
mode='aggregate' must return 'mixed' for coordinates that have no DB
|
|
record but whose descendants in the DB diverge."""
|
|
|
|
def test_virtual_folder_mixed_neutralize(self):
|
|
child1 = _ds("c1", "/virtual/a", neutralize=True)
|
|
child2 = _ds("c2", "/virtual/b", neutralize=False)
|
|
allDs = [child1, child2]
|
|
result = _inheritFlags.resolveEffectiveForPath(
|
|
"conn-1", "sharepointFolder", "/virtual", allDs, mode="aggregate",
|
|
)
|
|
self.assertEqual(result["effectiveNeutralize"], "mixed")
|
|
|
|
def test_virtual_folder_mixed_rag(self):
|
|
child1 = _ds("c1", "/virtual/a", ragIndexEnabled=True)
|
|
child2 = _ds("c2", "/virtual/b", ragIndexEnabled=False)
|
|
allDs = [child1, child2]
|
|
result = _inheritFlags.resolveEffectiveForPath(
|
|
"conn-1", "sharepointFolder", "/virtual", allDs, mode="aggregate",
|
|
)
|
|
self.assertEqual(result["effectiveRagIndexEnabled"], "mixed")
|
|
|
|
def test_virtual_folder_uniform_returns_concrete(self):
|
|
child1 = _ds("c1", "/virtual/a", neutralize=True)
|
|
child2 = _ds("c2", "/virtual/b", neutralize=True)
|
|
allDs = [child1, child2]
|
|
result = _inheritFlags.resolveEffectiveForPath(
|
|
"conn-1", "sharepointFolder", "/virtual", allDs, mode="aggregate",
|
|
)
|
|
self.assertTrue(result["effectiveNeutralize"])
|
|
|
|
def test_virtual_fds_workspace_mixed_neutralize(self):
|
|
tblA = _fds("tA", tableName="A", neutralize=True)
|
|
tblB = _fds("tB", tableName="B", neutralize=False)
|
|
allFds = [tblA, tblB]
|
|
result = _inheritFlags.resolveEffectiveForFds(
|
|
"fi-1", "*", None, allFds, mode="aggregate",
|
|
)
|
|
self.assertEqual(result["effectiveNeutralize"], "mixed")
|
|
|
|
def test_virtual_fds_workspace_uniform_returns_concrete(self):
|
|
tblA = _fds("tA", tableName="A", neutralize=True)
|
|
tblB = _fds("tB", tableName="B", neutralize=True)
|
|
allFds = [tblA, tblB]
|
|
result = _inheritFlags.resolveEffectiveForFds(
|
|
"fi-1", "*", None, allFds, mode="aggregate",
|
|
)
|
|
self.assertTrue(result["effectiveNeutralize"])
|
|
|
|
def test_virtual_connection_root_mixed_via_services(self):
|
|
"""Connection root (authority sourceType, path='/') with no DB record
|
|
but services that diverge must return 'mixed'."""
|
|
spRecord = _ds("sp", "/", sourceType="sharepointFolder", neutralize=True)
|
|
olRecord = _ds("ol", "/", sourceType="outlookFolder", neutralize=False)
|
|
allDs = [spRecord, olRecord]
|
|
result = _inheritFlags.resolveEffectiveForPath(
|
|
"conn-1", "msft", "/", allDs, mode="aggregate",
|
|
)
|
|
self.assertEqual(result["effectiveNeutralize"], "mixed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|