Add Unity Pipeline package support
This commit is contained in:
+85
@@ -0,0 +1,85 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.Assets;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the create_folder reference command (CLI-190), exercised directly and via
|
||||
/// PipelineClient. Verifies the result envelope, recursive creation, Assets-relative paths,
|
||||
/// and the traversal guard.
|
||||
/// </summary>
|
||||
public class FolderCommandsTests
|
||||
{
|
||||
private const string Root = "Assets/__CLI190Test";
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
// Start from the default authoring root so a leftover EditorPref (from another test or an
|
||||
// interrupted run) can't shift where bare paths resolve mid-suite.
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
if (AssetDatabase.IsValidFolder(Root))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Root);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
#region Direct
|
||||
|
||||
[Test]
|
||||
public void CreateFolder_ExplicitAssetsPath_CreatesFoldersAndReturnsIdentity()
|
||||
{
|
||||
var result = FolderCommands.CreateFolder(Root + "/Sub/Leaf");
|
||||
|
||||
Assert.IsTrue(AssetDatabase.IsValidFolder(Root + "/Sub/Leaf"), "Nested folders should exist");
|
||||
Assert.AreEqual(Root + "/Sub/Leaf", result.AssetPath);
|
||||
Assert.IsNotEmpty(result.Guid, "Created folder should have a GUID");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateFolder_BarePath_ResolvesUnderAssets()
|
||||
{
|
||||
// No "Assets/" prefix — should resolve under the default authoring root (Assets/).
|
||||
var result = FolderCommands.CreateFolder("__CLI190Test/Bare");
|
||||
|
||||
Assert.AreEqual("Assets/__CLI190Test/Bare", result.AssetPath);
|
||||
Assert.IsTrue(AssetDatabase.IsValidFolder("Assets/__CLI190Test/Bare"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateFolder_Traversal_Throws()
|
||||
{
|
||||
Assert.Throws<System.ArgumentException>(() => FolderCommands.CreateFolder("../Outside"));
|
||||
Assert.Throws<System.ArgumentException>(() => FolderCommands.CreateFolder("Assets/../Outside"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ViaClient
|
||||
|
||||
[Test]
|
||||
public void CreateFolder_ViaClient_Succeeds()
|
||||
{
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("create_folder", new { path = Root + "/ViaClient" });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"create_folder should succeed: {response.Error}");
|
||||
Assert.IsTrue(response.HasValidJson, "Response should have valid JSON");
|
||||
Assert.IsTrue(response.JsonResponse.ContainsKey("result"), "Should have result field");
|
||||
Assert.IsTrue(AssetDatabase.IsValidFolder(Root + "/ViaClient"), "Folder should be created");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4653539f2cf4a1b4699bd2c7a8d35188
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for <see cref="ObjectRefConverter"/>: 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.
|
||||
/// </summary>
|
||||
public class ObjectRefConverterTests
|
||||
{
|
||||
private static ObjectRef Parse(string handle) =>
|
||||
JsonConvert.DeserializeObject<ObjectRef>("\"" + 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<ObjectRef>(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<ObjectRef>(json);
|
||||
Assert.AreEqual("/Player", back.HierarchyPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Null_DeserializesToNull()
|
||||
{
|
||||
Assert.IsNull(JsonConvert.DeserializeObject<ObjectRef>("null"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Number_BareInteger_IsInstanceId()
|
||||
{
|
||||
Assert.AreEqual(ObjectId.FromRaw(48184), JsonConvert.DeserializeObject<ObjectRef>("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
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b218af7c5c4799a4ab37d7a670a6256e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
using Unity.Pipeline;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the object-reference resolver foundation (CLI-190): each handle form resolves back
|
||||
/// to the same object, and Describe produces a canonical identity.
|
||||
/// </summary>
|
||||
public class ObjectResolverTests
|
||||
{
|
||||
private const string AssetFolder = "Assets/AUTHAPI9_Res";
|
||||
|
||||
private GameObject m_SceneObject;
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (m_SceneObject != null)
|
||||
Object.DestroyImmediate(m_SceneObject);
|
||||
m_SceneObject = null;
|
||||
|
||||
if (AssetDatabase.IsValidFolder(AssetFolder))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(AssetFolder);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static string CreateMaterialAsset(string name)
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder(AssetFolder))
|
||||
AssetDatabase.CreateFolder("Assets", "AUTHAPI9_Res");
|
||||
|
||||
var shader = Shader.Find("Standard")
|
||||
?? Shader.Find("Universal Render Pipeline/Lit")
|
||||
?? Shader.Find("Sprites/Default");
|
||||
var mat = new Material(shader);
|
||||
var path = $"{AssetFolder}/{name}.mat";
|
||||
AssetDatabase.CreateAsset(mat, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
return path;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Describe_SceneObject_ProducesInstanceIdAndHierarchyPath()
|
||||
{
|
||||
m_SceneObject = new GameObject("CLI190_Root");
|
||||
var child = new GameObject("Child");
|
||||
child.transform.SetParent(m_SceneObject.transform);
|
||||
|
||||
var info = ObjectResolver.Describe(child);
|
||||
|
||||
Assert.IsNotNull(info);
|
||||
Assert.AreEqual(PipelineUtils.GetObjectId(child), info.InstanceId);
|
||||
Assert.AreEqual("/CLI190_Root/Child", info.HierarchyPath);
|
||||
Assert.AreEqual("GameObject", info.Type);
|
||||
Assert.IsNull(info.AssetPath, "A scene object should not report an asset path");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_ByInstanceId_ReturnsSameObject()
|
||||
{
|
||||
m_SceneObject = new GameObject("CLI190_ById");
|
||||
var handle = new ObjectRef { InstanceId = PipelineUtils.GetObjectId(m_SceneObject) };
|
||||
|
||||
Assert.IsTrue(ObjectResolver.TryResolve(handle, out var obj, out var error), error);
|
||||
Assert.AreSame(m_SceneObject, obj);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_ByHierarchyPath_ReturnsSameObject()
|
||||
{
|
||||
m_SceneObject = new GameObject("CLI190_ByPath");
|
||||
var handle = new ObjectRef { HierarchyPath = "/CLI190_ByPath" };
|
||||
|
||||
Assert.IsTrue(ObjectResolver.TryResolve(handle, out var obj, out var error), error);
|
||||
Assert.AreSame(m_SceneObject, obj);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_EmptyHandle_FailsWithError()
|
||||
{
|
||||
Assert.IsFalse(ObjectResolver.TryResolve(new ObjectRef(), out _, out var error));
|
||||
Assert.IsNotEmpty(error);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_UnknownGuid_FailsWithError()
|
||||
{
|
||||
var handle = new ObjectRef { Guid = "00000000000000000000000000000000" };
|
||||
Assert.IsFalse(ObjectResolver.TryResolve(handle, out _, out var error));
|
||||
Assert.IsNotEmpty(error);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_ByRelativeAssetPath_NormalizesUnderAuthoringRoot()
|
||||
{
|
||||
// A path without the "Assets/" prefix (AUTHAPI-9) resolves under the authoring root.
|
||||
var full = CreateMaterialAsset("Floor");
|
||||
var expected = AssetDatabase.LoadMainAssetAtPath(full);
|
||||
var handle = new ObjectRef { Path = "AUTHAPI9_Res/Floor.mat" };
|
||||
|
||||
Assert.IsTrue(ObjectResolver.TryResolve(handle, out var obj, out var error), error);
|
||||
Assert.AreSame(expected, obj);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_ExplicitAssetsPath_StillResolves()
|
||||
{
|
||||
var full = CreateMaterialAsset("Wall");
|
||||
var handle = new ObjectRef { Path = full };
|
||||
|
||||
Assert.IsTrue(ObjectResolver.TryResolve(handle, out var obj, out var error), error);
|
||||
Assert.AreSame(AssetDatabase.LoadMainAssetAtPath(full), obj);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_DottedGameObjectName_FallsBackToHierarchy()
|
||||
{
|
||||
// A dotted name (e.g. "Cube.001") is routed to Path by the string converter but is really a
|
||||
// scene object; the Path branch falls back to a hierarchy lookup.
|
||||
m_SceneObject = new GameObject("Cube.001");
|
||||
var handle = new ObjectRef { Path = "Cube.001" };
|
||||
|
||||
Assert.IsTrue(ObjectResolver.TryResolve(handle, out var obj, out var error), error);
|
||||
Assert.AreSame(m_SceneObject, obj);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_UnresolvableRelativePath_ErrorListsEveryStrategy()
|
||||
{
|
||||
var handle = new ObjectRef { Path = "AUTHAPI9_Res/Missing.mat" };
|
||||
|
||||
Assert.IsFalse(ObjectResolver.TryResolve(handle, out _, out var error));
|
||||
StringAssert.Contains("no asset at", error);
|
||||
StringAssert.Contains("no GameObject at hierarchy path", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e864b00059a57364d902089021b33742
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for ProjectPaths (CLI-190): Assets-relative resolution, the traversal/out-of-root
|
||||
/// guards, and the configurable authoring root.
|
||||
/// </summary>
|
||||
public class ProjectPathsTests
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
// Reset to the default root before each test so a leftover EditorPref can't leak in.
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultRoot_IsAssets()
|
||||
{
|
||||
ProjectPaths.ResetAuthoringRoot();
|
||||
Assert.AreEqual("Assets", ProjectPaths.AuthoringRoot);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_BarePath_PrependsAssets()
|
||||
{
|
||||
var resolved = ProjectPaths.Resolve("Gameplay/Enemies", out var error);
|
||||
Assert.IsNull(error, error);
|
||||
Assert.AreEqual("Assets/Gameplay/Enemies", resolved);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_ExplicitAssetsPath_Unchanged()
|
||||
{
|
||||
var resolved = ProjectPaths.Resolve("Assets/Gameplay", out var error);
|
||||
Assert.IsNull(error, error);
|
||||
Assert.AreEqual("Assets/Gameplay", resolved);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_Traversal_Fails()
|
||||
{
|
||||
Assert.IsNull(ProjectPaths.Resolve("../Outside", out var error));
|
||||
Assert.IsNotEmpty(error);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_EmptyPath_Fails()
|
||||
{
|
||||
Assert.IsNull(ProjectPaths.Resolve("", out var error));
|
||||
Assert.IsNotEmpty(error);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConfigurableRoot_ConfinesBareAndRejectsOutside()
|
||||
{
|
||||
ProjectPaths.AuthoringRoot = "Assets/__CLI190Root";
|
||||
|
||||
// Bare path resolves under the configured root.
|
||||
var inside = ProjectPaths.Resolve("Foo/Bar", out var insideError);
|
||||
Assert.IsNull(insideError, insideError);
|
||||
Assert.AreEqual("Assets/__CLI190Root/Foo/Bar", inside);
|
||||
|
||||
// Explicit path outside the configured root is rejected (confinement).
|
||||
Assert.IsNull(ProjectPaths.Resolve("Assets/Other", out var outsideError));
|
||||
Assert.IsNotEmpty(outsideError);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetAuthoringRoot_OutsideAssets_Throws()
|
||||
{
|
||||
Assert.Throws<System.ArgumentException>(() => ProjectPaths.AuthoringRoot = "Outside");
|
||||
Assert.Throws<System.ArgumentException>(() => ProjectPaths.AuthoringRoot = "Assets/../Escape");
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9af4698d059fd9d43b72c6e1dad56424
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user