unity-mcp/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/PythonToolRegistryServiceTe...

136 lines
4.4 KiB
C#
Raw Normal View History

Allow users to easily add tools in the Asset folder (#324) * Fix issue #308: Find py files in MCPForUnityTools and version.txt This allows for auto finding new tools. A good dir on a custom tool would look like this: CustomTool/ ├── CustomTool.MCPEnabler.asmdef ├── CustomTool.MCPEnabler.asmdef.meta ├── ExternalAssetToolFunction.cs ├── ExternalAssetToolFunction.cs.meta ├── external_asset_tool_function.py ├── external_asset_tool_function.py.meta ├── version.txt └── version.txt.meta CS files are left in the tools folder. The asmdef is recommended to allow for dependency on MCPForUnity when MCP For Unity is installed: asmdef example { "name": "CustomTool.MCPEnabler", "rootNamespace": "MCPForUnity.Editor.Tools", "references": [ "CustomTool", "MCPForUnity.Editor" ], "includePlatforms": [ "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], "versionDefines": [], "noEngineReferences": false } * Follow-up: address CodeRabbit feedback for #308 (<GetToolsFolderIdentifier was duplicated>) * Follow-up: address CodeRabbit feedback for #308 – centralize GetToolsFolderIdentifier, fix tools copy dir, and limit scan scope * Fixing so the MCP don't removes _skipDirs e.g. __pycache__ * skip empty folders with no py files * Rabbit: "Fix identifier collision between different package roots." * Update MCPForUnity/Editor/Helpers/ServerInstaller.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Rabbbit: Cleanup may delete server’s built-in tool subfolders — restrict to managed names. * Fixed minor + missed onadding rabit change * Revert "Fixed minor + missed onadding rabit change" This reverts commit 571ca8c5de3d07da3791dad558677909a07e886d. * refactor: remove Unity project tools copying and version tracking functionality * refactor: consolidate module discovery logic into shared utility function * Remove unused imports * feat: add Python tool registry and sync system for MCP server integration * feat: add auto-sync processor for Python tools with Unity editor integration * feat: add menu item to reimport all Python files in project Good to give users a manual option * Fix infinite loop error Don't react to PythonToolAsset changes - it only needs to react to Python file changes. And we also batch asset edits to minimise the DB refreshes * refactor: move Python tool sync menu items under Window/MCP For Unity/Tool Sync * Update docs * Remove duplicate header * feat: add OnValidate handler to sync Python tools when asset is modified This fixes the issue with deletions in the asset, now file removals are synced * test: add unit tests for Python tools asset and sync services * Update MCPForUnity/Editor/Helpers/PythonToolSyncProcessor.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * style: remove trailing whitespace from Python tool sync files * test: remove incomplete unit tests from ToolSyncServiceTests * perf: optimize Python file reimport by using AssetDatabase.FindAssets instead of GetAllAssetPaths --------- Co-authored-by: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-10-18 12:18:25 +08:00
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using MCPForUnity.Editor.Data;
using MCPForUnity.Editor.Services;
namespace MCPForUnityTests.Editor.Services
{
public class PythonToolRegistryServiceTests
{
private PythonToolRegistryService _service;
[SetUp]
public void SetUp()
{
_service = new PythonToolRegistryService();
}
[Test]
public void GetAllRegistries_ReturnsEmptyList_WhenNoPythonToolsAssetsExist()
{
var registries = _service.GetAllRegistries().ToList();
// Note: This might find assets in the test project, so we just verify it doesn't throw
Assert.IsNotNull(registries, "Should return a non-null list");
}
[Test]
public void NeedsSync_ReturnsTrue_WhenHashingDisabled()
{
var asset = ScriptableObject.CreateInstance<PythonToolsAsset>();
asset.useContentHashing = false;
var textAsset = new TextAsset("print('test')");
bool needsSync = _service.NeedsSync(asset, textAsset);
Assert.IsTrue(needsSync, "Should always need sync when hashing is disabled");
Object.DestroyImmediate(asset);
}
[Test]
public void NeedsSync_ReturnsTrue_WhenFileNotPreviouslySynced()
{
var asset = ScriptableObject.CreateInstance<PythonToolsAsset>();
asset.useContentHashing = true;
var textAsset = new TextAsset("print('test')");
bool needsSync = _service.NeedsSync(asset, textAsset);
Assert.IsTrue(needsSync, "Should need sync for new file");
Object.DestroyImmediate(asset);
}
[Test]
public void NeedsSync_ReturnsFalse_WhenHashMatches()
{
var asset = ScriptableObject.CreateInstance<PythonToolsAsset>();
asset.useContentHashing = true;
var textAsset = new TextAsset("print('test')");
// First sync
_service.RecordSync(asset, textAsset);
// Check if needs sync again
bool needsSync = _service.NeedsSync(asset, textAsset);
Assert.IsFalse(needsSync, "Should not need sync when hash matches");
Object.DestroyImmediate(asset);
}
[Test]
public void RecordSync_StoresFileState()
{
var asset = ScriptableObject.CreateInstance<PythonToolsAsset>();
var textAsset = new TextAsset("print('test')");
_service.RecordSync(asset, textAsset);
Assert.AreEqual(1, asset.fileStates.Count, "Should have one file state recorded");
Assert.IsNotNull(asset.fileStates[0].contentHash, "Hash should be stored");
Assert.IsNotNull(asset.fileStates[0].assetGuid, "GUID should be stored");
Object.DestroyImmediate(asset);
}
[Test]
public void RecordSync_UpdatesExistingState_WhenFileAlreadyRecorded()
{
var asset = ScriptableObject.CreateInstance<PythonToolsAsset>();
var textAsset = new TextAsset("print('test')");
// Record twice
_service.RecordSync(asset, textAsset);
var firstHash = asset.fileStates[0].contentHash;
_service.RecordSync(asset, textAsset);
Assert.AreEqual(1, asset.fileStates.Count, "Should still have only one state");
Assert.AreEqual(firstHash, asset.fileStates[0].contentHash, "Hash should remain the same");
Object.DestroyImmediate(asset);
}
[Test]
public void ComputeHash_ReturnsSameHash_ForSameContent()
{
var textAsset1 = new TextAsset("print('hello')");
var textAsset2 = new TextAsset("print('hello')");
string hash1 = _service.ComputeHash(textAsset1);
string hash2 = _service.ComputeHash(textAsset2);
Assert.AreEqual(hash1, hash2, "Same content should produce same hash");
}
[Test]
public void ComputeHash_ReturnsDifferentHash_ForDifferentContent()
{
var textAsset1 = new TextAsset("print('hello')");
var textAsset2 = new TextAsset("print('world')");
string hash1 = _service.ComputeHash(textAsset1);
string hash2 = _service.ComputeHash(textAsset2);
Assert.AreNotEqual(hash1, hash2, "Different content should produce different hash");
}
}
}