Files
kompas3d-mcp/tests/Kompas.Mcp.Tests/KompasInteropProviderTests.cs
T

189 lines
7.8 KiB
C#

using System.IO.Compression;
using Kompas.Mcp.Core.Interop;
namespace Kompas.Mcp.Tests;
/// <summary>
/// Поиск интероп-сборок КОМПАС: приоритет источников, распаковка архива SDK, тексты ошибок.
/// Настоящие DLL не нужны — провайдер проверяет только наличие файлов с нужными именами.
/// </summary>
[Trait("Category", "Unit")]
public sealed class KompasInteropProviderTests : IDisposable
{
private static readonly string[] RequiredFiles =
{
"KompasAPI7.dll",
"Kompas6API5.dll",
"Kompas6Constants.dll",
"Kompas6Constants3D.dll",
};
private readonly string _root = Path.Combine(Path.GetTempPath(), "kompas-interop-tests-" + Guid.NewGuid().ToString("N")[..8]);
public void Dispose()
{
try { if (Directory.Exists(_root)) Directory.Delete(_root, recursive: true); } catch { }
}
[Theory]
[InlineData("KompasAPI7", true)]
[InlineData("kompas6api5", true)]
[InlineData("Kompas6API3D5COM", true)]
[InlineData("Kompas.Mcp.Core", false)]
[InlineData(null, false)]
public void IsKompasAssembly_matches_only_interop_names(string? name, bool expected)
=> Assert.Equal(expected, KompasInteropProvider.IsKompasAssembly(name));
[Fact]
public void Override_directory_wins_over_everything()
{
var overrideDir = CreateDirectoryWithAssemblies("override");
var baseDir = CreateDirectoryWithAssemblies("base");
var provider = new KompasInteropProvider(overrideDir, baseDir, Cache, () => throw new Xunit.Sdk.XunitException(
"поиск установки не должен вызываться при заданном KOMPAS_INTEROP_DIR"));
Assert.Equal(overrideDir, provider.ResolveDirectory());
}
[Fact]
public void Override_directory_without_assemblies_fails_loudly()
{
var empty = Path.Combine(_root, "empty");
Directory.CreateDirectory(empty);
var provider = new KompasInteropProvider(empty, MissingDirectory, Cache, () => null);
var error = Assert.Throws<KompasInteropException>(() => provider.ResolveDirectory());
Assert.Contains(KompasInteropProvider.OverrideVariable, error.Message, StringComparison.Ordinal);
Assert.Contains(empty, error.Message, StringComparison.Ordinal);
}
[Fact]
public void Base_directory_is_used_when_it_holds_assemblies()
{
var baseDir = CreateDirectoryWithAssemblies("base");
var provider = new KompasInteropProvider(null, baseDir, Cache, () => throw new Xunit.Sdk.XunitException(
"поиск установки не должен вызываться, когда сборки лежат рядом с сервером"));
Assert.Equal(baseDir, provider.ResolveDirectory());
}
[Fact]
public void Missing_installation_reports_actionable_reason()
{
var provider = new KompasInteropProvider(null, MissingDirectory, Cache, () => null);
var error = Assert.Throws<KompasInteropException>(() => provider.ResolveDirectory());
Assert.Contains("KOMPAS.Application", error.Message, StringComparison.Ordinal);
Assert.Contains(KompasInteropProvider.OverrideVariable, error.Message, StringComparison.Ordinal);
}
[Fact]
public void Installation_without_sdk_component_reports_missing_archive()
{
var install = Path.Combine(_root, "KOMPAS-3D v24 Home");
Directory.CreateDirectory(install);
var provider = new KompasInteropProvider(null, MissingDirectory, Cache, () => install);
var error = Assert.Throws<KompasInteropException>(() => provider.ResolveDirectory());
Assert.Contains("SDK", error.Message, StringComparison.Ordinal);
Assert.Contains("CSharp.zip", error.Message, StringComparison.Ordinal);
}
[Fact]
public void Archive_without_expected_entries_reports_empty_common_folder()
{
var install = CreateInstallation("KOMPAS-3D v24 Home", entries: new[] { "Automation/Interop.KGAXLib.dll" });
var provider = new KompasInteropProvider(null, MissingDirectory, Cache, () => install);
var error = Assert.Throws<KompasInteropException>(() => provider.ResolveDirectory());
Assert.Contains("Common/", error.Message, StringComparison.Ordinal);
}
[Fact]
public void Assemblies_are_extracted_from_sdk_archive_into_cache()
{
var install = CreateInstallation("KOMPAS-3D v24 Home", entries: CommonEntries("readme.txt"));
var directory = new KompasInteropProvider(null, MissingDirectory, Cache, () => install).ResolveDirectory();
Assert.StartsWith(Cache, directory, StringComparison.Ordinal);
Assert.All(RequiredFiles, file => Assert.True(File.Exists(Path.Combine(directory, file)), file));
// В кеш попадают только DLL из Common — не весь архив.
Assert.False(File.Exists(Path.Combine(directory, "readme.txt")));
Assert.False(File.Exists(Path.Combine(directory, "Interop.KGAXLib.dll")));
}
[Fact]
public void Extracted_cache_is_reused_by_the_next_process()
{
var install = CreateInstallation("KOMPAS-3D v24 Home", entries: CommonEntries());
var first = new KompasInteropProvider(null, MissingDirectory, Cache, () => install).ResolveDirectory();
var marker = Path.Combine(first, "marker.txt");
File.WriteAllText(marker, "распаковано первым запуском");
var second = new KompasInteropProvider(null, MissingDirectory, Cache, () => install).ResolveDirectory();
Assert.Equal(first, second);
Assert.True(File.Exists(marker), "кеш должен переиспользоваться, а не распаковываться заново");
}
[Fact]
public void Cache_key_follows_the_sdk_archive()
{
var archive = Path.Combine(_root, "CSharp.zip");
Directory.CreateDirectory(_root);
File.WriteAllText(archive, "v24");
var before = KompasInteropProvider.CacheKey(new FileInfo(archive));
// Обновление КОМПАС меняет архив — ключ обязан смениться, иначе подхватится старый кеш.
File.WriteAllText(archive, "v25 — другой размер");
File.SetLastWriteTimeUtc(archive, DateTime.UnixEpoch.AddDays(1));
var after = KompasInteropProvider.CacheKey(new FileInfo(archive));
Assert.NotEqual(before, after);
Assert.Equal(16, before.Length);
}
private string Cache => Path.Combine(_root, "cache");
private string MissingDirectory => Path.Combine(_root, "no-such-directory");
private static string[] CommonEntries(params string[] extra)
=> RequiredFiles.Select(file => "Common/" + file)
.Concat(extra.Select(name => "Common/" + name))
.Append("Automation/Interop.KGAXLib.dll")
.ToArray();
private string CreateDirectoryWithAssemblies(string name)
{
var directory = Path.Combine(_root, name);
Directory.CreateDirectory(directory);
foreach (var file in RequiredFiles)
File.WriteAllText(Path.Combine(directory, file), "заглушка");
return directory;
}
private string CreateInstallation(string name, string[] entries)
{
var install = Path.Combine(_root, name);
var samples = Path.Combine(install, "SDK", "Samples");
Directory.CreateDirectory(samples);
using var archive = ZipFile.Open(Path.Combine(samples, "CSharp.zip"), ZipArchiveMode.Create);
foreach (var entry in entries)
{
using var writer = new StreamWriter(archive.CreateEntry(entry).Open());
writer.Write("заглушка");
}
return install;
}
}