unity-mcp/Server/tests/integration/test_manage_scriptable_obje...

131 lines
4.1 KiB
Python
Raw Normal View History

import asyncio
from .test_helpers import DummyContext
import services.tools.manage_scriptable_object as mod
def test_manage_scriptable_object_forwards_create_params(monkeypatch):
captured = {}
async def fake_async_send(cmd, params, **kwargs):
captured["cmd"] = cmd
captured["params"] = params
return {"success": True, "data": {"ok": True}}
monkeypatch.setattr(mod, "async_send_command_with_retry", fake_async_send)
ctx = DummyContext()
ctx.set_state("unity_instance", "UnityMCPTests@dummy")
result = asyncio.run(
mod.manage_scriptable_object(
ctx=ctx,
action="create",
type_name="My.Namespace.TestDefinition",
folder_path="Assets/Temp/Foo",
asset_name="Bar",
overwrite="true",
patches='[{"propertyPath":"displayName","op":"set","value":"Hello"}]',
)
)
assert result["success"] is True
assert captured["cmd"] == "manage_scriptable_object"
assert captured["params"]["action"] == "create"
assert captured["params"]["typeName"] == "My.Namespace.TestDefinition"
assert captured["params"]["folderPath"] == "Assets/Temp/Foo"
assert captured["params"]["assetName"] == "Bar"
assert captured["params"]["overwrite"] is True
assert isinstance(captured["params"]["patches"], list)
assert captured["params"]["patches"][0]["propertyPath"] == "displayName"
def test_manage_scriptable_object_forwards_modify_params(monkeypatch):
captured = {}
async def fake_async_send(cmd, params, **kwargs):
captured["cmd"] = cmd
captured["params"] = params
return {"success": True, "data": {"ok": True}}
monkeypatch.setattr(mod, "async_send_command_with_retry", fake_async_send)
ctx = DummyContext()
ctx.set_state("unity_instance", "UnityMCPTests@dummy")
result = asyncio.run(
mod.manage_scriptable_object(
ctx=ctx,
action="modify",
target='{"guid":"abc"}',
patches=[{"propertyPath": "materials.Array.size", "op": "array_resize", "value": 2}],
)
)
assert result["success"] is True
assert captured["cmd"] == "manage_scriptable_object"
assert captured["params"]["action"] == "modify"
assert captured["params"]["target"] == {"guid": "abc"}
assert captured["params"]["patches"][0]["op"] == "array_resize"
Harden `manage_scriptable_object` Tool (#522) * feat(manage_scriptable_object): harden tool with path normalization, auto-resize, bulk mapping Phase 1: Path Syntax & Auto-Resizing - Add NormalizePropertyPath() to convert field[index] to Array.data format - Add EnsureArrayCapacity() to auto-grow arrays when targeting out-of-bounds indices Phase 2: Consolidation - Replace duplicate TryGet* helpers with ParamCoercion/VectorParsing shared utilities - Add Vector4 parsing support to VectorParsing.cs Phase 3: Bulk Data Mapping - Handle JArray values for list/array properties (recursive element setting) - Handle JObject values for nested struct/class properties Phase 4: Enhanced Reference Resolution - Support plain 32-char GUID strings for ObjectReference fields Phase 5: Validation & Dry-Run - Add ValidatePatches() for pre-validation of all patches - Add dry_run parameter to validate without mutating Includes comprehensive stress test suite covering: - Big Bang (large nested arrays), Out of Bounds, Friendly Path Syntax - Deep Nesting, Mixed References, Rapid Fire, Type Mismatch - Bulk Array Mapping, GUID Shorthand, Dry Run validation * feat: Add AnimationCurve and Quaternion support to manage_scriptable_object tool - Implement TrySetAnimationCurve() supporting both {'keys': [...]} and direct [...] formats * Support keyframe properties: time, value, inSlope, outSlope, weightedMode, inWeight, outWeight * Gracefully default missing optional fields to 0 * Clear error messages for malformed structures - Implement TrySetQuaternion() with 4 input formats: * Euler array [x, y, z] - 3 elements interpreted as degrees * Raw array [x, y, z, w] - 4 components * Object format {x, y, z, w} - explicit components * Explicit euler {euler: [x, y, z]} - labeled format - Improve error handling: * Null values: AnimationCurve→empty, Quaternion→identity * Invalid inputs rejected with specific, actionable error messages * Validate keyframe objects and array sizes - Add comprehensive test coverage in ManageScriptableObjectStressTests.cs: * AnimationCurve with keyframe array format * AnimationCurve with direct array (no wrapper) * Quaternion via Euler angles * Quaternion via raw components * Quaternion via object format * Quaternion via explicit euler property - Fix test file compilation issues: * Replace undefined TestFolder with _runRoot * Add System.IO using statement * refactor: consolidate test utilities to eliminate duplication - Add TestUtilities.cs with shared helpers: - ToJObject() - consolidates 11 duplicates across test files - EnsureFolder() - consolidates 2 duplicates - WaitForUnityReady() - consolidates 2 duplicates - FindFallbackShader() - consolidates shader chain duplicates - SafeDeleteAsset() - helper for asset cleanup - CleanupEmptyParentFolders() - standardizes TearDown cleanup - Update 11 test files to use shared TestUtilities via 'using static' - Standardize TearDown cleanup patterns across all test files - Net reduction of ~40 lines while improving maintainability * fix: add missing animCurve and rotation fields to ComplexStressSO Add AnimationCurve and Quaternion fields required by Phase 6 stress tests.
2026-01-07 22:46:35 +08:00
def test_manage_scriptable_object_forwards_dry_run_param(monkeypatch):
captured = {}
async def fake_async_send(cmd, params, **kwargs):
captured["cmd"] = cmd
captured["params"] = params
return {"success": True, "data": {"dryRun": True, "validationResults": []}}
monkeypatch.setattr(mod, "async_send_command_with_retry", fake_async_send)
ctx = DummyContext()
ctx.set_state("unity_instance", "UnityMCPTests@dummy")
result = asyncio.run(
mod.manage_scriptable_object(
ctx=ctx,
action="modify",
target='{"guid":"abc123"}',
patches=[{"propertyPath": "intValue", "op": "set", "value": 42}],
dry_run=True,
)
)
assert result["success"] is True
assert captured["cmd"] == "manage_scriptable_object"
assert captured["params"]["action"] == "modify"
assert captured["params"]["dryRun"] is True
assert captured["params"]["target"] == {"guid": "abc123"}
def test_manage_scriptable_object_dry_run_string_coercion(monkeypatch):
"""Test that dry_run accepts string 'true' and coerces to boolean."""
captured = {}
async def fake_async_send(cmd, params, **kwargs):
captured["cmd"] = cmd
captured["params"] = params
return {"success": True, "data": {"dryRun": True}}
monkeypatch.setattr(mod, "async_send_command_with_retry", fake_async_send)
ctx = DummyContext()
ctx.set_state("unity_instance", "UnityMCPTests@dummy")
result = asyncio.run(
mod.manage_scriptable_object(
ctx=ctx,
action="modify",
target={"guid": "xyz"},
patches=[],
dry_run="true", # String instead of bool
)
)
assert result["success"] is True
assert captured["params"]["dryRun"] is True