using NUnit.Framework; using Newtonsoft.Json; using Unity.Pipeline; using Unity.Pipeline.Models; using UnityEngine; using Object = UnityEngine.Object; namespace Unity.Pipeline.Tests.Editor.Authoring { /// /// Tests for : a single canonical string handle coerces into the /// matching ObjectRef field (mirroring ObjectResolver/ToString priority), structured objects still /// deserialize fully, and serialization still emits a JSON object. Plus one end-to-end ViaClient /// test proving a string target flows through the real /api/exec dispatch path. /// public class ObjectRefConverterTests { private static ObjectRef Parse(string handle) => JsonConvert.DeserializeObject("\"" + handle + "\""); #region String coercion (one field per canonical form) [Test] public void String_HierarchyPath_LeadingSlash() { var r = Parse("/Player"); Assert.AreEqual("/Player", r.HierarchyPath); Assert.IsNull(r.GlobalId); Assert.IsNull(r.Path); Assert.IsNull(r.Guid); Assert.IsNull(r.InstanceId); Assert.IsNull(r.FileId); } [Test] public void String_HierarchyPath_Nested() { Assert.AreEqual("/Level/Floor", Parse("/Level/Floor").HierarchyPath); } [Test] public void String_GlobalId() { var r = Parse("GlobalObjectId_V1-2-abc123def4560000abc123def4560000-1095335325-0"); Assert.AreEqual("GlobalObjectId_V1-2-abc123def4560000abc123def4560000-1095335325-0", r.GlobalId); Assert.IsNull(r.HierarchyPath); } [Test] public void String_AssetPath() { var r = Parse("Assets/Prefabs/Enemy.prefab"); Assert.AreEqual("Assets/Prefabs/Enemy.prefab", r.Path); Assert.IsNull(r.HierarchyPath); } [Test] public void String_GuidWithFileId() { var r = Parse("guid:a1b2c3d4e5f6:123"); Assert.AreEqual("a1b2c3d4e5f6", r.Guid); Assert.AreEqual(123L, r.FileId); } [Test] public void String_GuidOnly_Prefixed() { var r = Parse("guid:a1b2c3d4e5f6"); Assert.AreEqual("a1b2c3d4e5f6", r.Guid); Assert.IsNull(r.FileId); } [Test] public void String_InstanceId_Prefixed_Negative() { #if !UNITY_6000_4_OR_NEWER Assert.AreEqual(ObjectId.FromRaw(-3070), Parse("instanceId:-3070").InstanceId); #else Assert.Ignore("Negative int instance ids do not exist in the EntityId (ulong) model on 6000.4+."); #endif } [Test] public void String_PlainNegativeInt_IsInstanceId() { #if !UNITY_6000_4_OR_NEWER var r = Parse("-3070"); Assert.AreEqual(ObjectId.FromRaw(-3070), r.InstanceId); Assert.IsNull(r.HierarchyPath); #else Assert.Ignore("Negative int instance ids do not exist in the EntityId (ulong) model on 6000.4+."); #endif } [Test] public void String_PlainPositiveInt_IsInstanceId() { Assert.AreEqual(ObjectId.FromRaw(48184), Parse("48184").InstanceId); } [Test] public void String_32Hex_IsGuid() { var r = Parse("a1b2c3d4e5f60718293a4b5c6d7e8f90"); Assert.AreEqual("a1b2c3d4e5f60718293a4b5c6d7e8f90", r.Guid); Assert.IsNull(r.InstanceId); } [Test] public void String_BareName_FallsBackToHierarchyPath() { var r = Parse("Enemy_1"); Assert.AreEqual("Enemy_1", r.HierarchyPath); Assert.IsNull(r.InstanceId); Assert.IsNull(r.Guid); } #endregion #region Authoring-root-relative asset paths (AUTHAPI-9) [Test] public void String_RelativePathWithExtension_IsPath() { // No "Assets/" prefix but a file extension: an authoring-root-relative asset path. var r = Parse("Materials/FloorA.mat"); Assert.AreEqual("Materials/FloorA.mat", r.Path); Assert.IsNull(r.HierarchyPath); } [Test] public void String_BareFileWithExtension_IsPath() { var r = Parse("Enemy.prefab"); Assert.AreEqual("Enemy.prefab", r.Path); Assert.IsNull(r.HierarchyPath); } [Test] public void String_RelativeNoExtension_StaysHierarchyPath() { // No extension and no leading slash: still a scene hierarchy path, not an asset candidate. var r = Parse("Root/Child"); Assert.AreEqual("Root/Child", r.HierarchyPath); Assert.IsNull(r.Path); } [Test] public void String_LeadingSlashWithExtension_StaysHierarchyPath() { // A leading slash marks a canonical hierarchy path even when a name looks dotted. var r = Parse("/Root/Cube.001"); Assert.AreEqual("/Root/Cube.001", r.HierarchyPath); Assert.IsNull(r.Path); } #endregion #region Object form, write, null [Test] public void Object_Structured_DeserializesAllFields() { var json = "{\"globalId\":\"G\",\"path\":\"Assets/A\",\"guid\":\"a1\",\"fileId\":42,\"instanceId\":7,\"hierarchyPath\":\"/H\"}"; var r = JsonConvert.DeserializeObject(json); Assert.AreEqual("G", r.GlobalId); Assert.AreEqual("Assets/A", r.Path); Assert.AreEqual("a1", r.Guid); Assert.AreEqual(42L, r.FileId); Assert.AreEqual(ObjectId.FromRaw(7), r.InstanceId); Assert.AreEqual("/H", r.HierarchyPath); } [Test] public void WriteJson_EmitsObject_NotString_AndRoundTrips() { var json = JsonConvert.SerializeObject(new ObjectRef { HierarchyPath = "/Player" }); StringAssert.StartsWith("{", json, "ObjectRef must serialize as a JSON object, not a bare string"); StringAssert.Contains("\"hierarchyPath\":\"/Player\"", json); var back = JsonConvert.DeserializeObject(json); Assert.AreEqual("/Player", back.HierarchyPath); } [Test] public void Null_DeserializesToNull() { Assert.IsNull(JsonConvert.DeserializeObject("null")); } [Test] public void Number_BareInteger_IsInstanceId() { Assert.AreEqual(ObjectId.FromRaw(48184), JsonConvert.DeserializeObject("48184").InstanceId); } #endregion #region End-to-end via the real dispatch path [Test] public void SetTransform_StringTarget_ResolvesAndApplies() { var go = new GameObject("CLI_ObjRefDispatch"); try { using (var server = new PipelineTestServer()) { var resp = server.Execute("set_transform", new { target = "/CLI_ObjRefDispatch", position = new[] { 1f, 2f, 3f } }); Assert.IsTrue(resp.IsSuccess, $"set_transform with a string target should succeed (string->ObjectRef coercion): {resp.Error}"); } Assert.AreEqual(new Vector3(1f, 2f, 3f), go.transform.localPosition, "The string handle should have resolved to the GameObject and applied the transform"); } finally { Object.DestroyImmediate(go); } } #endregion } }