stabilizaiton work - projects rework for maintenanceability and ui livening
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.DependencyInjection;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.DependencyInjection;
|
||||
|
||||
public sealed partial class PluginDependencyInjectionExtensionsTests
|
||||
{
|
||||
private sealed class TestPluginAssembly : IDisposable
|
||||
{
|
||||
private TestPluginAssembly(string directoryPath, string assemblyPath)
|
||||
{
|
||||
DirectoryPath = directoryPath;
|
||||
AssemblyPath = assemblyPath;
|
||||
|
||||
Options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = directoryPath,
|
||||
EnsureDirectoryExists = false,
|
||||
RecursiveSearch = false,
|
||||
};
|
||||
Options.SearchPatterns.Add(Path.GetFileName(assemblyPath));
|
||||
}
|
||||
|
||||
public string DirectoryPath { get; }
|
||||
|
||||
public string AssemblyPath { get; }
|
||||
|
||||
public PluginHostOptions Options { get; }
|
||||
|
||||
public static TestPluginAssembly Create(string source)
|
||||
{
|
||||
var directoryPath = Path.Combine(Path.GetTempPath(), "stellaops-plugin-tests-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
|
||||
var assemblyName = "SamplePlugin" + Guid.NewGuid().ToString("N");
|
||||
var assemblyPath = Path.Combine(directoryPath, assemblyName + ".dll");
|
||||
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||
var references = CollectMetadataReferences();
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
assemblyName,
|
||||
new[] { syntaxTree },
|
||||
references,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));
|
||||
|
||||
var emitResult = compilation.Emit(assemblyPath);
|
||||
if (!emitResult.Success)
|
||||
{
|
||||
var diagnostics = string.Join(Environment.NewLine, emitResult.Diagnostics);
|
||||
throw new InvalidOperationException("Failed to compile plugin assembly:" + Environment.NewLine + diagnostics);
|
||||
}
|
||||
|
||||
return new TestPluginAssembly(directoryPath, assemblyPath);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(DirectoryPath))
|
||||
{
|
||||
Directory.Delete(DirectoryPath, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup failures - plugin load contexts may keep files locked on Windows.
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<MetadataReference> CollectMetadataReferences()
|
||||
{
|
||||
var referencePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") is string tpa)
|
||||
{
|
||||
foreach (var path in tpa.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
referencePaths.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
referencePaths.Add(typeof(object).Assembly.Location);
|
||||
referencePaths.Add(typeof(ServiceBindingAttribute).Assembly.Location);
|
||||
referencePaths.Add(typeof(IDependencyInjectionRoutine).Assembly.Location);
|
||||
referencePaths.Add(typeof(IServiceCollection).Assembly.Location);
|
||||
referencePaths.Add(typeof(IConfiguration).Assembly.Location);
|
||||
|
||||
return referencePaths
|
||||
.Select(path => MetadataReference.CreateFromFile(path))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.DependencyInjection;
|
||||
using StellaOps.Plugin.DependencyInjection;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.DependencyInjection;
|
||||
|
||||
public sealed class PluginDependencyInjectionExtensionsTests
|
||||
public sealed partial class PluginDependencyInjectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void RegisterPluginRoutines_RegistersServiceBindingsAndHonoursLifetimes()
|
||||
public void RegisterPluginRoutines_RegistersBindingsAndRoutines()
|
||||
{
|
||||
const string source = """
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.DependencyInjection;
|
||||
|
||||
@@ -27,12 +22,24 @@ namespace SamplePlugin;
|
||||
|
||||
public interface IScopedExample {}
|
||||
public interface ISingletonExample {}
|
||||
public interface IRoutineMarker {}
|
||||
|
||||
[ServiceBinding(typeof(IScopedExample), ServiceLifetime.Scoped, RegisterAsSelf = true)]
|
||||
public sealed class ScopedExample : IScopedExample {}
|
||||
|
||||
[ServiceBinding(typeof(ISingletonExample), ServiceLifetime.Singleton)]
|
||||
public sealed class SingletonExample : ISingletonExample {}
|
||||
|
||||
public sealed class RoutineMarker : IRoutineMarker {}
|
||||
|
||||
public sealed class SampleRoutine : IDependencyInjectionRoutine
|
||||
{
|
||||
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<IRoutineMarker, RoutineMarker>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
using var plugin = TestPluginAssembly.Create(source);
|
||||
@@ -58,26 +65,11 @@ public sealed class SingletonExample : ISingletonExample {}
|
||||
static d => d.ServiceType.FullName == "SamplePlugin.ISingletonExample");
|
||||
Assert.Equal(ServiceLifetime.Singleton, singletonDescriptor.Lifetime);
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
|
||||
object firstScopeInstance;
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var resolvedFirst = ServiceProviderServiceExtensions.GetRequiredService(scope.ServiceProvider, scopedDescriptor.ServiceType);
|
||||
var resolvedSecond = ServiceProviderServiceExtensions.GetRequiredService(scope.ServiceProvider, scopedDescriptor.ServiceType);
|
||||
Assert.Same(resolvedFirst, resolvedSecond);
|
||||
firstScopeInstance = resolvedFirst;
|
||||
}
|
||||
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var resolved = ServiceProviderServiceExtensions.GetRequiredService(scope.ServiceProvider, scopedDescriptor.ServiceType);
|
||||
Assert.NotSame(firstScopeInstance, resolved);
|
||||
}
|
||||
|
||||
var singletonFirst = ServiceProviderServiceExtensions.GetRequiredService(provider, singletonDescriptor.ServiceType);
|
||||
var singletonSecond = ServiceProviderServiceExtensions.GetRequiredService(provider, singletonDescriptor.ServiceType);
|
||||
Assert.Same(singletonFirst, singletonSecond);
|
||||
var routineDescriptor = Assert.Single(
|
||||
services,
|
||||
static d => d.ServiceType.FullName == "SamplePlugin.IRoutineMarker");
|
||||
Assert.Equal(ServiceLifetime.Singleton, routineDescriptor.Lifetime);
|
||||
Assert.Equal("SamplePlugin.RoutineMarker", routineDescriptor.ImplementationType?.FullName);
|
||||
|
||||
services.RegisterPluginRoutines(configuration, plugin.Options, NullLogger.Instance);
|
||||
|
||||
@@ -86,91 +78,4 @@ public sealed class SingletonExample : ISingletonExample {}
|
||||
d.ImplementationType?.FullName == "SamplePlugin.ScopedExample");
|
||||
Assert.Equal(1, scopedRegistrations);
|
||||
}
|
||||
|
||||
private sealed class TestPluginAssembly : IDisposable
|
||||
{
|
||||
private TestPluginAssembly(string directoryPath, string assemblyPath)
|
||||
{
|
||||
DirectoryPath = directoryPath;
|
||||
AssemblyPath = assemblyPath;
|
||||
|
||||
Options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = directoryPath,
|
||||
EnsureDirectoryExists = false,
|
||||
RecursiveSearch = false,
|
||||
};
|
||||
Options.SearchPatterns.Add(Path.GetFileName(assemblyPath));
|
||||
}
|
||||
|
||||
public string DirectoryPath { get; }
|
||||
|
||||
public string AssemblyPath { get; }
|
||||
|
||||
public PluginHostOptions Options { get; }
|
||||
|
||||
public static TestPluginAssembly Create(string source)
|
||||
{
|
||||
var directoryPath = Path.Combine(Path.GetTempPath(), "stellaops-plugin-tests-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
|
||||
var assemblyName = "SamplePlugin" + Guid.NewGuid().ToString("N");
|
||||
var assemblyPath = Path.Combine(directoryPath, assemblyName + ".dll");
|
||||
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||
var references = CollectMetadataReferences();
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
assemblyName,
|
||||
new[] { syntaxTree },
|
||||
references,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));
|
||||
|
||||
var emitResult = compilation.Emit(assemblyPath);
|
||||
if (!emitResult.Success)
|
||||
{
|
||||
var diagnostics = string.Join(Environment.NewLine, emitResult.Diagnostics);
|
||||
throw new InvalidOperationException("Failed to compile plugin assembly:" + Environment.NewLine + diagnostics);
|
||||
}
|
||||
|
||||
return new TestPluginAssembly(directoryPath, assemblyPath);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(DirectoryPath))
|
||||
{
|
||||
Directory.Delete(DirectoryPath, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup failures – plugin load contexts may keep files locked on Windows.
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<MetadataReference> CollectMetadataReferences()
|
||||
{
|
||||
var referencePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") is string tpa)
|
||||
{
|
||||
foreach (var path in tpa.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
referencePaths.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
referencePaths.Add(typeof(object).Assembly.Location);
|
||||
referencePaths.Add(typeof(ServiceBindingAttribute).Assembly.Location);
|
||||
referencePaths.Add(typeof(IDependencyInjectionRoutine).Assembly.Location);
|
||||
referencePaths.Add(typeof(ServiceLifetime).Assembly.Location);
|
||||
|
||||
return referencePaths
|
||||
.Select(path => MetadataReference.CreateFromFile(path))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.DependencyInjection;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.DependencyInjection;
|
||||
|
||||
public sealed partial class PluginServiceRegistrationTests
|
||||
{
|
||||
private interface IScopedService
|
||||
{
|
||||
}
|
||||
|
||||
private interface ISelfContract
|
||||
{
|
||||
}
|
||||
|
||||
private interface IReplacementService
|
||||
{
|
||||
}
|
||||
|
||||
private interface IAnotherService
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class ExistingReplacementService : IReplacementService
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(IScopedService), ServiceLifetime.Scoped)]
|
||||
private sealed class ScopedTestService : IScopedService
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(ISelfContract), ServiceLifetime.Singleton, RegisterAsSelf = true)]
|
||||
private sealed class SelfRegisteringService : ISelfContract
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(IReplacementService), ServiceLifetime.Transient, ReplaceExisting = true)]
|
||||
private sealed class ReplacementService : IReplacementService
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(IAnotherService), ServiceLifetime.Singleton)]
|
||||
private sealed class InvalidServiceBinding
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.DependencyInjection;
|
||||
using StellaOps.Plugin.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.DependencyInjection;
|
||||
|
||||
public sealed class PluginServiceRegistrationTests
|
||||
public sealed partial class PluginServiceRegistrationTests
|
||||
{
|
||||
[Fact]
|
||||
public void RegisterAssemblyMetadata_RegistersScopedDescriptor()
|
||||
@@ -19,7 +17,9 @@ public sealed class PluginServiceRegistrationTests
|
||||
typeof(ScopedTestService).Assembly,
|
||||
NullLogger.Instance);
|
||||
|
||||
var descriptor = Assert.Single(services, static d => d.ServiceType == typeof(IScopedService));
|
||||
var descriptor = Assert.Single(
|
||||
services,
|
||||
static d => d.ServiceType == typeof(IScopedService));
|
||||
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
||||
Assert.Equal(typeof(ScopedTestService), descriptor.ImplementationType);
|
||||
}
|
||||
@@ -34,6 +34,10 @@ public sealed class PluginServiceRegistrationTests
|
||||
typeof(SelfRegisteringService).Assembly,
|
||||
NullLogger.Instance);
|
||||
|
||||
Assert.Contains(services, static d =>
|
||||
d.ServiceType == typeof(ISelfContract) &&
|
||||
d.ImplementationType == typeof(SelfRegisteringService));
|
||||
|
||||
Assert.Contains(services, static d =>
|
||||
d.ServiceType == typeof(SelfRegisteringService) &&
|
||||
d.ImplementationType == typeof(SelfRegisteringService));
|
||||
@@ -69,44 +73,4 @@ public sealed class PluginServiceRegistrationTests
|
||||
|
||||
Assert.DoesNotContain(services, static d => d.ServiceType == typeof(IAnotherService));
|
||||
}
|
||||
|
||||
private interface IScopedService
|
||||
{
|
||||
}
|
||||
|
||||
private interface ISelfContract
|
||||
{
|
||||
}
|
||||
|
||||
private interface IReplacementService
|
||||
{
|
||||
}
|
||||
|
||||
private interface IAnotherService
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class ExistingReplacementService : IReplacementService
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(IScopedService), ServiceLifetime.Scoped)]
|
||||
private sealed class ScopedTestService : IScopedService
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(ISelfContract), ServiceLifetime.Singleton, RegisterAsSelf = true)]
|
||||
private sealed class SelfRegisteringService : ISelfContract
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(IReplacementService), ServiceLifetime.Transient, ReplaceExisting = true)]
|
||||
private sealed class ReplacementService : IReplacementService
|
||||
{
|
||||
}
|
||||
|
||||
[ServiceBinding(typeof(IAnotherService), ServiceLifetime.Singleton)]
|
||||
private sealed class InvalidServiceBinding
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,8 @@ using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Hosting;
|
||||
|
||||
public sealed class PluginHostOptionsTests
|
||||
public sealed partial class PluginHostOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultValues_AreCorrect()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
Assert.Null(options.BaseDirectory);
|
||||
Assert.Null(options.PluginsDirectory);
|
||||
Assert.Null(options.PrimaryPrefix);
|
||||
Assert.Empty(options.AdditionalPrefixes);
|
||||
Assert.Empty(options.PluginOrder);
|
||||
Assert.Empty(options.SearchPatterns);
|
||||
Assert.True(options.EnsureDirectoryExists);
|
||||
Assert.True(options.RecursiveSearch);
|
||||
Assert.Null(options.HostVersion);
|
||||
Assert.True(options.EnforceVersionCompatibility);
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
Assert.Null(options.SignatureVerifier);
|
||||
Assert.False(options.EnforceSignatureVerification);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostVersion_CanBeSet()
|
||||
{
|
||||
@@ -106,19 +85,4 @@ public sealed class PluginHostOptionsTests
|
||||
|
||||
Assert.False(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProductionConfiguration_HasSecureDefaults()
|
||||
{
|
||||
// Verify that out-of-the-box defaults are secure for production
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// All enforcement options should default to strict/true
|
||||
Assert.True(options.EnforceVersionCompatibility, "Version compatibility should be enforced by default");
|
||||
Assert.True(options.RequireVersionAttribute, "Version attribute should be required by default");
|
||||
Assert.True(options.StrictMajorVersionCheck, "Strict major version check should be enabled by default");
|
||||
|
||||
// Signature verification is opt-in since it requires infrastructure
|
||||
Assert.False(options.EnforceSignatureVerification, "Signature verification is opt-in");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Hosting;
|
||||
|
||||
public sealed partial class PluginHostOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultValues_AreCorrect()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
Assert.Null(options.BaseDirectory);
|
||||
Assert.Null(options.PluginsDirectory);
|
||||
Assert.Null(options.PrimaryPrefix);
|
||||
Assert.Empty(options.AdditionalPrefixes);
|
||||
Assert.Empty(options.PluginOrder);
|
||||
Assert.Empty(options.SearchPatterns);
|
||||
Assert.True(options.EnsureDirectoryExists);
|
||||
Assert.True(options.RecursiveSearch);
|
||||
Assert.Null(options.HostVersion);
|
||||
Assert.True(options.EnforceVersionCompatibility);
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
Assert.Null(options.SignatureVerifier);
|
||||
Assert.False(options.EnforceSignatureVerification);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProductionConfiguration_HasSecureDefaults()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
Assert.True(options.EnforceVersionCompatibility, "Version compatibility should be enforced by default");
|
||||
Assert.True(options.RequireVersionAttribute, "Version attribute should be required by default");
|
||||
Assert.True(options.StrictMajorVersionCheck, "Strict major version check should be enabled by default");
|
||||
Assert.False(options.EnforceSignatureVerification, "Signature verification is opt-in");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginCompatibilityCheckerTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_AssemblyWithoutAttribute_Lenient_ReturnsCompatible()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(
|
||||
assembly, hostVersion, CompatibilityCheckOptions.Lenient);
|
||||
|
||||
Assert.True(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.Null(result.PluginVersion);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_AssemblyWithoutAttribute_RequireVersion_ReturnsIncompatible()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
var options = new CompatibilityCheckOptions
|
||||
{
|
||||
RequireVersionAttribute = true
|
||||
};
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(assembly, hostVersion, options);
|
||||
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.NotNull(result.FailureReason);
|
||||
Assert.Contains("StellaPluginVersion", result.FailureReason);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_NullAssembly_ThrowsException()
|
||||
{
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => PluginCompatibilityChecker.CheckCompatibility(null!, hostVersion));
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_NullHostVersion_ThrowsException()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => PluginCompatibilityChecker.CheckCompatibility(assembly, null!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginCompatibilityCheckerTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void DefaultOptions_HasCorrectValues()
|
||||
{
|
||||
var options = CompatibilityCheckOptions.Default;
|
||||
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LenientOptions_HasCorrectValues()
|
||||
{
|
||||
var options = CompatibilityCheckOptions.Lenient;
|
||||
|
||||
Assert.False(options.RequireVersionAttribute);
|
||||
Assert.False(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CustomOptions_CanBeConfigured()
|
||||
{
|
||||
var options = new CompatibilityCheckOptions
|
||||
{
|
||||
RequireVersionAttribute = true,
|
||||
StrictMajorVersionCheck = false
|
||||
};
|
||||
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.False(options.StrictMajorVersionCheck);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginCompatibilityCheckerTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PluginCompatibilityResult_RecordEquality()
|
||||
{
|
||||
var result1 = new PluginCompatibilityResult(
|
||||
IsCompatible: true,
|
||||
PluginVersion: new Version(1, 0, 0),
|
||||
MinimumHostVersion: new Version(1, 0, 0),
|
||||
MaximumHostVersion: new Version(2, 0, 0),
|
||||
RequiresSignature: true,
|
||||
FailureReason: null,
|
||||
HasVersionAttribute: true);
|
||||
|
||||
var result2 = new PluginCompatibilityResult(
|
||||
IsCompatible: true,
|
||||
PluginVersion: new Version(1, 0, 0),
|
||||
MinimumHostVersion: new Version(1, 0, 0),
|
||||
MaximumHostVersion: new Version(2, 0, 0),
|
||||
RequiresSignature: true,
|
||||
FailureReason: null,
|
||||
HasVersionAttribute: true);
|
||||
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PluginCompatibilityResult_PropertiesAreSet()
|
||||
{
|
||||
var pluginVersion = new Version(1, 2, 3);
|
||||
var minVersion = new Version(1, 0, 0);
|
||||
var maxVersion = new Version(2, 0, 0);
|
||||
var failureReason = "Test failure";
|
||||
|
||||
var result = new PluginCompatibilityResult(
|
||||
IsCompatible: false,
|
||||
PluginVersion: pluginVersion,
|
||||
MinimumHostVersion: minVersion,
|
||||
MaximumHostVersion: maxVersion,
|
||||
RequiresSignature: true,
|
||||
FailureReason: failureReason,
|
||||
HasVersionAttribute: true);
|
||||
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.Equal(pluginVersion, result.PluginVersion);
|
||||
Assert.Equal(minVersion, result.MinimumHostVersion);
|
||||
Assert.Equal(maxVersion, result.MaximumHostVersion);
|
||||
Assert.True(result.RequiresSignature);
|
||||
Assert.Equal(failureReason, result.FailureReason);
|
||||
Assert.True(result.HasVersionAttribute);
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
using System.Reflection;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for PluginCompatibilityChecker.
|
||||
/// </summary>
|
||||
public sealed class PluginCompatibilityCheckerTests
|
||||
{
|
||||
#region Basic Compatibility Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_AssemblyWithoutAttribute_Lenient_ReturnsCompatible()
|
||||
{
|
||||
// Arrange - Use current test assembly which doesn't have StellaPluginVersionAttribute
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
// Act
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(
|
||||
assembly, hostVersion, CompatibilityCheckOptions.Lenient);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.Null(result.PluginVersion);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_AssemblyWithoutAttribute_RequireVersion_ReturnsIncompatible()
|
||||
{
|
||||
// Arrange
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
var options = new CompatibilityCheckOptions
|
||||
{
|
||||
RequireVersionAttribute = true
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(assembly, hostVersion, options);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.NotNull(result.FailureReason);
|
||||
Assert.Contains("StellaPluginVersion", result.FailureReason);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_NullAssembly_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => PluginCompatibilityChecker.CheckCompatibility(null!, hostVersion));
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CheckCompatibility_NullHostVersion_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => PluginCompatibilityChecker.CheckCompatibility(assembly, null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CompatibilityCheckOptions Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void DefaultOptions_HasCorrectValues()
|
||||
{
|
||||
// Act
|
||||
var options = CompatibilityCheckOptions.Default;
|
||||
|
||||
// Assert
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LenientOptions_HasCorrectValues()
|
||||
{
|
||||
// Act
|
||||
var options = CompatibilityCheckOptions.Lenient;
|
||||
|
||||
// Assert
|
||||
Assert.False(options.RequireVersionAttribute);
|
||||
Assert.False(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void CustomOptions_CanBeConfigured()
|
||||
{
|
||||
// Arrange & Act
|
||||
var options = new CompatibilityCheckOptions
|
||||
{
|
||||
RequireVersionAttribute = true,
|
||||
StrictMajorVersionCheck = false
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.False(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PluginCompatibilityResult Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PluginCompatibilityResult_RecordEquality()
|
||||
{
|
||||
// Arrange
|
||||
var result1 = new PluginCompatibilityResult(
|
||||
IsCompatible: true,
|
||||
PluginVersion: new Version(1, 0, 0),
|
||||
MinimumHostVersion: new Version(1, 0, 0),
|
||||
MaximumHostVersion: new Version(2, 0, 0),
|
||||
RequiresSignature: true,
|
||||
FailureReason: null,
|
||||
HasVersionAttribute: true);
|
||||
|
||||
var result2 = new PluginCompatibilityResult(
|
||||
IsCompatible: true,
|
||||
PluginVersion: new Version(1, 0, 0),
|
||||
MinimumHostVersion: new Version(1, 0, 0),
|
||||
MaximumHostVersion: new Version(2, 0, 0),
|
||||
RequiresSignature: true,
|
||||
FailureReason: null,
|
||||
HasVersionAttribute: true);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PluginCompatibilityResult_PropertiesAreSet()
|
||||
{
|
||||
// Arrange
|
||||
var pluginVersion = new Version(1, 2, 3);
|
||||
var minVersion = new Version(1, 0, 0);
|
||||
var maxVersion = new Version(2, 0, 0);
|
||||
var failureReason = "Test failure";
|
||||
|
||||
// Act
|
||||
var result = new PluginCompatibilityResult(
|
||||
IsCompatible: false,
|
||||
PluginVersion: pluginVersion,
|
||||
MinimumHostVersion: minVersion,
|
||||
MaximumHostVersion: maxVersion,
|
||||
RequiresSignature: true,
|
||||
FailureReason: failureReason,
|
||||
HasVersionAttribute: true);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.Equal(pluginVersion, result.PluginVersion);
|
||||
Assert.Equal(minVersion, result.MinimumHostVersion);
|
||||
Assert.Equal(maxVersion, result.MaximumHostVersion);
|
||||
Assert.True(result.RequiresSignature);
|
||||
Assert.Equal(failureReason, result.FailureReason);
|
||||
Assert.True(result.HasVersionAttribute);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostOptionsTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void DefaultValues_AreCorrect()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
Assert.True(options.EnsureDirectoryExists);
|
||||
Assert.True(options.RecursiveSearch);
|
||||
Assert.False(options.EnforceSignatureVerification);
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.EnforceVersionCompatibility);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
Assert.Null(options.HostVersion);
|
||||
Assert.Null(options.SignatureVerifier);
|
||||
Assert.Empty(options.SearchPatterns);
|
||||
Assert.Empty(options.PluginOrder);
|
||||
Assert.Empty(options.AdditionalPrefixes);
|
||||
Assert.Null(options.BaseDirectory);
|
||||
Assert.Null(options.PluginsDirectory);
|
||||
Assert.Null(options.PrimaryPrefix);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void SearchPatterns_CanBeAdded()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
options.SearchPatterns.Add("MyPrefix.Plugin.*.dll");
|
||||
options.SearchPatterns.Add("Another.Plugin.*.dll");
|
||||
|
||||
Assert.Equal(2, options.SearchPatterns.Count);
|
||||
Assert.Contains("MyPrefix.Plugin.*.dll", options.SearchPatterns);
|
||||
Assert.Contains("Another.Plugin.*.dll", options.SearchPatterns);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PluginOrder_CanBeConfigured()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
options.PluginOrder.Add("PluginA");
|
||||
options.PluginOrder.Add("PluginB");
|
||||
options.PluginOrder.Add("PluginC");
|
||||
|
||||
Assert.Equal(3, options.PluginOrder.Count);
|
||||
Assert.Equal("PluginA", options.PluginOrder[0]);
|
||||
Assert.Equal("PluginB", options.PluginOrder[1]);
|
||||
Assert.Equal("PluginC", options.PluginOrder[2]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostOptionsTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void ResolveBaseDirectory_WhenNull_ReturnsAppContextBaseDirectory()
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
BaseDirectory = null
|
||||
};
|
||||
|
||||
var resolved = options.ResolveBaseDirectory();
|
||||
|
||||
Assert.Equal(AppContext.BaseDirectory, resolved);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void ResolveBaseDirectory_WhenSet_ReturnsConfiguredValue()
|
||||
{
|
||||
var customDir = Path.GetTempPath();
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
BaseDirectory = customDir
|
||||
};
|
||||
|
||||
var resolved = options.ResolveBaseDirectory();
|
||||
|
||||
Assert.Equal(customDir, resolved);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void ResolveBaseDirectory_WhenRelativePath_ReturnsFullPath()
|
||||
{
|
||||
var relativePath = Path.Combine("relative", "plugins");
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
BaseDirectory = relativePath
|
||||
};
|
||||
|
||||
var resolved = options.ResolveBaseDirectory();
|
||||
|
||||
Assert.True(Path.IsPathRooted(resolved));
|
||||
Assert.Equal(Path.GetFullPath(relativePath), resolved);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostOptionsTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PrimaryPrefix_AffectsDefaultDirectory()
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PrimaryPrefix = "MyModule"
|
||||
};
|
||||
|
||||
Assert.Equal("MyModule", options.PrimaryPrefix);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void AdditionalPrefixes_CanBeConfigured()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
options.AdditionalPrefixes.Add("Prefix1");
|
||||
options.AdditionalPrefixes.Add("Prefix2");
|
||||
|
||||
Assert.Equal(2, options.AdditionalPrefixes.Count);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void SignatureVerification_CanBeEnforced()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
options.EnforceSignatureVerification = true;
|
||||
|
||||
Assert.True(options.EnforceSignatureVerification);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void RecursiveSearch_CanBeEnabled()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
options.RecursiveSearch = true;
|
||||
|
||||
Assert.True(options.RecursiveSearch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostOptionsTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void HostVersion_CanBeConfigured()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
options.HostVersion = new Version(2, 1, 0);
|
||||
|
||||
Assert.NotNull(options.HostVersion);
|
||||
Assert.Equal(2, options.HostVersion.Major);
|
||||
Assert.Equal(1, options.HostVersion.Minor);
|
||||
Assert.Equal(0, options.HostVersion.Build);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void VersionCompatibility_CanBeEnforced()
|
||||
{
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
options.HostVersion = new Version(1, 0, 0);
|
||||
options.RequireVersionAttribute = true;
|
||||
options.EnforceVersionCompatibility = true;
|
||||
options.StrictMajorVersionCheck = true;
|
||||
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.EnforceVersionCompatibility);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
using StellaOps.Plugin.Hosting;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for PluginHostOptions configuration.
|
||||
/// </summary>
|
||||
public sealed class PluginHostOptionsTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void DefaultValues_AreCorrect()
|
||||
{
|
||||
// Arrange & Act
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Assert
|
||||
Assert.True(options.EnsureDirectoryExists);
|
||||
Assert.True(options.RecursiveSearch); // Default is true
|
||||
Assert.False(options.EnforceSignatureVerification);
|
||||
Assert.True(options.RequireVersionAttribute); // Default is true
|
||||
Assert.True(options.EnforceVersionCompatibility); // Default is true
|
||||
Assert.True(options.StrictMajorVersionCheck); // Default is true
|
||||
Assert.Null(options.HostVersion);
|
||||
Assert.Null(options.SignatureVerifier);
|
||||
Assert.Empty(options.SearchPatterns);
|
||||
Assert.Empty(options.PluginOrder);
|
||||
Assert.Empty(options.AdditionalPrefixes);
|
||||
Assert.Null(options.BaseDirectory);
|
||||
Assert.Null(options.PluginsDirectory);
|
||||
Assert.Null(options.PrimaryPrefix);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Search Pattern Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void SearchPatterns_CanBeAdded()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Act
|
||||
options.SearchPatterns.Add("MyPrefix.Plugin.*.dll");
|
||||
options.SearchPatterns.Add("Another.Plugin.*.dll");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, options.SearchPatterns.Count);
|
||||
Assert.Contains("MyPrefix.Plugin.*.dll", options.SearchPatterns);
|
||||
Assert.Contains("Another.Plugin.*.dll", options.SearchPatterns);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Plugin Order Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PluginOrder_CanBeConfigured()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Act
|
||||
options.PluginOrder.Add("PluginA");
|
||||
options.PluginOrder.Add("PluginB");
|
||||
options.PluginOrder.Add("PluginC");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, options.PluginOrder.Count);
|
||||
Assert.Equal("PluginA", options.PluginOrder[0]);
|
||||
Assert.Equal("PluginB", options.PluginOrder[1]);
|
||||
Assert.Equal("PluginC", options.PluginOrder[2]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BaseDirectory Resolution Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void ResolveBaseDirectory_WhenNull_ReturnsAppContextBaseDirectory()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
BaseDirectory = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var resolved = options.ResolveBaseDirectory();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AppContext.BaseDirectory, resolved);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void ResolveBaseDirectory_WhenSet_ReturnsConfiguredValue()
|
||||
{
|
||||
// Arrange
|
||||
var customDir = Path.GetTempPath();
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
BaseDirectory = customDir
|
||||
};
|
||||
|
||||
// Act
|
||||
var resolved = options.ResolveBaseDirectory();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(customDir, resolved);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Version Configuration Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void HostVersion_CanBeConfigured()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Act
|
||||
options.HostVersion = new Version(2, 1, 0);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(options.HostVersion);
|
||||
Assert.Equal(2, options.HostVersion.Major);
|
||||
Assert.Equal(1, options.HostVersion.Minor);
|
||||
Assert.Equal(0, options.HostVersion.Build);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void VersionCompatibility_CanBeEnforced()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Act
|
||||
options.HostVersion = new Version(1, 0, 0);
|
||||
options.RequireVersionAttribute = true;
|
||||
options.EnforceVersionCompatibility = true;
|
||||
options.StrictMajorVersionCheck = true;
|
||||
|
||||
// Assert
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.EnforceVersionCompatibility);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prefix Configuration Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void PrimaryPrefix_AffectsDefaultDirectory()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PrimaryPrefix = "MyModule"
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Equal("MyModule", options.PrimaryPrefix);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void AdditionalPrefixes_CanBeConfigured()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Act
|
||||
options.AdditionalPrefixes.Add("Prefix1");
|
||||
options.AdditionalPrefixes.Add("Prefix2");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, options.AdditionalPrefixes.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signature Verification Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void SignatureVerification_CanBeEnforced()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Act
|
||||
options.EnforceSignatureVerification = true;
|
||||
|
||||
// Assert
|
||||
Assert.True(options.EnforceSignatureVerification);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Recursive Search Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void RecursiveSearch_CanBeEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions();
|
||||
|
||||
// Act
|
||||
options.RecursiveSearch = true;
|
||||
|
||||
// Assert
|
||||
Assert.True(options.RecursiveSearch);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task LoadPluginsAsync_NonExistentDirectory_ReturnsEmptyResultAsync()
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "plugins"),
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
var result = await PluginHost.LoadPluginsAsync(options);
|
||||
|
||||
Assert.Empty(result.Plugins);
|
||||
Assert.Empty(result.Failures);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task LoadPluginsAsync_WithCancellationToken_AcceptsTokenAsync()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var result = await PluginHost.LoadPluginsAsync(options, cancellationToken: cts.Token);
|
||||
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_NonExistentDirectory_ReturnsEmptyResult()
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "plugins"),
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
Assert.Empty(result.Plugins);
|
||||
Assert.Empty(result.Failures);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_EmptyDirectory_ReturnsEmptyResult()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_EnsureDirectoryExists_CreatesDirectory()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "plugins");
|
||||
Assert.False(Directory.Exists(tempDir));
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = true
|
||||
};
|
||||
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
Assert.True(Directory.Exists(tempDir));
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempDir))
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
|
||||
var parent = Path.GetDirectoryName(tempDir);
|
||||
if (parent is not null && Directory.Exists(parent))
|
||||
{
|
||||
Directory.Delete(parent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_NullOptions_ThrowsException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => PluginHost.LoadPlugins(null!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_WithPluginOrder_AcceptsOrderConfiguration()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
options.PluginOrder.Add("PluginA");
|
||||
options.PluginOrder.Add("PluginB");
|
||||
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
Assert.Empty(result.Plugins);
|
||||
Assert.NotNull(result.MissingOrderedPlugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
public sealed partial class PluginHostTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_CustomSearchPattern_RespectsPattern()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(tempDir, "NotAPlugin.dll"), "dummy");
|
||||
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
options.SearchPatterns.Add("MyPrefix.Plugin.*.dll");
|
||||
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
|
||||
namespace StellaOps.Plugin.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for PluginHost plugin loading.
|
||||
/// </summary>
|
||||
public sealed class PluginHostTests
|
||||
{
|
||||
#region LoadPlugins Directory Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_NonExistentDirectory_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "plugins"),
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Plugins);
|
||||
Assert.Empty(result.Failures);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_EmptyDirectory_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_EnsureDirectoryExists_CreatesDirectory()
|
||||
{
|
||||
// Arrange
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "plugins");
|
||||
Assert.False(Directory.Exists(tempDir));
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = true
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
// Assert
|
||||
Assert.True(Directory.Exists(tempDir));
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempDir))
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
|
||||
var parent = Path.GetDirectoryName(tempDir);
|
||||
if (parent is not null && Directory.Exists(parent))
|
||||
{
|
||||
Directory.Delete(parent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_NullOptions_ThrowsException()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => PluginHost.LoadPlugins(null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Search Pattern Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_CustomSearchPattern_RespectsPattern()
|
||||
{
|
||||
// Arrange
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
// Create a file that won't match the pattern
|
||||
File.WriteAllText(Path.Combine(tempDir, "NotAPlugin.dll"), "dummy");
|
||||
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
options.SearchPatterns.Add("MyPrefix.Plugin.*.dll");
|
||||
|
||||
// Act
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Plugin Order Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadPlugins_WithPluginOrder_AcceptsOrderConfiguration()
|
||||
{
|
||||
// Arrange
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
options.PluginOrder.Add("PluginA");
|
||||
options.PluginOrder.Add("PluginB");
|
||||
|
||||
// Act
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
// Assert - In an empty directory, no plugins are loaded
|
||||
Assert.Empty(result.Plugins);
|
||||
Assert.NotNull(result.MissingOrderedPlugins);
|
||||
// MissingOrderedPlugins only tracks plugins that were in PluginOrder but not found in discovered files
|
||||
// In an empty directory, no files are discovered, so the list may be empty
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Async Tests
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task LoadPluginsAsync_NonExistentDirectory_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), "plugins"),
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await PluginHost.LoadPluginsAsync(options);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Plugins);
|
||||
Assert.Empty(result.Failures);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task LoadPluginsAsync_WithCancellationToken_AcceptsToken()
|
||||
{
|
||||
// Arrange
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = tempDir,
|
||||
EnsureDirectoryExists = false
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
// Don't cancel - just verify the async method accepts a cancellation token
|
||||
|
||||
// Act
|
||||
var result = await PluginHost.LoadPluginsAsync(options, cancellationToken: cts.Token);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Plugins);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace StellaOps.Plugin.Tests.Security;
|
||||
public sealed class NullPluginVerifierTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task VerifyAsync_AlwaysReturnsValid()
|
||||
public async Task VerifyAsync_AlwaysReturnsValidAsync()
|
||||
{
|
||||
var verifier = NullPluginVerifier.Instance;
|
||||
|
||||
@@ -30,13 +30,14 @@ public sealed class NullPluginVerifierTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_HandlesNullPath()
|
||||
public async Task VerifyAsync_HandlesNullPathAsync()
|
||||
{
|
||||
var verifier = NullPluginVerifier.Instance;
|
||||
|
||||
// NullPluginVerifier doesn't validate the path, just returns success
|
||||
var result = await verifier.VerifyAsync(null!);
|
||||
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Null(result.FailureReason);
|
||||
Assert.Null(result.SignerIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,6 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
| AUDIT-0031-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
|
||||
| AUDIT-0031-A | DONE | Waived (test project; revalidated 2026-01-08). |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| REMED-03 | DONE | Tier 0 remediation (usings normalized); dotnet test passed 2026-02-02 (MTP0001 warning). |
|
||||
| REMED-04 | DONE | Async naming updates; ConfigureAwait(false) skipped in tests per xUnit1030; dotnet test passed 2026-02-02 (MTP0001 warning). |
|
||||
| REMED-05 | DONE | Files split <= 100 lines; service locator removed; tests enriched; dotnet test passed 2026-02-02 (MTP0001 warning). |
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class PluginCompatibilityCheckerTests
|
||||
{
|
||||
[Fact]
|
||||
public void CheckCompatibility_WithAttribute_ReturnsCompatible()
|
||||
{
|
||||
var assembly = CreateAssemblyWithVersion(
|
||||
pluginVersion: "1.2.3",
|
||||
minimumHostVersion: "1.0.0",
|
||||
maximumHostVersion: "2.0.0",
|
||||
requiresSignature: false);
|
||||
var hostVersion = new Version(1, 5, 0);
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(
|
||||
assembly,
|
||||
hostVersion,
|
||||
CompatibilityCheckOptions.Default);
|
||||
|
||||
Assert.True(result.IsCompatible);
|
||||
Assert.True(result.HasVersionAttribute);
|
||||
Assert.Equal(new Version(1, 2, 3), result.PluginVersion);
|
||||
Assert.Equal(new Version(1, 0, 0), result.MinimumHostVersion);
|
||||
Assert.Equal(new Version(2, 0, 0), result.MaximumHostVersion);
|
||||
Assert.False(result.RequiresSignature);
|
||||
Assert.Null(result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_RejectsHostBelowMinimum()
|
||||
{
|
||||
var assembly = CreateAssemblyWithVersion(
|
||||
pluginVersion: "1.0.0",
|
||||
minimumHostVersion: "2.0.0");
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(
|
||||
assembly,
|
||||
hostVersion,
|
||||
CompatibilityCheckOptions.Default);
|
||||
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.Contains("below minimum required version", result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_RejectsHostAboveMaximum()
|
||||
{
|
||||
var assembly = CreateAssemblyWithVersion(
|
||||
pluginVersion: "1.0.0",
|
||||
maximumHostVersion: "2.0.0");
|
||||
var hostVersion = new Version(3, 0, 0);
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(
|
||||
assembly,
|
||||
hostVersion,
|
||||
CompatibilityCheckOptions.Default);
|
||||
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.Contains("exceeds maximum supported version", result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_StrictMajorVersionRejectsWhenMinimumSpecified()
|
||||
{
|
||||
var assembly = CreateAssemblyWithVersion(
|
||||
pluginVersion: "1.0.0",
|
||||
minimumHostVersion: "1.0.0");
|
||||
var hostVersion = new Version(2, 0, 0);
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(
|
||||
assembly,
|
||||
hostVersion,
|
||||
CompatibilityCheckOptions.Default);
|
||||
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.Contains("declared compatibility range", result.FailureReason);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class PluginCompatibilityCheckerTests
|
||||
{
|
||||
[Fact]
|
||||
public void CheckCompatibility_LenientMode_ReturnsCompatibleForAssemblyWithoutAttribute()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(assembly, hostVersion);
|
||||
|
||||
Assert.True(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.Null(result.PluginVersion);
|
||||
Assert.Null(result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_StrictMode_RejectsAssemblyWithoutAttribute()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
var options = CompatibilityCheckOptions.Default;
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(assembly, hostVersion, options);
|
||||
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.Null(result.PluginVersion);
|
||||
Assert.NotNull(result.FailureReason);
|
||||
Assert.Contains("[StellaPluginVersion]", result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_ThrowsOnNullAssembly()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
PluginCompatibilityChecker.CheckCompatibility(null!, new Version(1, 0, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_ThrowsOnNullHostVersion()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
PluginCompatibilityChecker.CheckCompatibility(assembly, null!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class PluginCompatibilityCheckerTests
|
||||
{
|
||||
private static int _assemblySequence;
|
||||
|
||||
private static Assembly CreateAssemblyWithVersion(
|
||||
string pluginVersion,
|
||||
string? minimumHostVersion = null,
|
||||
string? maximumHostVersion = null,
|
||||
bool? requiresSignature = null)
|
||||
{
|
||||
var assemblyName = new AssemblyName(
|
||||
$"StellaOps.Plugin.Tests.Dynamic.{Interlocked.Increment(ref _assemblySequence)}");
|
||||
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name!);
|
||||
moduleBuilder.DefineType("CompatibilityMarker", TypeAttributes.Public).CreateType();
|
||||
|
||||
var ctor = typeof(StellaPluginVersionAttribute).GetConstructor(new[] { typeof(string) })!;
|
||||
var properties = new List<PropertyInfo>();
|
||||
var values = new List<object>();
|
||||
|
||||
if (minimumHostVersion != null)
|
||||
{
|
||||
properties.Add(typeof(StellaPluginVersionAttribute).GetProperty(
|
||||
nameof(StellaPluginVersionAttribute.MinimumHostVersion))!);
|
||||
values.Add(minimumHostVersion);
|
||||
}
|
||||
|
||||
if (maximumHostVersion != null)
|
||||
{
|
||||
properties.Add(typeof(StellaPluginVersionAttribute).GetProperty(
|
||||
nameof(StellaPluginVersionAttribute.MaximumHostVersion))!);
|
||||
values.Add(maximumHostVersion);
|
||||
}
|
||||
|
||||
if (requiresSignature.HasValue)
|
||||
{
|
||||
properties.Add(typeof(StellaPluginVersionAttribute).GetProperty(
|
||||
nameof(StellaPluginVersionAttribute.RequiresSignature))!);
|
||||
values.Add(requiresSignature.Value);
|
||||
}
|
||||
|
||||
var attributeBuilder = new CustomAttributeBuilder(
|
||||
ctor,
|
||||
new object[] { pluginVersion },
|
||||
properties.ToArray(),
|
||||
values.ToArray());
|
||||
|
||||
assemblyBuilder.SetCustomAttribute(attributeBuilder);
|
||||
|
||||
return assemblyBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class PluginCompatibilityCheckerTests
|
||||
{
|
||||
[Fact]
|
||||
public void CompatibilityCheckOptions_DefaultRequiresVersionAttribute()
|
||||
{
|
||||
var options = CompatibilityCheckOptions.Default;
|
||||
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompatibilityCheckOptions_LenientDoesNotRequireVersionAttribute()
|
||||
{
|
||||
var options = CompatibilityCheckOptions.Lenient;
|
||||
|
||||
Assert.False(options.RequireVersionAttribute);
|
||||
Assert.False(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompatibilityCheckOptions_CanBeCustomized()
|
||||
{
|
||||
var options = new CompatibilityCheckOptions
|
||||
{
|
||||
RequireVersionAttribute = false,
|
||||
StrictMajorVersionCheck = true
|
||||
};
|
||||
|
||||
Assert.False(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed class PluginCompatibilityCheckerTests
|
||||
{
|
||||
[Fact]
|
||||
public void CheckCompatibility_LenientMode_ReturnsCompatibleForAssemblyWithoutAttribute()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
|
||||
// Default overload uses lenient options
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(assembly, hostVersion);
|
||||
|
||||
Assert.True(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.Null(result.PluginVersion);
|
||||
Assert.Null(result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_StrictMode_RejectsAssemblyWithoutAttribute()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
var hostVersion = new Version(1, 0, 0);
|
||||
var options = CompatibilityCheckOptions.Default; // Requires version attribute
|
||||
|
||||
var result = PluginCompatibilityChecker.CheckCompatibility(assembly, hostVersion, options);
|
||||
|
||||
Assert.False(result.IsCompatible);
|
||||
Assert.False(result.HasVersionAttribute);
|
||||
Assert.Null(result.PluginVersion);
|
||||
Assert.NotNull(result.FailureReason);
|
||||
Assert.Contains("[StellaPluginVersion]", result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_ThrowsOnNullAssembly()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
PluginCompatibilityChecker.CheckCompatibility(null!, new Version(1, 0, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckCompatibility_ThrowsOnNullHostVersion()
|
||||
{
|
||||
var assembly = typeof(PluginCompatibilityCheckerTests).Assembly;
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
PluginCompatibilityChecker.CheckCompatibility(assembly, null!));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1.0.0", "1.0.0", true)] // host == min → compatible
|
||||
[InlineData("2.0.0", "1.0.0", true)] // host > min → compatible
|
||||
[InlineData("1.0.0", "2.0.0", false)] // host < min → NOT compatible
|
||||
public void CheckCompatibility_ValidatesMinimumHostVersion(
|
||||
string hostVersionStr,
|
||||
string minHostVersionStr,
|
||||
bool expectedCompatible)
|
||||
{
|
||||
// This test validates the logic conceptually
|
||||
// In a real scenario, we'd need a dynamically created assembly with the attribute
|
||||
var hostVersion = Version.Parse(hostVersionStr);
|
||||
var minHostVersion = Version.Parse(minHostVersionStr);
|
||||
|
||||
// Direct validation of version comparison logic
|
||||
var isCompatible = hostVersion >= minHostVersion;
|
||||
|
||||
Assert.Equal(expectedCompatible, isCompatible);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("3.0.0", "2.0.0", false)]
|
||||
[InlineData("2.0.0", "2.0.0", true)]
|
||||
[InlineData("1.0.0", "2.0.0", true)]
|
||||
public void CheckCompatibility_ValidatesMaximumHostVersion(
|
||||
string hostVersionStr,
|
||||
string maxHostVersionStr,
|
||||
bool expectedCompatible)
|
||||
{
|
||||
var hostVersion = Version.Parse(hostVersionStr);
|
||||
var maxHostVersion = Version.Parse(maxHostVersionStr);
|
||||
|
||||
var isCompatible = hostVersion <= maxHostVersion;
|
||||
|
||||
Assert.Equal(expectedCompatible, isCompatible);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("2.0.0", "1.0.0", false)] // host major 2 > min major 1 → NOT compatible (strict)
|
||||
[InlineData("1.5.0", "1.0.0", true)] // host major 1 == min major 1 → compatible
|
||||
[InlineData("1.0.0", "1.0.0", true)] // host == min → compatible
|
||||
public void StrictMajorVersionCheck_RejectsCrossMajorVersionWhenNoMaxSpecified(
|
||||
string hostVersionStr,
|
||||
string minHostVersionStr,
|
||||
bool expectedCompatible)
|
||||
{
|
||||
var hostVersion = Version.Parse(hostVersionStr);
|
||||
var minHostVersion = Version.Parse(minHostVersionStr);
|
||||
|
||||
// With strict major version check, if plugin declares min=1.0.0 but no max,
|
||||
// and host is 2.x, it should be rejected
|
||||
var hostMajorExceedsMin = hostVersion.Major > minHostVersion.Major;
|
||||
var isCompatible = !hostMajorExceedsMin && hostVersion >= minHostVersion;
|
||||
|
||||
Assert.Equal(expectedCompatible, isCompatible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompatibilityCheckOptions_DefaultRequiresVersionAttribute()
|
||||
{
|
||||
var options = CompatibilityCheckOptions.Default;
|
||||
|
||||
Assert.True(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompatibilityCheckOptions_LenientDoesNotRequireVersionAttribute()
|
||||
{
|
||||
var options = CompatibilityCheckOptions.Lenient;
|
||||
|
||||
Assert.False(options.RequireVersionAttribute);
|
||||
Assert.False(options.StrictMajorVersionCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompatibilityCheckOptions_CanBeCustomized()
|
||||
{
|
||||
var options = new CompatibilityCheckOptions
|
||||
{
|
||||
RequireVersionAttribute = false,
|
||||
StrictMajorVersionCheck = true
|
||||
};
|
||||
|
||||
Assert.False(options.RequireVersionAttribute);
|
||||
Assert.True(options.StrictMajorVersionCheck);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class StellaPluginVersionAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_ParsesValidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.2.3");
|
||||
|
||||
Assert.Equal(new Version(1, 2, 3), attr.PluginVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ParsesTwoPartVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0");
|
||||
|
||||
Assert.Equal(new Version(1, 0), attr.PluginVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ParsesFourPartVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.2.3.4");
|
||||
|
||||
Assert.Equal(new Version(1, 2, 3, 4), attr.PluginVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnInvalidVersion()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new StellaPluginVersionAttribute("invalid"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnEmptyVersion()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new StellaPluginVersionAttribute(""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullVersion()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new StellaPluginVersionAttribute(null!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class StellaPluginVersionAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetMaximumHostVersion_ReturnsNullWhenNotSet()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0");
|
||||
|
||||
Assert.Null(attr.GetMaximumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaximumHostVersion_ParsesValidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
MaximumHostVersion = "3.0.0"
|
||||
};
|
||||
|
||||
Assert.Equal(new Version(3, 0, 0), attr.GetMaximumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaximumHostVersion_ThrowsOnInvalidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
MaximumHostVersion = "invalid"
|
||||
};
|
||||
|
||||
Assert.Throws<ArgumentException>(() => attr.GetMaximumHostVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class StellaPluginVersionAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetMinimumHostVersion_ReturnsNullWhenNotSet()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0");
|
||||
|
||||
Assert.Null(attr.GetMinimumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMinimumHostVersion_ParsesValidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
MinimumHostVersion = "2.0.0"
|
||||
};
|
||||
|
||||
Assert.Equal(new Version(2, 0, 0), attr.GetMinimumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMinimumHostVersion_ThrowsOnInvalidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
MinimumHostVersion = "invalid"
|
||||
};
|
||||
|
||||
Assert.Throws<ArgumentException>(() => attr.GetMinimumHostVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed partial class StellaPluginVersionAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void RequiresSignature_DefaultsToTrue()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0");
|
||||
|
||||
Assert.True(attr.RequiresSignature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiresSignature_CanBeSetToFalse()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
RequiresSignature = false
|
||||
};
|
||||
|
||||
Assert.False(attr.RequiresSignature);
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
using System;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Plugin.Tests.Versioning;
|
||||
|
||||
public sealed class StellaPluginVersionAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_ParsesValidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.2.3");
|
||||
|
||||
Assert.Equal(new Version(1, 2, 3), attr.PluginVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ParsesTwoPartVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0");
|
||||
|
||||
Assert.Equal(new Version(1, 0), attr.PluginVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ParsesFourPartVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.2.3.4");
|
||||
|
||||
Assert.Equal(new Version(1, 2, 3, 4), attr.PluginVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnInvalidVersion()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new StellaPluginVersionAttribute("invalid"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnEmptyVersion()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new StellaPluginVersionAttribute(""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullVersion()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new StellaPluginVersionAttribute(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMinimumHostVersion_ReturnsNullWhenNotSet()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0");
|
||||
|
||||
Assert.Null(attr.GetMinimumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMinimumHostVersion_ParsesValidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
MinimumHostVersion = "2.0.0"
|
||||
};
|
||||
|
||||
Assert.Equal(new Version(2, 0, 0), attr.GetMinimumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaximumHostVersion_ReturnsNullWhenNotSet()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0");
|
||||
|
||||
Assert.Null(attr.GetMaximumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaximumHostVersion_ParsesValidVersion()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
MaximumHostVersion = "3.0.0"
|
||||
};
|
||||
|
||||
Assert.Equal(new Version(3, 0, 0), attr.GetMaximumHostVersion());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiresSignature_DefaultsToTrue()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0");
|
||||
|
||||
Assert.True(attr.RequiresSignature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiresSignature_CanBeSetToFalse()
|
||||
{
|
||||
var attr = new StellaPluginVersionAttribute("1.0.0")
|
||||
{
|
||||
RequiresSignature = false
|
||||
};
|
||||
|
||||
Assert.False(attr.RequiresSignature);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user