/* +------------------------------------------------------------------+ | Author: Ivan Murzak (https://github.com/IvanMurzak) | | Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) | | Copyright (c) 2025 Ivan Murzak | | Licensed under the Apache License, Version 2.0. | | See the LICENSE file in the project root for more information. | +------------------------------------------------------------------+ */ #nullable enable using NUnit.Framework; using com.IvanMurzak.Unity.MCP.Editor.DependencyResolver; namespace com.IvanMurzak.Unity.MCP.Editor.Tests.DependencyResolverTests { /// /// Coverage for the Windows MAX_PATH pre-flight check introduced for issue /// #733. The test seam lets /// us drive the rejection deterministically by injecting the OS check and /// the threshold instead of relying on an actual ~250-char temp path. /// [TestFixture] public class NuGetLongPathPreflightTests { [Test] public void CheckWith_NoOpOnNonWindows() { // 5000-char fake path — much larger than Windows' 260, but the // pre-flight is a no-op on macOS/Linux. var longPath = "/tmp/" + new string('x', 5000) + ".dll"; Assert.DoesNotThrow(() => NuGetLongPathPreflight.CheckWith(longPath, "Some.Package", isWindows: false, maxAllowedPathLength: 255)); } [Test] public void CheckWith_ThrowsOnWindowsWhenPathExceedsThreshold() { // 300-char path with a Windows-shaped prefix; threshold 255. var longPath = "C:\\" + new string('x', 300) + ".dll"; var ex = Assert.Throws(() => NuGetLongPathPreflight.CheckWith(longPath, "Some.Package", isWindows: true, maxAllowedPathLength: 255)); Assert.IsNotNull(ex); StringAssert.Contains("Some.Package", ex!.Message); StringAssert.Contains("260-character path limit", ex.Message); Assert.That(ex.PlannedPathLength, Is.GreaterThan(255)); } [Test] public void CheckWith_ReturnsNormallyOnWindowsWhenPathFits() { // Short path, well below any reasonable threshold. var shortPath = "C:\\src\\proj\\Assets\\Plugins\\NuGet\\System.Memory.10.0.3.dll"; Assert.DoesNotThrow(() => NuGetLongPathPreflight.CheckWith(shortPath, "Microsoft.Bcl.Memory", isWindows: true, maxAllowedPathLength: 255)); } #if UNITY_EDITOR_WIN // Windows-only: the boundary case relies on `Path.GetFullPath("C:\\…")` // returning the input unchanged. Mono on Linux does not recognize the // `C:\` drive prefix and treats the whole input as a relative path, // prepending the cwd (e.g. `/github/workspace/Unity-Tests//`). The // resolved path then exceeds the 255-char threshold and the pre-flight // throws — correctly for Windows semantics, but the test is asserting // exactly the Windows path-resolution shape, so we gate it accordingly. [Test] public void CheckWith_BoundaryAtThreshold_DoesNotThrow() { // Path of exactly 255 characters at threshold 255 must NOT throw. // This pins the "<=" comparison so a future change to "<" (which // would reject the boundary) is caught by the test gate. var prefix = "C:\\"; var suffix = ".dll"; var fillerLen = 255 - prefix.Length - suffix.Length; var path = prefix + new string('a', fillerLen) + suffix; Assert.AreEqual(255, path.Length); Assert.DoesNotThrow(() => NuGetLongPathPreflight.CheckWith(path, "Pkg", isWindows: true, maxAllowedPathLength: 255)); } #endif #if UNITY_EDITOR_WIN // Same Windows-only rationale as the boundary-at-threshold test above: // Mono on Linux does not recognise `C:\` as a drive prefix, so // `Path.GetFullPath` prepends the cwd and inflates the resolved path // well past 255. The threshold-throws assertion still passes by // accident on Linux (because 256 + cwd-prefix is still > 255), but // that is meaningless — the test no longer exercises the boundary. [Test] public void CheckWith_BoundaryAboveThreshold_Throws() { // Path of exactly 256 characters at threshold 255 MUST throw. var prefix = "C:\\"; var suffix = ".dll"; var fillerLen = 256 - prefix.Length - suffix.Length; var path = prefix + new string('a', fillerLen) + suffix; Assert.AreEqual(256, path.Length); Assert.Throws(() => NuGetLongPathPreflight.CheckWith(path, "Pkg", isWindows: true, maxAllowedPathLength: 255)); } #endif #if !UNITY_EDITOR_WIN // Unix counterpart of the boundary-at-threshold test. A `/`-rooted path // is already absolute on Unix, so `Path.GetFullPath` returns it unchanged // and the boundary check at <= 255 holds. Pins the same `<=` semantics // on Linux/macOS that the Windows variant pins on Windows. [Test] public void CheckWith_BoundaryAtThreshold_UnixShapedPath_DoesNotThrow() { var prefix = "/tmp/"; var suffix = ".dll"; var fillerLen = 255 - prefix.Length - suffix.Length; var path = prefix + new string('a', fillerLen) + suffix; Assert.AreEqual(255, path.Length); Assert.DoesNotThrow(() => NuGetLongPathPreflight.CheckWith(path, "Pkg", isWindows: true, maxAllowedPathLength: 255)); } // Unix counterpart of the boundary-above-threshold test. A `/`-rooted // 256-char path round-trips through `Path.GetFullPath` unchanged on // Unix, so the rejection fires for the right reason rather than via // cwd-prefix inflation. [Test] public void CheckWith_BoundaryAboveThreshold_UnixShapedPath_Throws() { var prefix = "/tmp/"; var suffix = ".dll"; var fillerLen = 256 - prefix.Length - suffix.Length; var path = prefix + new string('a', fillerLen) + suffix; Assert.AreEqual(256, path.Length); Assert.Throws(() => NuGetLongPathPreflight.CheckWith(path, "Pkg", isWindows: true, maxAllowedPathLength: 255)); } #endif } }