Skip to content

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Oct 24, 2025

  • Add friendly-name/property aliasing in ManageAsset: metallic_Metallic, smoothness_Smoothness/_Glossiness, albedo_BaseMap/_MainTex
  • Support structured texture blocks case-insensitively:
    • Accept "texture" key regardless of casing
    • Accept "name"/"Name" and "path"/"Path"
    • If name omitted, resolve _BaseMap/_MainTex automatically
  • Keep direct assignments working: {"_BaseMap": "Assets/.../Tex.asset"}

Tests

  • Strengthen MCPToolParameterTests:
    • Accept multiple shaders (URP/HDRP/Standard/Unlit) via helper
    • Add float _Metallic create/modify test
    • Add texture assignment tests (direct and structured)
    • Add end-to-end flow with unique GUIDs; tolerance-based color assertion for pipeline variance
    • Assert warning on invalid JSON
  • Python: add simple JSON parsing tests for properties coercion

Misc

  • Update TestProjects/UnityMCPTests/Packages/manifest.json
  • Update ProjectSettings/ProjectVersion.txt for test project
  • Unity Test ci can be triggered from GitHub actions panel (adds 'workflow' option)

Result

  • Live/manual tests pass for JSON strings, float/alias properties, direct and structured texture assignment.
  • No breaking changes expected; structured texture support is additive.

Summary by CodeRabbit

  • New Features

    • Added manual trigger option for test workflow in CI/CD pipeline
  • Bug Fixes

    • Improved material property application with case-insensitive handling and more robust texture resolution
  • Tests

    • Expanded test coverage for JSON parsing and material creation/modification workflows
    • Added comprehensive end-to-end material property handling scenarios
  • Chores

    • Updated Unity project version to 2021.3.45f2
    • Switched test package dependency to local reference

dsarno and others added 8 commits October 24, 2025 08:25
…ercion and end-to-end; update test project manifest and ProjectVersion
…ly; resolve _BaseMap/_MainTex automatically and apply when missing name
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…red texture (supports 'albedo'); tests: self-sufficient texture asset and _BaseColor/_Color guards; python: assert success in invalid JSON case
…valid-json case tolerant; ensure prefab modify test patches transport correctly
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Walkthrough

This PR enhances material property handling in ManageAsset with case-insensitive, alias-aware shader property resolution and texture assignment, adds comprehensive unit and integration tests for JSON parsing and material operations across C# and Python, updates project infrastructure including a local package reference and Unity version downgrade, and improves test mocking strategies for async operations.

Changes

Cohort / File(s) Summary
Workflow & Infrastructure
​.github/workflows/unity-tests.yml, TestProjects/UnityMCPTests/Packages/manifest.json, TestProjects/UnityMCPTests/ProjectSettings/ProjectVersion.txt
Adds workflow_dispatch trigger for manual workflow execution; switches MCPForUnity package source from remote GitHub URL to local file path; downgrades Unity project version from 2022.3.6f1 to 2021.3.45f2
Material Property Resolution
MCPForUnity/Editor/Tools/ManageAsset.cs
Refactors HandleCommand to precompute properties; enhances ApplyMaterialProperties with case-insensitive key handling, alias-aware shader property name resolution (e.g., albedo → _BaseMap, metallic → _Smoothness), robust texture path resolution, and flexible texture assignment via top-level or nested texture objects
C\# Test Coverage
TestProjects/UnityMCPTests/Assets/Tests/EditMode/MCPToolParameterTests.cs
Adds AssertShaderIsSupported helper; introduces 6 new test methods covering JSON string parsing, material creation/modification with textures and float properties, invalid JSON handling, and end-to-end property scenarios; enhances existing tests with shader validation assertions
Python JSON Parsing Tests
tests/test_json_parsing_simple.py, tests/test_manage_asset_json_parsing.py
Adds unit tests for JSON parsing logic with edge cases; introduces pytest-based tests for JSON string coercion in manage_asset and manage_gameobject flows, validating logging and success payloads across valid JSON, invalid JSON, dict, and None inputs
Test Mocking Enhancement
tests/test_script_tools.py
Updates test_manage_asset_prefab_modify_request to patch async_send_command_with_retry at both module and function-global scope via closure assignment, ensuring mock visibility within the function's execution context

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller
    participant ManageAsset as ManageAsset.ApplyMaterialProperties
    participant PropResolver as Property Name Resolver
    participant TextureLoader as Texture Loader
    participant Material as Material

    Caller->>ManageAsset: Apply properties dict
    
    loop For each property
        ManageAsset->>PropResolver: Resolve key (case-insensitive, alias-aware)<br/>(e.g., "albedo" → "_BaseMap")
        PropResolver-->>ManageAsset: Canonical shader property
        
        alt Texture Property
            ManageAsset->>TextureLoader: Resolve texture path<br/>from top-level or nested "texture"
            TextureLoader-->>ManageAsset: Loaded texture or null
            ManageAsset->>Material: Assign texture to<br/>resolved shader property
        else Float/Color Property
            ManageAsset->>Material: Assign value to<br/>resolved shader property
        end
    end
    
    ManageAsset-->>Caller: Material updated
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes involve mixed complexity across heterogeneous file types: case-insensitive property resolution logic with alias mapping in ManageAsset requires careful reasoning; seven new C# test methods follow established patterns but cover diverse scenarios (JSON parsing, textures, floats, end-to-end); new Python test infrastructure is straightforward but spans multiple test files; test mocking updates involve subtle closure-level patching requiring focused attention.

Possibly related PRs

Poem

🐰 ✨ A rabbit hops through shader names with glee,
Case-insensitive aliases dance wild and free,
From albedo to _BaseMap, textures align,
Tests bloom in Python and C# so fine,
Material magic—now robust and keen! 🎨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "fix: JSON material property handling + tests (manage_asset) #90" is directly related to the main changes in the changeset. The title accurately captures the primary focus: improvements to JSON material property handling in ManageAsset (including case-insensitive resolution, property aliasing, and texture handling) along with comprehensive test additions in both C# and Python. The title is specific and clear—it indicates scope through "(manage_asset)" and conveys meaningful information without vague terms or excessive detail. While the changeset includes secondary modifications like workflow additions and project configuration updates, the title appropriately prioritizes the main developer intent, which is acceptable per the check principles.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
.github/workflows/unity-tests.yml (1)

4-4: Good addition; consider small CI hardening (optional).

Add:

  • pull_request trigger to validate PRs,
  • a concurrency group to cancel superseded runs,
  • minimal permissions.

Example:

 on:
+  pull_request:
+    branches: [main]
   workflow_dispatch: {}
   push:
     branches: [main]
     paths:
       - TestProjects/UnityMCPTests/**
       - MCPForUnity/Editor/**
       - .github/workflows/unity-tests.yml
+
+concurrency:
+  group: unity-tests-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
tests/test_script_tools.py (1)

181-186: Use monkeypatch.setitem for globals to avoid leakage across tests.

Direct assignment persists beyond the test. Use monkeypatch so it auto-reverts.

-# Patch both at the module and at the function closure location
-monkeypatch.setattr(tools_manage_asset,
-                    "async_send_command_with_retry", fake_async)
-# Also patch the globals of the function object (handles dynamically loaded module alias)
-manage_asset.__globals__["async_send_command_with_retry"] = fake_async
+# Patch both at the module and at the function closure location
+monkeypatch.setattr(tools_manage_asset, "async_send_command_with_retry", fake_async)
+monkeypatch.setitem(manage_asset.__globals__, "async_send_command_with_retry", fake_async)
MCPForUnity/Editor/Tools/ManageAsset.cs (3)

992-1031: Structured texture block is robust; minor enhancement suggestion.

Case-insensitive keys and default to _BaseMap look good. Consider also accepting a plain string under "texture" (treat as path, defaulting name to _BaseMap) for symmetry with direct assignments.

-// Example: Set texture property (case-insensitive key and subkeys)
+// Example: Set texture property (case-insensitive key and subkeys; string path supported)
 {
-    JObject texProps = null;
+    JObject texProps = null;
+    // Allow: "texture": "Assets/.."
+    var textureToken = properties.Properties()
+        .FirstOrDefault(p => string.Equals(p.Name, "texture", StringComparison.OrdinalIgnoreCase))?.Value;
+    if (textureToken != null && textureToken.Type == JTokenType.String)
+    {
+        texProps = new JObject { ["path"] = textureToken };
+    }
-    var direct = properties.Property("texture");
+    var direct = properties.Property("texture");
     if (direct != null && direct.Value is JObject t0) texProps = t0;
     ...
 }

1041-1066: Alias resolver: add a few common shader aliases.

Add HDRP/Standard texture aliases so friendly names resolve more often.

 switch (lower)
 {
   case "_color": candidates = new[] { "_Color", "_BaseColor" }; break;
   case "_basecolor": candidates = new[] { "_BaseColor", "_Color" }; break;
   case "_maintex": candidates = new[] { "_MainTex", "_BaseMap" }; break;
   case "_basemap": candidates = new[] { "_BaseMap", "_MainTex" }; break;
   case "_glossiness": candidates = new[] { "_Glossiness", "_Smoothness" }; break;
   case "_smoothness": candidates = new[] { "_Smoothness", "_Glossiness" }; break;
   // Friendly names → shader property names
   case "metallic": candidates = new[] { "_Metallic" }; break;
   case "smoothness": candidates = new[] { "_Smoothness", "_Glossiness" }; break;
   case "albedo": candidates = new[] { "_BaseMap", "_MainTex" }; break;
+  case "maskmap": candidates = new[] { "_MaskMap", "_MetallicGlossMap" }; break;         // HDRP/Standard packed map
+  case "metallicglossmap": candidates = new[] { "_MetallicGlossMap", "_MaskMap" }; break; // Standard/HDRP
+  case "occlusion": candidates = new[] { "_OcclusionMap" }; break;
+  case "normal": candidates = new[] { "_BumpMap", "_NormalMap" }; break;
   default: candidates = new[] { name }; break;
 }

1067-1137: Direct assignments: warn on missing textures and reuse aliasing in structured blocks.

  • Emit a warning if a direct texture path fails to load (parity with structured block).
  • Reuse ResolvePropertyName in structured "float"/"color" too so aliases like "smoothness"/"_BaseColor" work consistently.
 // Float: single number
 if (v.Type == JTokenType.Float || v.Type == JTokenType.Integer)
 {
-    if (mat.HasProperty(shaderProp))
+    if (mat.HasProperty(shaderProp))
     {
         try
         {
             float f = v.ToObject<float>();
             if (!Mathf.Approximately(mat.GetFloat(shaderProp), f))
             {
                 mat.SetFloat(shaderProp, f);
                 modified = true;
             }
         }
         catch (Exception ex)
         {
             Debug.LogWarning($"Error setting float '{shaderProp}': {ex.Message}");
         }
     }
     continue;
 }
 
 // Texture: string path
 if (v.Type == JTokenType.String)
 {
     string texPath = v.ToString();
     if (!string.IsNullOrEmpty(texPath) && mat.HasProperty(shaderProp))
     {
         var tex = AssetDatabase.LoadAssetAtPath<Texture>(AssetPathUtility.SanitizeAssetPath(texPath));
-        if (tex != null && mat.GetTexture(shaderProp) != tex)
+        if (tex != null && mat.GetTexture(shaderProp) != tex)
         {
             mat.SetTexture(shaderProp, tex);
             modified = true;
         }
+        else if (tex == null)
+        {
+            Debug.LogWarning($"Texture not found at path: {texPath}");
+        }
     }
     continue;
 }

Additionally, near the structured blocks above:

- string propName = colorProps["name"]?.ToString() ?? "_Color";
+ string propName = ResolvePropertyName(colorProps["name"]?.ToString() ?? "_Color");

- string propName = floatProps["name"]?.ToString();
+ string propName = ResolvePropertyName(floatProps["name"]?.ToString());
tests/test_json_parsing_simple.py (1)

14-20: Optional: Prefer explicit else for clarity.

The return statement at line 20 is only reached when properties is not a string. Per the static analysis hint, consider moving it into an explicit else block to make the control flow more apparent.

Apply this diff:

     if isinstance(properties, str):
         try:
             parsed = json.loads(properties)
             return parsed, "success"
         except json.JSONDecodeError as e:
             return properties, f"failed to parse: {e}"
-    return properties, "no_parsing_needed"
+    else:
+        return properties, "no_parsing_needed"
TestProjects/UnityMCPTests/Assets/Tests/EditMode/MCPToolParameterTests.cs (1)

466-697: LGTM! Comprehensive end-to-end validation of all property handling scenarios.

This integration test thoroughly validates all 10 scenarios described in the PR objectives: JSON string parsing, friendly name aliases (metallic, smoothness, albedo), structured blocks, direct texture assignments, GameObject material assignment, pipeline-specific properties, invalid JSON handling, shader switching, and mixed key types.

The test design is solid:

  • Uses unique GUID suffix to avoid collisions
  • Tolerance-based color comparisons (line 665) handle pipeline variance
  • Tests skip gracefully when optional textures are missing (lines 547-563, 566-585)
  • Proper cleanup ensures no test pollution

Optional: Consider splitting into smaller focused tests.

At 230+ lines, this test is at the upper limit for maintainability. If a scenario fails, it can be harder to pinpoint which of the 10 sub-tests caused the issue. Consider extracting each scenario into a separate test method, sharing setup/cleanup via fixtures or helper methods. This would:

  • Improve test isolation and failure diagnosis
  • Allow running individual scenarios independently
  • Make the test suite easier to maintain as scenarios evolve

However, as an integration test that validates the full flow, the current structure is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4643802 and 37d3f96.

📒 Files selected for processing (8)
  • .github/workflows/unity-tests.yml (1 hunks)
  • MCPForUnity/Editor/Tools/ManageAsset.cs (3 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/MCPToolParameterTests.cs (6 hunks)
  • TestProjects/UnityMCPTests/Packages/manifest.json (1 hunks)
  • TestProjects/UnityMCPTests/ProjectSettings/ProjectVersion.txt (1 hunks)
  • tests/test_json_parsing_simple.py (1 hunks)
  • tests/test_manage_asset_json_parsing.py (1 hunks)
  • tests/test_script_tools.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tests/test_script_tools.py (2)
MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py (1)
  • manage_asset (16-92)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (1)
  • manage_asset (15-83)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/MCPToolParameterTests.cs (3)
MCPForUnity/Editor/Tools/ManageGameObject.cs (4)
  • GameObject (1284-1307)
  • HandleCommand (42-202)
  • Color (2124-2137)
  • UnityEngine (2177-2274)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialDirectPropertiesTests.cs (3)
  • Test (89-154)
  • JObject (84-87)
  • Texture2D (72-82)
MCPForUnity/Editor/Tools/ManageAsset.cs (4)
  • HandleCommand (46-127)
  • DeleteAsset (511-539)
  • CreateFolder (296-349)
  • CreateAsset (163-294)
tests/test_manage_asset_json_parsing.py (1)
tests/test_helpers.py (2)
  • info (3-4)
  • warning (6-7)
🪛 Ruff (0.14.1)
tests/test_json_parsing_simple.py

17-17: Consider moving this statement to an else block

(TRY300)

tests/test_manage_asset_json_parsing.py

22-22: Unused function argument: cmd

(ARG001)


22-22: Unused function argument: params

(ARG001)


22-22: Unused function argument: loop

(ARG001)


49-49: Unused function argument: cmd

(ARG001)


49-49: Unused function argument: params

(ARG001)


49-49: Unused function argument: loop

(ARG001)


72-72: Unused function argument: cmd

(ARG001)


72-72: Unused function argument: params

(ARG001)


72-72: Unused function argument: loop

(ARG001)


97-97: Unused function argument: cmd

(ARG001)


97-97: Unused function argument: params

(ARG001)


97-97: Unused function argument: loop

(ARG001)


127-127: Unused function argument: cmd

(ARG001)


127-127: Unused function argument: params

(ARG001)

🔇 Additional comments (13)
TestProjects/UnityMCPTests/ProjectSettings/ProjectVersion.txt (1)

1-2: Version aligns with CI matrix.

EditorVersion 2021.3.45f2 matches unity-tests.yml matrix. No action.

MCPForUnity/Editor/Tools/ManageAsset.cs (1)

91-92: LGTM: pass parsed properties to ModifyAsset.

Keeps parsing centralized and avoids re-reading raw tokens.

TestProjects/UnityMCPTests/Packages/manifest.json (1)

3-3: Local package path verified and resolves correctly.

The relative path ../../../MCPForUnity resolves to the MCPForUnity directory at the repository root, and the directory exists. No UPM import failures will occur from path resolution issues.

tests/test_json_parsing_simple.py (1)

26-108: LGTM! Comprehensive test coverage.

The test suite covers the key scenarios: valid JSON parsing, invalid JSON handling, dict/None passthrough, complex nested structures, empty objects, and malformed edge cases. The assertions are clear and appropriate.

tests/test_manage_asset_json_parsing.py (2)

14-112: LGTM! Mock signatures are intentionally complete.

The tests properly verify JSON string coercion behavior and logging. The static analysis warnings about unused function arguments in the mock functions (lines 22, 49, 72, 97) are false positives—mocks need to match the expected signature of async_send_command_with_retry even if they don't use all parameters.


119-143: LGTM! Synchronous mock matches manage_gameobject's transport.

The test correctly uses a synchronous mock for send_command_with_retry, which aligns with manage_gameobject's usage pattern. The static analysis warnings about unused arguments (line 127) are false positives for the same reason as above.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/MCPToolParameterTests.cs (7)

22-32: LGTM! Good cross-pipeline shader validation.

The helper centralizes shader validation and accepts the common fallback shaders across URP, HDRP, and Built-in pipelines. This makes the tests resilient to pipeline-specific defaults.


65-67: LGTM! Shader verification strengthens existing tests.

Adding shader assertions to existing tests ensures that material creation and modification operations preserve the expected shader. This catches regressions in shader handling.

Also applies to: 124-134, 185-189


200-238: LGTM! Clean test for JSON string material creation.

The test validates JSON string parsing during material creation, uses unique GUIDs to avoid collisions, and has proper cleanup. The assertions verify both shader and color properties.


240-291: LGTM! Validates JSON parsing for modify operations.

The test covers the modify path by creating a material and then modifying it via JSON string properties. The two-step verification ensures the modification was applied correctly.


293-325: LGTM! Proper validation of error handling.

The test verifies that invalid JSON is handled gracefully without crashing. Using LogAssert.Expect with a regex pattern is appropriate for matching warning messages, and the flexible assertion accepts either success with defaults or graceful failure.


327-384: LGTM! Thorough float property validation with tolerance.

The test validates the metallic_Metallic alias mapping for both create and modify operations. Using tolerance-based comparison (1e-3f) and checking HasProperty before access are good practices for shader property tests.


386-464: LGTM! Flexible texture assignment test handles pipeline variance.

The test validates texture assignment via both _BaseMap (URP) and _MainTex (Standard) aliases. Creating the texture inline if missing makes the test self-sufficient. The dual property check (hasTexture logic at lines 435-437 and 452-454) appropriately handles shader differences across pipelines.

@dsarno dsarno merged commit faf9aff into CoplayDev:main Oct 24, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant