Notify users when there's a new version (#329)
* feat: add package update service with version check and GitHub integration * feat: add migration warning banner and dialog for legacy package users * test: remove redundant cache expiration and clearing tests from PackageUpdateService * test: add package update service tests for expired cache and asset store installationsmain
parent
350337813b
commit
673456b701
|
|
@ -0,0 +1,60 @@
|
||||||
|
namespace MCPForUnity.Editor.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Service for checking package updates and version information
|
||||||
|
/// </summary>
|
||||||
|
public interface IPackageUpdateService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a newer version of the package is available
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentVersion">The current package version</param>
|
||||||
|
/// <returns>Update check result containing availability and latest version info</returns>
|
||||||
|
UpdateCheckResult CheckForUpdate(string currentVersion);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two version strings to determine if the first is newer than the second
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version1">First version string</param>
|
||||||
|
/// <param name="version2">Second version string</param>
|
||||||
|
/// <returns>True if version1 is newer than version2</returns>
|
||||||
|
bool IsNewerVersion(string version1, string version2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the package was installed via Git or Asset Store
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if installed via Git, false if Asset Store or unknown</returns>
|
||||||
|
bool IsGitInstallation();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the cached update check data, forcing a fresh check on next request
|
||||||
|
/// </summary>
|
||||||
|
void ClearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of an update check operation
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateCheckResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether an update is available
|
||||||
|
/// </summary>
|
||||||
|
public bool UpdateAvailable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The latest version available (null if check failed or no update)
|
||||||
|
/// </summary>
|
||||||
|
public string LatestVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the check was successful (false if network error, etc.)
|
||||||
|
/// </summary>
|
||||||
|
public bool CheckSucceeded { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional message about the check result
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e94ae28f193184e4fb5068f62f4f00c6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -13,6 +13,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
private static IPythonToolRegistryService _pythonToolRegistryService;
|
private static IPythonToolRegistryService _pythonToolRegistryService;
|
||||||
private static ITestRunnerService _testRunnerService;
|
private static ITestRunnerService _testRunnerService;
|
||||||
private static IToolSyncService _toolSyncService;
|
private static IToolSyncService _toolSyncService;
|
||||||
|
private static IPackageUpdateService _packageUpdateService;
|
||||||
|
|
||||||
public static IBridgeControlService Bridge => _bridgeService ??= new BridgeControlService();
|
public static IBridgeControlService Bridge => _bridgeService ??= new BridgeControlService();
|
||||||
public static IClientConfigurationService Client => _clientService ??= new ClientConfigurationService();
|
public static IClientConfigurationService Client => _clientService ??= new ClientConfigurationService();
|
||||||
|
|
@ -20,6 +21,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
public static IPythonToolRegistryService PythonToolRegistry => _pythonToolRegistryService ??= new PythonToolRegistryService();
|
public static IPythonToolRegistryService PythonToolRegistry => _pythonToolRegistryService ??= new PythonToolRegistryService();
|
||||||
public static ITestRunnerService Tests => _testRunnerService ??= new TestRunnerService();
|
public static ITestRunnerService Tests => _testRunnerService ??= new TestRunnerService();
|
||||||
public static IToolSyncService ToolSync => _toolSyncService ??= new ToolSyncService();
|
public static IToolSyncService ToolSync => _toolSyncService ??= new ToolSyncService();
|
||||||
|
public static IPackageUpdateService Updates => _packageUpdateService ??= new PackageUpdateService();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a custom implementation for a service (useful for testing)
|
/// Registers a custom implementation for a service (useful for testing)
|
||||||
|
|
@ -40,6 +42,8 @@ namespace MCPForUnity.Editor.Services
|
||||||
_testRunnerService = t;
|
_testRunnerService = t;
|
||||||
else if (implementation is IToolSyncService ts)
|
else if (implementation is IToolSyncService ts)
|
||||||
_toolSyncService = ts;
|
_toolSyncService = ts;
|
||||||
|
else if (implementation is IPackageUpdateService pu)
|
||||||
|
_packageUpdateService = pu;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -53,6 +57,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
(_pythonToolRegistryService as IDisposable)?.Dispose();
|
(_pythonToolRegistryService as IDisposable)?.Dispose();
|
||||||
(_testRunnerService as IDisposable)?.Dispose();
|
(_testRunnerService as IDisposable)?.Dispose();
|
||||||
(_toolSyncService as IDisposable)?.Dispose();
|
(_toolSyncService as IDisposable)?.Dispose();
|
||||||
|
(_packageUpdateService as IDisposable)?.Dispose();
|
||||||
|
|
||||||
_bridgeService = null;
|
_bridgeService = null;
|
||||||
_clientService = null;
|
_clientService = null;
|
||||||
|
|
@ -60,6 +65,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
_pythonToolRegistryService = null;
|
_pythonToolRegistryService = null;
|
||||||
_testRunnerService = null;
|
_testRunnerService = null;
|
||||||
_toolSyncService = null;
|
_toolSyncService = null;
|
||||||
|
_packageUpdateService = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Service for checking package updates from GitHub
|
||||||
|
/// </summary>
|
||||||
|
public class PackageUpdateService : IPackageUpdateService
|
||||||
|
{
|
||||||
|
private const string LastCheckDateKey = "MCPForUnity.LastUpdateCheck";
|
||||||
|
private const string CachedVersionKey = "MCPForUnity.LatestKnownVersion";
|
||||||
|
private const string PackageJsonUrl = "https://raw.githubusercontent.com/CoplayDev/unity-mcp/main/MCPForUnity/package.json";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public UpdateCheckResult CheckForUpdate(string currentVersion)
|
||||||
|
{
|
||||||
|
// Check cache first - only check once per day
|
||||||
|
string lastCheckDate = EditorPrefs.GetString(LastCheckDateKey, "");
|
||||||
|
string cachedLatestVersion = EditorPrefs.GetString(CachedVersionKey, "");
|
||||||
|
|
||||||
|
if (lastCheckDate == DateTime.Now.ToString("yyyy-MM-dd") && !string.IsNullOrEmpty(cachedLatestVersion))
|
||||||
|
{
|
||||||
|
return new UpdateCheckResult
|
||||||
|
{
|
||||||
|
CheckSucceeded = true,
|
||||||
|
LatestVersion = cachedLatestVersion,
|
||||||
|
UpdateAvailable = IsNewerVersion(cachedLatestVersion, currentVersion),
|
||||||
|
Message = "Using cached version check"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't check for Asset Store installations
|
||||||
|
if (!IsGitInstallation())
|
||||||
|
{
|
||||||
|
return new UpdateCheckResult
|
||||||
|
{
|
||||||
|
CheckSucceeded = false,
|
||||||
|
UpdateAvailable = false,
|
||||||
|
Message = "Asset Store installations are updated via Unity Asset Store"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch latest version from GitHub
|
||||||
|
string latestVersion = FetchLatestVersionFromGitHub();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(latestVersion))
|
||||||
|
{
|
||||||
|
// Cache the result
|
||||||
|
EditorPrefs.SetString(LastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
|
||||||
|
EditorPrefs.SetString(CachedVersionKey, latestVersion);
|
||||||
|
|
||||||
|
return new UpdateCheckResult
|
||||||
|
{
|
||||||
|
CheckSucceeded = true,
|
||||||
|
LatestVersion = latestVersion,
|
||||||
|
UpdateAvailable = IsNewerVersion(latestVersion, currentVersion),
|
||||||
|
Message = "Successfully checked for updates"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UpdateCheckResult
|
||||||
|
{
|
||||||
|
CheckSucceeded = false,
|
||||||
|
UpdateAvailable = false,
|
||||||
|
Message = "Failed to check for updates (network issue or offline)"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsNewerVersion(string version1, string version2)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Remove any "v" prefix
|
||||||
|
version1 = version1.TrimStart('v', 'V');
|
||||||
|
version2 = version2.TrimStart('v', 'V');
|
||||||
|
|
||||||
|
var version1Parts = version1.Split('.');
|
||||||
|
var version2Parts = version2.Split('.');
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.Min(version1Parts.Length, version2Parts.Length); i++)
|
||||||
|
{
|
||||||
|
if (int.TryParse(version1Parts[i], out int v1Num) &&
|
||||||
|
int.TryParse(version2Parts[i], out int v2Num))
|
||||||
|
{
|
||||||
|
if (v1Num > v2Num) return true;
|
||||||
|
if (v1Num < v2Num) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsGitInstallation()
|
||||||
|
{
|
||||||
|
// Git packages are installed via Package Manager and have a package.json in Packages/
|
||||||
|
// Asset Store packages are in Assets/
|
||||||
|
string packageRoot = AssetPathUtility.GetMcpPackageRootPath();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(packageRoot))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the package is in Packages/ it's a PM install (likely Git)
|
||||||
|
// If it's in Assets/ it's an Asset Store install
|
||||||
|
return packageRoot.StartsWith("Packages/", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ClearCache()
|
||||||
|
{
|
||||||
|
EditorPrefs.DeleteKey(LastCheckDateKey);
|
||||||
|
EditorPrefs.DeleteKey(CachedVersionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the latest version from GitHub's main branch package.json
|
||||||
|
/// </summary>
|
||||||
|
private string FetchLatestVersionFromGitHub()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// GitHub API endpoint (Option 1 - has rate limits):
|
||||||
|
// https://api.github.com/repos/CoplayDev/unity-mcp/releases/latest
|
||||||
|
//
|
||||||
|
// We use Option 2 (package.json directly) because:
|
||||||
|
// - No API rate limits (GitHub serves raw files freely)
|
||||||
|
// - Simpler - just parse JSON for version field
|
||||||
|
// - More reliable - doesn't require releases to be published
|
||||||
|
// - Direct source of truth from the main branch
|
||||||
|
|
||||||
|
using (var client = new WebClient())
|
||||||
|
{
|
||||||
|
client.Headers.Add("User-Agent", "Unity-MCPForUnity-UpdateChecker");
|
||||||
|
string jsonContent = client.DownloadString(PackageJsonUrl);
|
||||||
|
|
||||||
|
var packageJson = JObject.Parse(jsonContent);
|
||||||
|
string version = packageJson["version"]?.ToString();
|
||||||
|
|
||||||
|
return string.IsNullOrEmpty(version) ? null : version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Silent fail - don't interrupt the user if network is unavailable
|
||||||
|
McpLog.Info($"Update check failed (this is normal if offline): {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7c3c2304b14e9485ca54182fad73b035
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -242,7 +242,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
private void InitializeUI()
|
private void InitializeUI()
|
||||||
{
|
{
|
||||||
// Settings Section
|
// Settings Section
|
||||||
versionLabel.text = AssetPathUtility.GetPackageVersion();
|
UpdateVersionLabel();
|
||||||
debugLogsToggle.value = EditorPrefs.GetBool("MCPForUnity.DebugLogs", false);
|
debugLogsToggle.value = EditorPrefs.GetBool("MCPForUnity.DebugLogs", false);
|
||||||
|
|
||||||
validationLevelField.Init(ValidationLevel.Standard);
|
validationLevelField.Init(ValidationLevel.Standard);
|
||||||
|
|
@ -833,5 +833,28 @@ namespace MCPForUnity.Editor.Windows
|
||||||
EditorGUIUtility.systemCopyBuffer = configJsonField.value;
|
EditorGUIUtility.systemCopyBuffer = configJsonField.value;
|
||||||
McpLog.Info("Configuration copied to clipboard");
|
McpLog.Info("Configuration copied to clipboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateVersionLabel()
|
||||||
|
{
|
||||||
|
string currentVersion = AssetPathUtility.GetPackageVersion();
|
||||||
|
versionLabel.text = $"v{currentVersion}";
|
||||||
|
|
||||||
|
// Check for updates using the service
|
||||||
|
var updateCheck = MCPServiceLocator.Updates.CheckForUpdate(currentVersion);
|
||||||
|
|
||||||
|
if (updateCheck.UpdateAvailable && !string.IsNullOrEmpty(updateCheck.LatestVersion))
|
||||||
|
{
|
||||||
|
// Update available - enhance the label
|
||||||
|
versionLabel.text = $"\u2191 v{currentVersion} (Update available: v{updateCheck.LatestVersion})";
|
||||||
|
versionLabel.style.color = new Color(1f, 0.7f, 0f); // Orange
|
||||||
|
versionLabel.tooltip = $"Version {updateCheck.LatestVersion} is available. Update via Package Manager.\n\nGit URL: https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
versionLabel.style.color = StyleKeyword.Null; // Default color
|
||||||
|
versionLabel.tooltip = $"Current version: {currentVersion}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,295 @@
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEditor;
|
||||||
|
using MCPForUnity.Editor.Services;
|
||||||
|
|
||||||
|
namespace MCPForUnityTests.Editor.Services
|
||||||
|
{
|
||||||
|
public class PackageUpdateServiceTests
|
||||||
|
{
|
||||||
|
private PackageUpdateService _service;
|
||||||
|
private const string TestLastCheckDateKey = "MCPForUnity.LastUpdateCheck";
|
||||||
|
private const string TestCachedVersionKey = "MCPForUnity.LatestKnownVersion";
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
_service = new PackageUpdateService();
|
||||||
|
|
||||||
|
// Clean up any existing test data
|
||||||
|
CleanupEditorPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
// Clean up test data
|
||||||
|
CleanupEditorPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupEditorPrefs()
|
||||||
|
{
|
||||||
|
if (EditorPrefs.HasKey(TestLastCheckDateKey))
|
||||||
|
{
|
||||||
|
EditorPrefs.DeleteKey(TestLastCheckDateKey);
|
||||||
|
}
|
||||||
|
if (EditorPrefs.HasKey(TestCachedVersionKey))
|
||||||
|
{
|
||||||
|
EditorPrefs.DeleteKey(TestCachedVersionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_ReturnsTrue_WhenMajorVersionIsNewer()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("2.0.0", "1.0.0");
|
||||||
|
Assert.IsTrue(result, "2.0.0 should be newer than 1.0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_ReturnsTrue_WhenMinorVersionIsNewer()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("1.2.0", "1.1.0");
|
||||||
|
Assert.IsTrue(result, "1.2.0 should be newer than 1.1.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_ReturnsTrue_WhenPatchVersionIsNewer()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("1.0.2", "1.0.1");
|
||||||
|
Assert.IsTrue(result, "1.0.2 should be newer than 1.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_ReturnsFalse_WhenVersionsAreEqual()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("1.0.0", "1.0.0");
|
||||||
|
Assert.IsFalse(result, "Same versions should return false");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_ReturnsFalse_WhenVersionIsOlder()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("1.0.0", "2.0.0");
|
||||||
|
Assert.IsFalse(result, "1.0.0 should not be newer than 2.0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_HandlesVersionPrefix_v()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("v2.0.0", "v1.0.0");
|
||||||
|
Assert.IsTrue(result, "Should handle 'v' prefix correctly");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_HandlesVersionPrefix_V()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("V2.0.0", "V1.0.0");
|
||||||
|
Assert.IsTrue(result, "Should handle 'V' prefix correctly");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_HandlesMixedPrefixes()
|
||||||
|
{
|
||||||
|
bool result = _service.IsNewerVersion("v2.0.0", "1.0.0");
|
||||||
|
Assert.IsTrue(result, "Should handle mixed prefixes correctly");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_ComparesCorrectly_WhenMajorDiffers()
|
||||||
|
{
|
||||||
|
bool result1 = _service.IsNewerVersion("10.0.0", "9.0.0");
|
||||||
|
bool result2 = _service.IsNewerVersion("2.0.0", "10.0.0");
|
||||||
|
|
||||||
|
Assert.IsTrue(result1, "10.0.0 should be newer than 9.0.0");
|
||||||
|
Assert.IsFalse(result2, "2.0.0 should not be newer than 10.0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsNewerVersion_ReturnsFalse_OnInvalidVersionFormat()
|
||||||
|
{
|
||||||
|
// Service should handle errors gracefully
|
||||||
|
bool result = _service.IsNewerVersion("invalid", "1.0.0");
|
||||||
|
Assert.IsFalse(result, "Should return false for invalid version format");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckForUpdate_ReturnsCachedVersion_WhenCacheIsValid()
|
||||||
|
{
|
||||||
|
// Arrange: Set up valid cache
|
||||||
|
string today = DateTime.Now.ToString("yyyy-MM-dd");
|
||||||
|
string cachedVersion = "5.5.5";
|
||||||
|
EditorPrefs.SetString(TestLastCheckDateKey, today);
|
||||||
|
EditorPrefs.SetString(TestCachedVersionKey, cachedVersion);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.CheckForUpdate("5.0.0");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.CheckSucceeded, "Check should succeed with valid cache");
|
||||||
|
Assert.AreEqual(cachedVersion, result.LatestVersion, "Should return cached version");
|
||||||
|
Assert.IsTrue(result.UpdateAvailable, "Update should be available (5.5.5 > 5.0.0)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckForUpdate_DetectsUpdateAvailable_WhenNewerVersionCached()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string today = DateTime.Now.ToString("yyyy-MM-dd");
|
||||||
|
EditorPrefs.SetString(TestLastCheckDateKey, today);
|
||||||
|
EditorPrefs.SetString(TestCachedVersionKey, "6.0.0");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.CheckForUpdate("5.0.0");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.UpdateAvailable, "Should detect update is available");
|
||||||
|
Assert.AreEqual("6.0.0", result.LatestVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckForUpdate_DetectsNoUpdate_WhenVersionsMatch()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string today = DateTime.Now.ToString("yyyy-MM-dd");
|
||||||
|
EditorPrefs.SetString(TestLastCheckDateKey, today);
|
||||||
|
EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.CheckForUpdate("5.0.0");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(result.UpdateAvailable, "Should detect no update needed");
|
||||||
|
Assert.AreEqual("5.0.0", result.LatestVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckForUpdate_DetectsNoUpdate_WhenCurrentVersionIsNewer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string today = DateTime.Now.ToString("yyyy-MM-dd");
|
||||||
|
EditorPrefs.SetString(TestLastCheckDateKey, today);
|
||||||
|
EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.CheckForUpdate("6.0.0");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(result.UpdateAvailable, "Should detect no update when current is newer");
|
||||||
|
Assert.AreEqual("5.0.0", result.LatestVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckForUpdate_IgnoresExpiredCache_AndAttemptsFreshFetch()
|
||||||
|
{
|
||||||
|
// Arrange: Set cache from yesterday (expired)
|
||||||
|
string yesterday = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd");
|
||||||
|
string cachedVersion = "4.0.0";
|
||||||
|
EditorPrefs.SetString(TestLastCheckDateKey, yesterday);
|
||||||
|
EditorPrefs.SetString(TestCachedVersionKey, cachedVersion);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.CheckForUpdate("5.0.0");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result, "Should return a result");
|
||||||
|
|
||||||
|
// If the check succeeded (network available), verify it didn't use the expired cache
|
||||||
|
if (result.CheckSucceeded)
|
||||||
|
{
|
||||||
|
Assert.AreNotEqual(cachedVersion, result.LatestVersion,
|
||||||
|
"Should not return expired cached version when fresh fetch succeeds");
|
||||||
|
Assert.IsNotNull(result.LatestVersion, "Should have fetched a new version");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If offline, check should fail (not succeed with cached data)
|
||||||
|
Assert.IsFalse(result.UpdateAvailable,
|
||||||
|
"Should not report update available when fetch fails and cache is expired");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CheckForUpdate_ReturnsAssetStoreMessage_ForNonGitInstallations()
|
||||||
|
{
|
||||||
|
// Note: This test verifies the service behavior when IsGitInstallation() returns false.
|
||||||
|
// Since the actual result depends on package installation method, we create a mock
|
||||||
|
// implementation to test this specific code path.
|
||||||
|
|
||||||
|
var mockService = new MockAssetStorePackageUpdateService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = mockService.CheckForUpdate("5.0.0");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(result.CheckSucceeded, "Check should not succeed for Asset Store installs");
|
||||||
|
Assert.IsFalse(result.UpdateAvailable, "No update should be reported for Asset Store installs");
|
||||||
|
Assert.AreEqual("Asset Store installations are updated via Unity Asset Store", result.Message,
|
||||||
|
"Should return Asset Store update message");
|
||||||
|
Assert.IsNull(result.LatestVersion, "Latest version should be null for Asset Store installs");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ClearCache_RemovesAllCachedData()
|
||||||
|
{
|
||||||
|
// Arrange: Set up cache
|
||||||
|
EditorPrefs.SetString(TestLastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
|
||||||
|
EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");
|
||||||
|
|
||||||
|
// Verify cache exists
|
||||||
|
Assert.IsTrue(EditorPrefs.HasKey(TestLastCheckDateKey), "Cache should exist before clearing");
|
||||||
|
Assert.IsTrue(EditorPrefs.HasKey(TestCachedVersionKey), "Cache should exist before clearing");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
_service.ClearCache();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(EditorPrefs.HasKey(TestLastCheckDateKey), "Date cache should be cleared");
|
||||||
|
Assert.IsFalse(EditorPrefs.HasKey(TestCachedVersionKey), "Version cache should be cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ClearCache_DoesNotThrow_WhenNoCacheExists()
|
||||||
|
{
|
||||||
|
// Ensure no cache exists
|
||||||
|
CleanupEditorPrefs();
|
||||||
|
|
||||||
|
// Act & Assert - should not throw
|
||||||
|
Assert.DoesNotThrow(() => _service.ClearCache(), "Should not throw when clearing non-existent cache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of IPackageUpdateService that simulates Asset Store installation behavior
|
||||||
|
/// </summary>
|
||||||
|
internal class MockAssetStorePackageUpdateService : IPackageUpdateService
|
||||||
|
{
|
||||||
|
public UpdateCheckResult CheckForUpdate(string currentVersion)
|
||||||
|
{
|
||||||
|
// Simulate Asset Store installation (IsGitInstallation returns false)
|
||||||
|
return new UpdateCheckResult
|
||||||
|
{
|
||||||
|
CheckSucceeded = false,
|
||||||
|
UpdateAvailable = false,
|
||||||
|
Message = "Asset Store installations are updated via Unity Asset Store"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsNewerVersion(string version1, string version2)
|
||||||
|
{
|
||||||
|
// Not used in the Asset Store test, but required by interface
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsGitInstallation()
|
||||||
|
{
|
||||||
|
// Simulate non-Git installation (Asset Store)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearCache()
|
||||||
|
{
|
||||||
|
// Not used in the Asset Store test, but required by interface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 676c3849f71a84b17b14d813774d3f74
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -71,6 +71,9 @@ namespace MCPForUnity.Editor.Windows
|
||||||
// Load validation level setting
|
// Load validation level setting
|
||||||
LoadValidationLevelSetting();
|
LoadValidationLevelSetting();
|
||||||
|
|
||||||
|
// Show one-time migration dialog
|
||||||
|
ShowMigrationDialogIfNeeded();
|
||||||
|
|
||||||
// First-run auto-setup only if Claude CLI is available
|
// First-run auto-setup only if Claude CLI is available
|
||||||
if (autoRegisterEnabled && !string.IsNullOrEmpty(ExecPath.ResolveClaude()))
|
if (autoRegisterEnabled && !string.IsNullOrEmpty(ExecPath.ResolveClaude()))
|
||||||
{
|
{
|
||||||
|
|
@ -170,6 +173,9 @@ namespace MCPForUnity.Editor.Windows
|
||||||
{
|
{
|
||||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||||
|
|
||||||
|
// Migration warning banner (non-dismissible)
|
||||||
|
DrawMigrationWarningBanner();
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
DrawHeader();
|
DrawHeader();
|
||||||
|
|
||||||
|
|
@ -1573,6 +1579,65 @@ namespace MCPForUnity.Editor.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowMigrationDialogIfNeeded()
|
||||||
|
{
|
||||||
|
const string dialogShownKey = "MCPForUnity.LegacyMigrationDialogShown";
|
||||||
|
if (EditorPrefs.GetBool(dialogShownKey, false))
|
||||||
|
{
|
||||||
|
return; // Already shown
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = EditorUtility.DisplayDialogComplex(
|
||||||
|
"Migration Required",
|
||||||
|
"This is the legacy UnityMcpBridge package.\n\n" +
|
||||||
|
"Please migrate to the new MCPForUnity package to receive updates and support.\n\n" +
|
||||||
|
"Migration takes just a few minutes.",
|
||||||
|
"View Migration Guide",
|
||||||
|
"Remind Me Later",
|
||||||
|
"I'll Migrate Later"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == 0) // View Migration Guide
|
||||||
|
{
|
||||||
|
Application.OpenURL("https://github.com/CoplayDev/unity-mcp/blob/main/docs/v5_MIGRATION.md");
|
||||||
|
EditorPrefs.SetBool(dialogShownKey, true);
|
||||||
|
}
|
||||||
|
else if (result == 2) // I'll Migrate Later
|
||||||
|
{
|
||||||
|
EditorPrefs.SetBool(dialogShownKey, true);
|
||||||
|
}
|
||||||
|
// result == 1 (Remind Me Later) - don't set the flag, show again next time
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMigrationWarningBanner()
|
||||||
|
{
|
||||||
|
// Warning banner - not dismissible, always visible
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
Rect bannerRect = EditorGUILayout.GetControlRect(false, 50);
|
||||||
|
EditorGUI.DrawRect(bannerRect, new Color(1f, 0.6f, 0f, 0.3f)); // Orange background
|
||||||
|
|
||||||
|
GUIStyle warningStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||||
|
{
|
||||||
|
fontSize = 13,
|
||||||
|
alignment = TextAnchor.MiddleLeft,
|
||||||
|
richText = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use Unicode warning triangle (same as used elsewhere in codebase at line 647, 652)
|
||||||
|
string warningText = "\u26A0 <color=#FF8C00>LEGACY PACKAGE:</color> Please migrate to MCPForUnity for updates and support.";
|
||||||
|
|
||||||
|
Rect textRect = new Rect(bannerRect.x + 15, bannerRect.y + 8, bannerRect.width - 180, bannerRect.height - 16);
|
||||||
|
GUI.Label(textRect, warningText, warningStyle);
|
||||||
|
|
||||||
|
// Button on the right
|
||||||
|
Rect buttonRect = new Rect(bannerRect.xMax - 160, bannerRect.y + 10, 145, 30);
|
||||||
|
if (GUI.Button(buttonRect, "View Migration Guide"))
|
||||||
|
{
|
||||||
|
Application.OpenURL("https://github.com/CoplayDev/unity-mcp/blob/main/docs/v5_MIGRATION.md");
|
||||||
|
}
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsPythonDetected()
|
private bool IsPythonDetected()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue