/* +------------------------------------------------------------------+ | 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 { /// /// Coverage for 's flat-layout extraction. /// Synthetic .nupkg fixtures exercise the extractor directly without /// touching nuget.org. DLLs are written FLAT under the install directory /// using the original {stem}.dll filename from the .nupkg lib /// folder — the package version is NOT embedded in the on-disk filename /// (it is tracked in instead, so /// asmdef precompiledReferences stay stable across version bumps). /// [TestFixture] public class NuGetExtractorFlatLayoutTests { string _tempDir = string.Empty; string _installDir = string.Empty; [SetUp] public void SetUp() { _tempDir = Path.Combine(Path.GetTempPath(), "UnityMcp-Extractor-" + Path.GetRandomFileName()); Directory.CreateDirectory(_tempDir); _installDir = Path.Combine(_tempDir, "install"); Directory.CreateDirectory(_installDir); } [TearDown] public void TearDown() { if (Directory.Exists(_tempDir)) { try { Directory.Delete(_tempDir, recursive: true); } catch { /* best-effort */ } } } [Test] public void ExtractDlls_WritesUnversionedFlatFilename() { // Output filename is the original {stem}.dll from the .nupkg's // lib// folder; version is tracked in the manifest, not the // filename. The DLL sits directly under the install path (no // per-package subfolder, no version tail). var nupkg = BuildNupkg("System.Text.Json", "8.0.5", "lib/netstandard2.0/System.Text.Json.dll"); var extracted = NuGetExtractor.ExtractDlls(nupkg, _installDir); CollectionAssert.AreEqual(new[] { "System.Text.Json.dll" }, extracted); Assert.IsTrue(File.Exists(Path.Combine(_installDir, "System.Text.Json.dll"))); Assert.IsFalse(File.Exists(Path.Combine(_installDir, "System.Text.Json.8.0.5.dll")), "Versioned-filename layout is no longer produced."); } [Test] public void ExtractDlls_WritesAllShippedDlls_ForMultiDllPackage() { // Multi-DLL packages (e.g. Microsoft.Bcl.Memory) write every DLL // under its original stem; the per-package version (10.0.3 here) // lives in the manifest entry. var nupkg = BuildNupkg("Microsoft.Bcl.Memory", "10.0.3", "lib/netstandard2.0/System.Memory.dll", "lib/netstandard2.0/System.Buffers.dll", "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll"); var extracted = NuGetExtractor.ExtractDlls(nupkg, _installDir); CollectionAssert.AreEquivalent( new[] { "System.Memory.dll", "System.Buffers.dll", "System.Runtime.CompilerServices.Unsafe.dll", }, extracted); } [Test] public void ExtractDlls_OverwritesExistingFile_ForVersionBumpInPlace() { // Steady-state version-bump: the same {stem}.dll filename is // reused across versions, so re-extraction overwrites bytes in // place. This is the primary win of the unversioned layout — // asmdef precompiledReferences never need to be edited when the // configured package version bumps. var nupkg = BuildNupkg("Foo.Bar", "1.0.0", "lib/netstandard2.0/Foo.Bar.dll"); var targetPath = Path.Combine(_installDir, "Foo.Bar.dll"); File.WriteAllText(targetPath, "old-version-bytes"); NuGetExtractor.ExtractDlls(nupkg, _installDir); Assert.AreEqual("dummy-dll-content", File.ReadAllText(targetPath), "Extraction must overwrite the existing file with the new version's bytes."); } [Test] public void ExtractDlls_ReturnsEmpty_WhenPackageHasNoCompatibleFramework() { // Build a nupkg with NO lib/ entries. var emptyNupkg = Path.Combine(_tempDir, "Empty.1.0.0.nupkg"); using (var zip = ZipFile.Open(emptyNupkg, ZipArchiveMode.Create)) { var entry = zip.CreateEntry("Empty.nuspec"); using var w = new StreamWriter(entry.Open()); w.Write("Empty1.0.0tt"); } var extracted = NuGetExtractor.ExtractDlls(emptyNupkg, _installDir); Assert.AreEqual(0, extracted.Count); Assert.AreEqual(0, Directory.GetFiles(_installDir).Length, "Empty package must not write anything to disk."); } /// /// Builds a minimal synthetic .nupkg zip with a .nuspec at the root and /// the given lib/{tfm}/*.dll entries. Sufficient input for the /// extractor under test. /// string BuildNupkg(string id, string version, params string[] entryPaths) { var nupkgPath = Path.Combine(_tempDir, $"{id}.{version}.nupkg"); using var zip = ZipFile.Open(nupkgPath, ZipArchiveMode.Create); // Minimal .nuspec at root. var nuspec = zip.CreateEntry($"{id}.nuspec"); using (var w = new StreamWriter(nuspec.Open())) { w.Write( "" + "" + "" + $"{id}" + $"{version}" + "test" + "fixture" + "" + ""); } foreach (var path in entryPaths) { var entry = zip.CreateEntry(path); using var w = new StreamWriter(entry.Open()); w.Write("dummy-dll-content"); } return nupkgPath; } } }