using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; using NUnit.Framework; using UnityEditor; using UnityEngine; using MCPForUnity.Editor.Tools.ProBuilder; using static MCPForUnityTests.Editor.TestUtilities; namespace MCPForUnityTests.Editor.Tools { public class ManageProBuilderTests { private readonly List _createdObjects = new List(); private bool _proBuilderInstalled; [OneTimeSetUp] public void OneTimeSetUp() { _proBuilderInstalled = Type.GetType( "UnityEngine.ProBuilder.ProBuilderMesh, Unity.ProBuilder" ) != null; } [TearDown] public void TearDown() { foreach (var go in _createdObjects) { if (go != null) UnityEngine.Object.DestroyImmediate(go); } _createdObjects.Clear(); } // ===================================================================== // Basic action validation (works regardless of ProBuilder installation) // ===================================================================== [Test] public void HandleCommand_MissingAction_ReturnsError() { var paramsObj = new JObject(); var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsFalse(result.Value("success"), result.ToString()); } [Test] public void HandleCommand_UnknownAction_ReturnsError() { var paramsObj = new JObject { ["action"] = "nonexistent_action" }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsFalse(result.Value("success"), result.ToString()); var errorMsg = result["error"]?.ToString() ?? result["message"]?.ToString(); Assert.That(errorMsg, Does.Contain("Unknown action").Or.Contain("not installed")); } [Test] public void HandleCommand_Ping_ReturnsSuccess() { var paramsObj = new JObject { ["action"] = "ping" }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); if (!_proBuilderInstalled) { // Without ProBuilder, should return error about missing package Assert.IsFalse(result.Value("success"), result.ToString()); Assert.That(result["error"]?.ToString(), Does.Contain("ProBuilder").IgnoreCase); return; } Assert.IsTrue(result.Value("success"), result.ToString()); } // ===================================================================== // Shape creation (requires ProBuilder) // ===================================================================== [Test] public void CreateShape_MissingShapeType_ReturnsError() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var paramsObj = new JObject { ["action"] = "create_shape", }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsFalse(result.Value("success"), result.ToString()); } [Test] public void CreateShape_InvalidShapeType_ReturnsError() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var paramsObj = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "InvalidShape" }, }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsFalse(result.Value("success"), result.ToString()); } [Test] public void CreateShape_Cube_CreatesGameObject() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var paramsObj = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestCube", }, }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data, "Result should contain data"); Assert.AreEqual("PBTestCube", data.Value("gameObjectName")); Assert.Greater(data.Value("faceCount"), 0); Assert.Greater(data.Value("vertexCount"), 0); // Track for cleanup var go = GameObject.Find("PBTestCube"); if (go != null) _createdObjects.Add(go); } [Test] public void CreateShape_WithPosition_SetsTransform() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var paramsObj = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestCubePos", ["position"] = new JArray(5f, 10f, 15f), }, }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsTrue(result.Value("success"), result.ToString()); var go = GameObject.Find("PBTestCubePos"); Assert.IsNotNull(go, "Created GameObject should exist"); Assert.AreEqual(new Vector3(5f, 10f, 15f), go.transform.position); _createdObjects.Add(go); } [Test] public void CreatePolyShape_CreatesFromPoints() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var paramsObj = new JObject { ["action"] = "create_poly_shape", ["properties"] = new JObject { ["points"] = new JArray( new JArray(0f, 0f, 0f), new JArray(5f, 0f, 0f), new JArray(5f, 0f, 5f), new JArray(0f, 0f, 5f) ), ["extrudeHeight"] = 3f, ["name"] = "PBTestPoly", }, }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsTrue(result.Value("success"), result.ToString()); var go = GameObject.Find("PBTestPoly"); if (go != null) _createdObjects.Add(go); } // ===================================================================== // Mesh editing (requires ProBuilder + created shape) // ===================================================================== [Test] public void GetMeshInfo_ReturnsDetails() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } // First create a shape var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestInfoCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestInfoCube"); Assert.IsNotNull(go); _createdObjects.Add(go); // Now get mesh info var infoParams = new JObject { ["action"] = "get_mesh_info", ["target"] = "PBTestInfoCube", ["properties"] = new JObject { ["include"] = "faces" }, }; var result = ToJObject(ManageProBuilder.HandleCommand(infoParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data); Assert.Greater(data.Value("faceCount"), 0); Assert.Greater(data.Value("vertexCount"), 0); Assert.IsNotNull(data["bounds"]); Assert.IsNotNull(data["faces"]); } [Test] public void ExtrudeFaces_IncreaseFaceCount() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } // Create a cube var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestExtrudeCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestExtrudeCube"); _createdObjects.Add(go); int initialFaceCount = createResult["data"].Value("faceCount"); // Extrude face 0 var extrudeParams = new JObject { ["action"] = "extrude_faces", ["target"] = "PBTestExtrudeCube", ["properties"] = new JObject { ["faceIndices"] = new JArray(0), ["distance"] = 1.0f, }, }; var result = ToJObject(ManageProBuilder.HandleCommand(extrudeParams)); Assert.IsTrue(result.Value("success"), result.ToString()); int newFaceCount = result["data"].Value("faceCount"); Assert.Greater(newFaceCount, initialFaceCount, "Face count should increase after extrusion"); } [Test] public void DeleteFaces_DecreasesFaceCount() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestDeleteCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestDeleteCube"); _createdObjects.Add(go); int initialFaceCount = createResult["data"].Value("faceCount"); var deleteParams = new JObject { ["action"] = "delete_faces", ["target"] = "PBTestDeleteCube", ["properties"] = new JObject { ["faceIndices"] = new JArray(0), }, }; var result = ToJObject(ManageProBuilder.HandleCommand(deleteParams)); Assert.IsTrue(result.Value("success"), result.ToString()); int newFaceCount = result["data"].Value("faceCount"); Assert.Less(newFaceCount, initialFaceCount, "Face count should decrease after deletion"); } [Test] public void SetFaceMaterial_WithMissingTarget_ReturnsError() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var paramsObj = new JObject { ["action"] = "set_face_material", ["target"] = "NonExistentObject999", ["properties"] = new JObject { ["faceIndices"] = new JArray(0), ["materialPath"] = "Assets/Materials/Test.mat", }, }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsFalse(result.Value("success"), result.ToString()); } [Test] public void FlipNormals_SucceedsOnValidMesh() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestFlipCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestFlipCube"); _createdObjects.Add(go); var flipParams = new JObject { ["action"] = "flip_normals", ["target"] = "PBTestFlipCube", ["properties"] = new JObject { ["faceIndices"] = new JArray(0, 1), }, }; var result = ToJObject(ManageProBuilder.HandleCommand(flipParams)); Assert.IsTrue(result.Value("success"), result.ToString()); } // ===================================================================== // Enhanced get_mesh_info with include parameter // ===================================================================== [Test] public void GetMeshInfo_DefaultInclude_ReturnsSummaryOnly() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestSummaryCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestSummaryCube"); Assert.IsNotNull(go); _createdObjects.Add(go); var infoParams = new JObject { ["action"] = "get_mesh_info", ["target"] = "PBTestSummaryCube", }; var result = ToJObject(ManageProBuilder.HandleCommand(infoParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data); Assert.Greater(data.Value("faceCount"), 0); // Default "summary" should NOT include faces array Assert.IsNull(data["faces"], "Summary mode should not include faces array"); } [Test] public void GetMeshInfo_IncludeFaces_ReturnsFaceNormalsAndDirections() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestFacesCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestFacesCube"); Assert.IsNotNull(go); _createdObjects.Add(go); var infoParams = new JObject { ["action"] = "get_mesh_info", ["target"] = "PBTestFacesCube", ["properties"] = new JObject { ["include"] = "faces" }, }; var result = ToJObject(ManageProBuilder.HandleCommand(infoParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data); var faces = data["faces"] as JArray; Assert.IsNotNull(faces, "Faces mode should include faces array"); Assert.Greater(faces.Count, 0); // Each face should have normal, center, direction var firstFace = faces[0] as JObject; Assert.IsNotNull(firstFace); Assert.IsNotNull(firstFace["normal"], "Face should have normal"); Assert.IsNotNull(firstFace["center"], "Face should have center"); // direction may be null for angled faces, but should exist as key Assert.IsTrue(firstFace.ContainsKey("direction"), "Face should have direction key"); } [Test] public void GetMeshInfo_IncludeEdges_ReturnsEdgeData() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestEdgesCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestEdgesCube"); Assert.IsNotNull(go); _createdObjects.Add(go); var infoParams = new JObject { ["action"] = "get_mesh_info", ["target"] = "PBTestEdgesCube", ["properties"] = new JObject { ["include"] = "edges" }, }; var result = ToJObject(ManageProBuilder.HandleCommand(infoParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data); var edges = data["edges"] as JArray; Assert.IsNotNull(edges, "Edges mode should include edges array"); Assert.Greater(edges.Count, 0); var firstEdge = edges[0] as JObject; Assert.IsNotNull(firstEdge); Assert.IsTrue(firstEdge.ContainsKey("vertexA"), "Edge should have vertexA"); Assert.IsTrue(firstEdge.ContainsKey("vertexB"), "Edge should have vertexB"); } [Test] public void GetMeshInfo_CubeTopFace_HasUpNormal() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestTopNormalCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestTopNormalCube"); Assert.IsNotNull(go); _createdObjects.Add(go); var infoParams = new JObject { ["action"] = "get_mesh_info", ["target"] = "PBTestTopNormalCube", ["properties"] = new JObject { ["include"] = "faces" }, }; var result = ToJObject(ManageProBuilder.HandleCommand(infoParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var faces = result["data"]["faces"] as JArray; Assert.IsNotNull(faces); // At least one face should have direction "top" bool hasTop = false; foreach (JObject face in faces) { if (face["direction"]?.ToString() == "top") { hasTop = true; break; } } Assert.IsTrue(hasTop, "A cube should have at least one face with direction 'top'"); } // ===================================================================== // Smoothing // ===================================================================== [Test] public void AutoSmooth_DefaultAngle_AssignsGroups() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestAutoSmoothCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestAutoSmoothCube"); Assert.IsNotNull(go); _createdObjects.Add(go); var smoothParams = new JObject { ["action"] = "auto_smooth", ["target"] = "PBTestAutoSmoothCube", ["properties"] = new JObject { ["angleThreshold"] = 30 }, }; var result = ToJObject(ManageProBuilder.HandleCommand(smoothParams)); Assert.IsTrue(result.Value("success"), result.ToString()); } [Test] public void SetSmoothing_OnSpecificFaces_SetsGroup() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestSetSmoothCube", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestSetSmoothCube"); Assert.IsNotNull(go); _createdObjects.Add(go); var smoothParams = new JObject { ["action"] = "set_smoothing", ["target"] = "PBTestSetSmoothCube", ["properties"] = new JObject { ["faceIndices"] = new JArray(0, 1), ["smoothingGroup"] = 1, }, }; var result = ToJObject(ManageProBuilder.HandleCommand(smoothParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data); Assert.AreEqual(2, data.Value("facesModified")); } // ===================================================================== // Mesh Utilities // ===================================================================== [Test] public void CenterPivot_MovesPivotToCenter() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestCenterPivot", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestCenterPivot"); Assert.IsNotNull(go); _createdObjects.Add(go); var pivotParams = new JObject { ["action"] = "center_pivot", ["target"] = "PBTestCenterPivot", }; var result = ToJObject(ManageProBuilder.HandleCommand(pivotParams)); Assert.IsTrue(result.Value("success"), result.ToString()); } [Test] public void FreezeTransform_ResetsTransformKeepsShape() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestFreeze", ["position"] = new JArray(5f, 3f, 2f), }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestFreeze"); Assert.IsNotNull(go); _createdObjects.Add(go); var freezeParams = new JObject { ["action"] = "freeze_transform", ["target"] = "PBTestFreeze", }; var result = ToJObject(ManageProBuilder.HandleCommand(freezeParams)); Assert.IsTrue(result.Value("success"), result.ToString()); // Transform should be reset to identity Assert.AreEqual(Vector3.zero, go.transform.position); Assert.AreEqual(Quaternion.identity, go.transform.rotation); Assert.AreEqual(Vector3.one, go.transform.localScale); } [Test] public void ValidateMesh_CleanMesh_ReturnsNoIssues() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestValidate", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestValidate"); Assert.IsNotNull(go); _createdObjects.Add(go); var validateParams = new JObject { ["action"] = "validate_mesh", ["target"] = "PBTestValidate", }; var result = ToJObject(ManageProBuilder.HandleCommand(validateParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data); Assert.IsTrue(data.Value("healthy"), "Fresh cube should be healthy"); Assert.AreEqual(0, data.Value("degenerateTriangles")); } [Test] public void RepairMesh_OnCleanMesh_ReportsNoChanges() { if (!_proBuilderInstalled) { Assert.Pass("ProBuilder not installed - skipping."); return; } var createParams = new JObject { ["action"] = "create_shape", ["properties"] = new JObject { ["shapeType"] = "Cube", ["name"] = "PBTestRepair", }, }; var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams)); Assert.IsTrue(createResult.Value("success"), createResult.ToString()); var go = GameObject.Find("PBTestRepair"); Assert.IsNotNull(go); _createdObjects.Add(go); var repairParams = new JObject { ["action"] = "repair_mesh", ["target"] = "PBTestRepair", }; var result = ToJObject(ManageProBuilder.HandleCommand(repairParams)); Assert.IsTrue(result.Value("success"), result.ToString()); var data = result["data"] as JObject; Assert.IsNotNull(data); Assert.AreEqual(0, data.Value("degenerateTrianglesRemoved")); } // ===================================================================== // ProBuilder not installed fallback // ===================================================================== [Test] public void AllActions_WithoutProBuilder_ReturnPackageError() { if (_proBuilderInstalled) { Assert.Pass("ProBuilder IS installed - this test verifies the not-installed path."); return; } string[] testActions = { "ping", "create_shape", "get_mesh_info", "extrude_faces", "auto_smooth", "set_smoothing", "center_pivot", "validate_mesh", }; foreach (var action in testActions) { var paramsObj = new JObject { ["action"] = action }; var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj)); Assert.IsFalse(result.Value("success"), $"Action '{action}' should fail without ProBuilder: {result}"); Assert.That(result["error"]?.ToString(), Does.Contain("ProBuilder").IgnoreCase, $"Error for '{action}' should mention ProBuilder"); } } } }