using System.Linq; using MCPForUnity.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NUnit.Framework; using UnityEngine; namespace MCPForUnityTests.Editor.Helpers { /// /// Tests for Matrix4x4Converter to ensure it safely serializes matrices /// without accessing dangerous computed properties (lossyScale, rotation). /// Regression test for https://github.com/CoplayDev/unity-mcp/issues/478 /// public class Matrix4x4ConverterTests { private JsonSerializerSettings _settings; [SetUp] public void SetUp() { _settings = new JsonSerializerSettings { Converters = { new Matrix4x4Converter() } }; } [Test] public void Serialize_IdentityMatrix_ReturnsCorrectJson() { var matrix = Matrix4x4.identity; var json = JsonConvert.SerializeObject(matrix, _settings); Assert.That(json, Does.Contain("\"m00\":1")); Assert.That(json, Does.Contain("\"m11\":1")); Assert.That(json, Does.Contain("\"m22\":1")); Assert.That(json, Does.Contain("\"m33\":1")); Assert.That(json, Does.Contain("\"m01\":0")); } [Test] public void Deserialize_IdentityMatrix_ReturnsIdentity() { var original = Matrix4x4.identity; var json = JsonConvert.SerializeObject(original, _settings); var result = JsonConvert.DeserializeObject(json, _settings); Assert.That(result, Is.EqualTo(original)); } [Test] public void Serialize_TranslationMatrix_PreservesValues() { var matrix = Matrix4x4.Translate(new Vector3(10, 20, 30)); var json = JsonConvert.SerializeObject(matrix, _settings); var result = JsonConvert.DeserializeObject(json, _settings); Assert.That(result.m03, Is.EqualTo(10f)); Assert.That(result.m13, Is.EqualTo(20f)); Assert.That(result.m23, Is.EqualTo(30f)); } [Test] public void Serialize_DegenerateMatrix_DoesNotCrashAndRoundtrips() { // This is the key test - a degenerate matrix that would crash // if we accessed lossyScale or rotation properties var matrix = new Matrix4x4(); matrix.m00 = 0; matrix.m11 = 0; matrix.m22 = 0; // Degenerate - determinant = 0 // This should NOT throw or crash - the old code would fail here var json = JsonConvert.SerializeObject(matrix, _settings); var result = JsonConvert.DeserializeObject(json, _settings); // Verify JSON only contains raw mXY properties var jo = JObject.Parse(json); var expectedProps = new[] { "m00", "m01", "m02", "m03", "m10", "m11", "m12", "m13", "m20", "m21", "m22", "m23", "m30", "m31", "m32", "m33" }; CollectionAssert.AreEquivalent(expectedProps, jo.Properties().Select(p => p.Name).ToArray()); // Verify values roundtrip correctly (all zeros for degenerate matrix) Assert.That(result.m00, Is.EqualTo(0f)); Assert.That(result.m11, Is.EqualTo(0f)); Assert.That(result.m22, Is.EqualTo(0f)); Assert.That(result, Is.EqualTo(matrix)); } [Test] public void Serialize_NonTRSMatrix_DoesNotCrash() { // Projection matrices are NOT valid TRS matrices // Accessing lossyScale/rotation on them causes ValidTRS() assertion var matrix = Matrix4x4.Perspective(60f, 1.77f, 0.1f, 1000f); // Verify it's not a valid TRS matrix Assert.That(matrix.ValidTRS(), Is.False, "Test requires non-TRS matrix"); // This should NOT throw - the fix ensures we never access computed properties Assert.DoesNotThrow(() => { var json = JsonConvert.SerializeObject(matrix, _settings); var result = JsonConvert.DeserializeObject(json, _settings); }); } [Test] public void Deserialize_NullToken_ReturnsZeroMatrix() { var json = "null"; var result = JsonConvert.DeserializeObject(json, _settings); // Returns zero matrix (consistent with missing field defaults of 0f) Assert.That(result, Is.EqualTo(new Matrix4x4())); } [Test] public void Serialize_DoesNotContainDangerousProperties() { var matrix = Matrix4x4.TRS(Vector3.one, Quaternion.identity, Vector3.one); var json = JsonConvert.SerializeObject(matrix, _settings); // Ensure we're not serializing the dangerous computed properties Assert.That(json, Does.Not.Contain("lossyScale")); Assert.That(json, Does.Not.Contain("rotation")); Assert.That(json, Does.Not.Contain("inverse")); Assert.That(json, Does.Not.Contain("transpose")); } } }