170 lines
6 KiB
Python
170 lines
6 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Tests for the Schicht-3 NodeAdapter projection (Phase 3).
|
|
|
|
Covers the pure projection helpers in nodeAdapter.py:
|
|
- identifying method-bound vs framework-primitive nodes
|
|
- extracting bindsAction
|
|
- building UserParamMapping from legacy parameter dicts
|
|
- converting inputPorts dict-of-dicts into per-port accepts lists
|
|
- end-to-end legacy-node → NodeAdapter projection
|
|
|
|
These tests do NOT touch live methods; they verify the projection logic
|
|
in isolation so it is robust before the adapterValidator composes with it.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from modules.features.graphicalEditor.nodeAdapter import (
|
|
NodeAdapter,
|
|
UserParamMapping,
|
|
_adapterFromLegacyNode,
|
|
_bindsActionFromLegacy,
|
|
_extractVisibleWhen,
|
|
_isMethodBoundNode,
|
|
_projectAllAdapters,
|
|
_projectInputAccepts,
|
|
_userParamFromLegacyParam,
|
|
)
|
|
|
|
|
|
def _legacyMethodNode(**overrides):
|
|
base = {
|
|
"id": "trustee.processDocuments",
|
|
"category": "trustee",
|
|
"label": "Verarbeiten",
|
|
"description": "...",
|
|
"parameters": [
|
|
{"name": "documentList", "type": "DocumentList", "required": True,
|
|
"frontendType": "dataRef", "description": "Eingabe"},
|
|
{"name": "featureInstanceId", "type": "FeatureInstanceRef", "required": True,
|
|
"frontendType": "hidden", "description": "Trustee-Instanz"},
|
|
],
|
|
"inputs": 1,
|
|
"outputs": 1,
|
|
"inputPorts": {0: {"accepts": ["DocumentList", "Transit"]}},
|
|
"outputPorts": {0: {"schema": "TrusteeProcessResult"}},
|
|
"meta": {"icon": "mdi-x", "color": "#000", "usesAi": False},
|
|
"_method": "trustee",
|
|
"_action": "processDocuments",
|
|
}
|
|
base.update(overrides)
|
|
return base
|
|
|
|
|
|
def _primitiveNode(**overrides):
|
|
base = {
|
|
"id": "flow.loop",
|
|
"category": "flow",
|
|
"label": "Schleife",
|
|
"parameters": [{"name": "items", "type": "string", "required": True}],
|
|
"inputs": 1,
|
|
"outputs": 1,
|
|
"inputPorts": {0: {"accepts": ["Transit"]}},
|
|
"executor": "flow",
|
|
}
|
|
base.update(overrides)
|
|
return base
|
|
|
|
|
|
class TestIsMethodBound:
|
|
def test_methodBoundIsTrue(self):
|
|
assert _isMethodBoundNode(_legacyMethodNode()) is True
|
|
|
|
def test_primitiveIsFalse(self):
|
|
assert _isMethodBoundNode(_primitiveNode()) is False
|
|
|
|
@pytest.mark.parametrize("partial", [
|
|
{"_method": "trustee"}, # missing _action
|
|
{"_action": "processDocuments"}, # missing _method
|
|
{},
|
|
])
|
|
def test_partialBindingIsFalse(self, partial):
|
|
node = _primitiveNode(**partial)
|
|
assert _isMethodBoundNode(node) is False
|
|
|
|
|
|
class TestBindsActionFromLegacy:
|
|
def test_returnsCanonicalFqn(self):
|
|
assert _bindsActionFromLegacy(_legacyMethodNode()) == "trustee.processDocuments"
|
|
|
|
def test_returnsNoneForPrimitive(self):
|
|
assert _bindsActionFromLegacy(_primitiveNode()) is None
|
|
|
|
|
|
class TestUserParamFromLegacy:
|
|
def test_carriesEditorOverridesOnly(self):
|
|
legacy = {"name": "documentList", "type": "DocumentList", "required": True,
|
|
"frontendType": "dataRef", "description": "Eingabe", "default": []}
|
|
mapping = _userParamFromLegacyParam(legacy)
|
|
assert isinstance(mapping, UserParamMapping)
|
|
assert mapping.actionArg == "documentList"
|
|
assert mapping.uiHint == "dataRef"
|
|
assert mapping.description == "Eingabe"
|
|
assert mapping.defaultValue == []
|
|
assert mapping.frontendOptions is None
|
|
|
|
def test_extractsConditionalVisibility(self):
|
|
legacy = {
|
|
"name": "filterJson",
|
|
"type": "string",
|
|
"frontendType": "textarea",
|
|
"frontendOptions": {"dependsOn": "mode", "showWhen": ["raw", "aggregate"]},
|
|
}
|
|
mapping = _userParamFromLegacyParam(legacy)
|
|
assert mapping.visibleWhen == {"actionArg": "mode", "in": ["raw", "aggregate"]}
|
|
|
|
|
|
class TestExtractVisibleWhen:
|
|
def test_returnsNoneForMissingHint(self):
|
|
assert _extractVisibleWhen(None) is None
|
|
assert _extractVisibleWhen({}) is None
|
|
assert _extractVisibleWhen({"dependsOn": "x"}) is None
|
|
|
|
def test_normalizesScalarShowWhen(self):
|
|
out = _extractVisibleWhen({"dependsOn": "entity", "showWhen": "tenant"})
|
|
assert out == {"actionArg": "entity", "in": ["tenant"]}
|
|
|
|
|
|
class TestProjectInputAccepts:
|
|
def test_perPortAcceptsList(self):
|
|
node = _legacyMethodNode()
|
|
assert _projectInputAccepts(node) == [["DocumentList", "Transit"]]
|
|
|
|
def test_emptyForZeroInputs(self):
|
|
node = _legacyMethodNode(inputs=0, inputPorts={})
|
|
assert _projectInputAccepts(node) == []
|
|
|
|
def test_handlesStringKeys(self):
|
|
node = _legacyMethodNode(inputPorts={"0": {"accepts": ["Transit"]}})
|
|
assert _projectInputAccepts(node) == [["Transit"]]
|
|
|
|
def test_missingPortReturnsEmptyList(self):
|
|
node = _legacyMethodNode(inputs=2, inputPorts={0: {"accepts": ["Transit"]}})
|
|
assert _projectInputAccepts(node) == [["Transit"], []]
|
|
|
|
|
|
class TestAdapterFromLegacyNode:
|
|
def test_buildsAdapter(self):
|
|
adapter = _adapterFromLegacyNode(_legacyMethodNode())
|
|
assert isinstance(adapter, NodeAdapter)
|
|
assert adapter.nodeId == "trustee.processDocuments"
|
|
assert adapter.bindsAction == "trustee.processDocuments"
|
|
assert adapter.category == "trustee"
|
|
assert len(adapter.userParams) == 2
|
|
assert adapter.userParams[0].actionArg == "documentList"
|
|
assert adapter.inputAccepts == [["DocumentList", "Transit"]]
|
|
assert adapter.contextParams == {}
|
|
assert adapter.meta.get("icon") == "mdi-x"
|
|
|
|
def test_returnsNoneForPrimitive(self):
|
|
assert _adapterFromLegacyNode(_primitiveNode()) is None
|
|
|
|
|
|
class TestProjectAllAdapters:
|
|
def test_skipsPrimitives(self):
|
|
nodes = [_legacyMethodNode(), _primitiveNode()]
|
|
out = _projectAllAdapters(nodes)
|
|
assert list(out.keys()) == ["trustee.processDocuments"]
|