nodeslicer/tests/test_path_sockets.py
2026-10-16 12:03:01 +03:00

173 lines
7 KiB
Python

from __future__ import annotations
import sys
import unittest
from pathlib import Path
from unittest.mock import MagicMock
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.append(str(PROJECT_ROOT.parent))
# Ensure mock bpy is set up
from tests.test_lists import mock_bpy
from NodeSlicer.core.nodeslicer.sockets import SLICESOCK_DirPath, SLICESOCK_FilePath, InputNodeSocket
from NodeSlicer.infra.blender.blender_slice_nodes_generic import SliceNode, resolve_input
from NodeSlicer.core.nodeslicer.nodes.output.gcode_to_file import SLICENODE_GcodeToFile
from NodeSlicer.core.nodeslicer.nodes.output.mesh_to_pdf import SLICENODE_MeshToPdf
from NodeSlicer.infra.blender.list_utils import LIST_CAPABLE_SOCKET_TYPES
class TestPathSockets(unittest.TestCase):
def test_socket_definitions(self):
"""Test that SLICESOCK_DirPath and SLICESOCK_FilePath are properly defined subclasses."""
self.assertTrue(issubclass(SLICESOCK_DirPath, InputNodeSocket))
self.assertTrue(issubclass(SLICESOCK_FilePath, InputNodeSocket))
dir_sock = SLICESOCK_DirPath()
file_sock = SLICESOCK_FilePath()
self.assertEqual(dir_sock.default, "")
self.assertEqual(file_sock.default, "")
self.assertEqual(dir_sock.value, "")
self.assertEqual(file_sock.value, "")
dir_sock.value = "/custom/directory/path"
file_sock.value = "/custom/file/path.gcode"
self.assertEqual(dir_sock.value, "/custom/directory/path")
self.assertEqual(file_sock.value, "/custom/file/path.gcode")
def test_list_capable_socket_types(self):
"""Ensure path sockets are recognized as valid socket types in list utilities."""
self.assertIn("SLICESOCK_DirPath", LIST_CAPABLE_SOCKET_TYPES)
self.assertIn("SLICESOCK_FilePath", LIST_CAPABLE_SOCKET_TYPES)
def test_add_input_subtype_mapping(self):
"""Test that add_input maps subtype='DIR_PATH' and subtype='FILE_PATH' to the appropriate socket."""
class DummyNode(SliceNode):
def __init__(self):
self.inputs = MagicMock()
self.outputs = MagicMock()
node = DummyNode()
node.add_input("NodeSocketString", "Folder", subtype="DIR_PATH")
node.inputs.new.assert_called_with("SLICESOCK_DirPath", "Folder", identifier="Folder")
node.add_input("NodeSocketString", "File", subtype="FILE_PATH")
node.inputs.new.assert_called_with("SLICESOCK_FilePath", "File", identifier="File")
# Case-insensitive and alias tests
node.add_input("NodeSocketString", "Dir", subtype="folder")
node.inputs.new.assert_called_with("SLICESOCK_DirPath", "Dir", identifier="Dir")
node.add_input("NodeSocketString", "Doc", subtype="file")
node.inputs.new.assert_called_with("SLICESOCK_FilePath", "Doc", identifier="Doc")
# Standard string without subtype remains NodeSocketString
node.add_input("NodeSocketString", "Name")
node.inputs.new.assert_called_with("NodeSocketString", "Name", identifier="Name")
# Direct socket type specification
node.add_input("SLICESOCK_DirPath", "TargetDir")
node.inputs.new.assert_called_with("SLICESOCK_DirPath", "TargetDir", identifier="TargetDir")
def test_add_output_subtype_mapping(self):
"""Test that add_output also supports subtype mapping."""
class DummyNode(SliceNode):
def __init__(self):
self.inputs = MagicMock()
self.outputs = MagicMock()
node = DummyNode()
node.add_output("NodeSocketString", "OutFolder", subtype="DIR_PATH")
node.outputs.new.assert_called_with("SLICESOCK_DirPath", "OutFolder", identifier="OutFolder")
node.add_output("NodeSocketString", "OutFile", subtype="FILE_PATH")
node.outputs.new.assert_called_with("SLICESOCK_FilePath", "OutFile", identifier="OutFile")
def test_nodes_use_dir_path_socket(self):
"""Verify that GcodeToFile and MeshToPdf configure the Folder socket with DIR_PATH subtype."""
class MockSocketCollection:
def __init__(self):
self.created = []
def new(self, bl_idname, name, identifier=""):
mock_sock = MagicMock()
mock_sock.bl_idname = bl_idname
mock_sock.name = name
mock_sock.identifier = identifier or name
self.created.append((bl_idname, name, identifier, mock_sock))
return mock_sock
def __iter__(self):
return iter([item[3] for item in self.created])
def get(self, name):
for bl_id, n, ident, s in self.created:
if n == name:
return s
return None
class InspectGcodeToFile(SLICENODE_GcodeToFile):
def __init__(self):
self.inputs = MockSocketCollection()
self.outputs = MockSocketCollection()
self._data = {}
class InspectMeshToPdf(SLICENODE_MeshToPdf):
def __init__(self):
self.inputs = MockSocketCollection()
self.outputs = MockSocketCollection()
self._data = {}
gcode_node = InspectGcodeToFile()
gcode_node.init(None)
folder_in_gcode = next((bl_id for bl_id, name, _, _ in gcode_node.inputs.created if name == "Folder"), None)
self.assertEqual(folder_in_gcode, "SLICESOCK_DirPath")
pdf_node = InspectMeshToPdf()
pdf_node.init(None)
folder_in_pdf = next((bl_id for bl_id, name, _, _ in pdf_node.inputs.created if name == "Folder"), None)
self.assertEqual(folder_in_pdf, "SLICESOCK_DirPath")
def test_resolve_input_with_path_socket(self):
"""Verify resolve_input behavior for unlinked and linked path sockets."""
class DummySocket:
def __init__(self, name, bl_idname, value=""):
self.name = name
self.bl_idname = bl_idname
self.identifier = name
self.value = value
self.default = ""
self.is_linked = False
self.links = []
self.node = MagicMock()
sock = DummySocket("Folder", "SLICESOCK_DirPath", value="")
# Empty value returns default empty string
self.assertEqual(resolve_input(sock), "")
# Set value
sock.value = "/path/to/folder"
self.assertEqual(resolve_input(sock), "/path/to/folder")
# Linked socket follows upstream
class UpstreamNode(SliceNode):
def evaluate_output(self, socket_name, **kwargs):
return "/upstream/linked/path"
upstream = UpstreamNode()
from_socket = MagicMock()
from_socket.node = upstream
from_socket.name = "String Out"
from_socket.identifier = "String Out"
mock_link = MagicMock()
mock_link.from_socket = from_socket
sock.is_linked = True
sock.links = [mock_link]
self.assertEqual(resolve_input(sock), "/upstream/linked/path")
if __name__ == "__main__":
unittest.main()