/*
+------------------------------------------------------------------+
| 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 System.IO;
using System.IO.Compression;
using NUnit.Framework;
using com.IvanMurzak.Unity.MCP.Editor.DependencyResolver;
namespace com.IvanMurzak.Unity.MCP.Editor.Tests.DependencyResolverTests
{
///
/// Regression coverage for issue #670: DependencyResolver was logging
/// "No compatible framework found" / "No DLLs extracted" warnings for
/// Roslyn analyzer packages (e.g. Microsoft.CodeAnalysis.Analyzers), which
/// are marked <developmentDependency>true</developmentDependency>
/// in their .nuspec and legitimately have no runtime DLLs under lib/<tfm>/.
///
[TestFixture]
public class NuGetExtractorDevelopmentDependencyTests
{
string _tempDir = string.Empty;
[SetUp]
public void SetUp()
{
_tempDir = Path.Combine(Path.GetTempPath(), "UnityMcp-NuGetExtractor-" + Path.GetRandomFileName());
Directory.CreateDirectory(_tempDir);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(_tempDir))
{
try { Directory.Delete(_tempDir, recursive: true); } catch { /* best-effort cleanup */ }
}
}
[Test]
public void IsDevelopmentDependency_ReturnsTrue_WhenNuspecDeclaresDevDependencyTrue()
{
var nupkg = BuildNupkg(
id: "Example.Analyzer",
version: "1.0.0",
developmentDependencyElement: "true");
Assert.IsTrue(NuGetExtractor.IsDevelopmentDependency(nupkg));
}
[Test]
public void IsDevelopmentDependency_ReturnsTrue_WhenValueHasWhitespace()
{
// Some package authors pretty-print their nuspec; we must not choke on whitespace.
var nupkg = BuildNupkg(
id: "Example.Analyzer",
version: "1.0.0",
developmentDependencyElement: " true ");
Assert.IsTrue(NuGetExtractor.IsDevelopmentDependency(nupkg));
}
[Test]
public void IsDevelopmentDependency_ReturnsFalse_WhenNuspecDeclaresDevDependencyFalse()
{
var nupkg = BuildNupkg(
id: "Example.Library",
version: "1.0.0",
developmentDependencyElement: "false");
Assert.IsFalse(NuGetExtractor.IsDevelopmentDependency(nupkg));
}
[Test]
public void IsDevelopmentDependency_ReturnsFalse_WhenDevDependencyElementIsMissing()
{
// The overwhelmingly common case: normal runtime libraries simply omit the element.
var nupkg = BuildNupkg(
id: "Example.Library",
version: "1.0.0",
developmentDependencyElement: null);
Assert.IsFalse(NuGetExtractor.IsDevelopmentDependency(nupkg));
}
[Test]
public void IsDevelopmentDependency_ReturnsFalse_WhenNupkgIsCorrupt()
{
// An invalid zip header is not a valid zip archive; the helper must swallow the
// exception and return false so the caller falls back to normal extraction.
var bogus = Path.Combine(_tempDir, "corrupt.nupkg");
File.WriteAllBytes(bogus, new byte[] { 0, 0, 0, 0 });
Assert.IsFalse(NuGetExtractor.IsDevelopmentDependency(bogus));
}
[Test]
public void IsDevelopmentDependency_ReturnsFalse_WhenNupkgHasNoNuspec()
{
var nupkg = Path.Combine(_tempDir, "no-nuspec.nupkg");
using (var zip = ZipFile.Open(nupkg, ZipArchiveMode.Create))
{
var entry = zip.CreateEntry("readme.txt");
using var writer = new StreamWriter(entry.Open());
writer.Write("no nuspec here");
}
Assert.IsFalse(NuGetExtractor.IsDevelopmentDependency(nupkg));
}
///
/// Builds a minimal synthetic .nupkg containing a single .nuspec entry at the root.
/// Mirrors enough of the real NuGet v3 layout to exercise the parser without downloading
/// anything over the network.
///
string BuildNupkg(string id, string version, string? developmentDependencyElement)
{
var nupkg = Path.Combine(_tempDir, $"{id}.{version}.nupkg");
var nuspec =
"" +
"" +
" " +
$" {id}" +
$" {version}" +
" test" +
" test fixture package" +
(developmentDependencyElement ?? string.Empty) +
" " +
"";
using (var zip = ZipFile.Open(nupkg, ZipArchiveMode.Create))
{
var entry = zip.CreateEntry($"{id}.nuspec");
using var writer = new StreamWriter(entry.Open());
writer.Write(nuspec);
}
return nupkg;
}
}
}