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