This commit is contained in:
StellaOps Bot
2025-12-14 23:20:14 +02:00
parent 3411e825cd
commit b058dbe031
356 changed files with 68310 additions and 1108 deletions

View File

@@ -0,0 +1,124 @@
using System;
using StellaOps.Plugin.Hosting;
using StellaOps.Plugin.Security;
using Xunit;
namespace StellaOps.Plugin.Tests.Hosting;
public sealed 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()
{
var options = new PluginHostOptions
{
HostVersion = new Version(2, 1, 0)
};
Assert.Equal(new Version(2, 1, 0), options.HostVersion);
}
[Fact]
public void SignatureVerifier_CanBeSet()
{
var verifier = NullPluginVerifier.Instance;
var options = new PluginHostOptions
{
SignatureVerifier = verifier
};
Assert.Same(verifier, options.SignatureVerifier);
}
[Fact]
public void EnforceVersionCompatibility_CanBeDisabled()
{
var options = new PluginHostOptions
{
EnforceVersionCompatibility = false
};
Assert.False(options.EnforceVersionCompatibility);
}
[Fact]
public void EnforceSignatureVerification_CanBeEnabled()
{
var options = new PluginHostOptions
{
EnforceSignatureVerification = true
};
Assert.True(options.EnforceSignatureVerification);
}
[Fact]
public void AdditionalPrefixes_CanBePopulated()
{
var options = new PluginHostOptions();
options.AdditionalPrefixes.Add("MyCompany");
options.AdditionalPrefixes.Add("ThirdParty");
Assert.Equal(2, options.AdditionalPrefixes.Count);
Assert.Contains("MyCompany", options.AdditionalPrefixes);
Assert.Contains("ThirdParty", options.AdditionalPrefixes);
}
[Fact]
public void RequireVersionAttribute_CanBeDisabled()
{
var options = new PluginHostOptions
{
RequireVersionAttribute = false
};
Assert.False(options.RequireVersionAttribute);
}
[Fact]
public void StrictMajorVersionCheck_CanBeDisabled()
{
var options = new PluginHostOptions
{
StrictMajorVersionCheck = false
};
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");
}
}

View File

@@ -0,0 +1,60 @@
using StellaOps.Plugin.Hosting;
using Xunit;
namespace StellaOps.Plugin.Tests.Hosting;
public sealed class PluginLoadFailureTests
{
[Fact]
public void Record_StoresValues()
{
var failure = new PluginLoadFailure(
AssemblyPath: "/path/to/plugin.dll",
Reason: PluginLoadFailureReason.SignatureInvalid,
Message: "Signature verification failed");
Assert.Equal("/path/to/plugin.dll", failure.AssemblyPath);
Assert.Equal(PluginLoadFailureReason.SignatureInvalid, failure.Reason);
Assert.Equal("Signature verification failed", failure.Message);
}
[Fact]
public void PluginLoadFailureReason_HasExpectedValues()
{
Assert.Equal(0, (int)PluginLoadFailureReason.LoadError);
Assert.Equal(1, (int)PluginLoadFailureReason.SignatureInvalid);
Assert.Equal(2, (int)PluginLoadFailureReason.IncompatibleVersion);
Assert.Equal(3, (int)PluginLoadFailureReason.MissingVersionAttribute);
}
[Fact]
public void MissingVersionAttribute_CanBeUsedInFailure()
{
var failure = new PluginLoadFailure(
AssemblyPath: "/path/to/unversioned-plugin.dll",
Reason: PluginLoadFailureReason.MissingVersionAttribute,
Message: "Plugin does not declare a [StellaPluginVersion] attribute");
Assert.Equal("/path/to/unversioned-plugin.dll", failure.AssemblyPath);
Assert.Equal(PluginLoadFailureReason.MissingVersionAttribute, failure.Reason);
Assert.Contains("StellaPluginVersion", failure.Message);
}
[Fact]
public void Record_SupportsEquality()
{
var failure1 = new PluginLoadFailure("/path/plugin.dll", PluginLoadFailureReason.LoadError, "Error");
var failure2 = new PluginLoadFailure("/path/plugin.dll", PluginLoadFailureReason.LoadError, "Error");
Assert.Equal(failure1, failure2);
}
[Fact]
public void Record_SupportsInequality()
{
var failure1 = new PluginLoadFailure("/path/plugin.dll", PluginLoadFailureReason.LoadError, "Error");
var failure2 = new PluginLoadFailure("/path/other.dll", PluginLoadFailureReason.LoadError, "Error");
Assert.NotEqual(failure1, failure2);
}
}

View File

@@ -0,0 +1,44 @@
using StellaOps.Plugin.Security;
using Xunit;
namespace StellaOps.Plugin.Tests.Security;
public sealed class CosignVerifierOptionsTests
{
[Fact]
public void DefaultValues_AreCorrect()
{
var options = new CosignVerifierOptions();
Assert.Null(options.CosignPath);
Assert.Null(options.PublicKeyPath);
Assert.Null(options.CertificatePath);
Assert.Null(options.CertificateIdentity);
Assert.Null(options.CertificateOidcIssuer);
Assert.True(options.UseRekorTransparencyLog);
Assert.False(options.AllowUnsigned);
}
[Fact]
public void Properties_CanBeSet()
{
var options = new CosignVerifierOptions
{
CosignPath = "/usr/local/bin/cosign",
PublicKeyPath = "/keys/cosign.pub",
CertificatePath = "/certs/cert.pem",
CertificateIdentity = "test@example.com",
CertificateOidcIssuer = "https://accounts.google.com",
UseRekorTransparencyLog = false,
AllowUnsigned = true
};
Assert.Equal("/usr/local/bin/cosign", options.CosignPath);
Assert.Equal("/keys/cosign.pub", options.PublicKeyPath);
Assert.Equal("/certs/cert.pem", options.CertificatePath);
Assert.Equal("test@example.com", options.CertificateIdentity);
Assert.Equal("https://accounts.google.com", options.CertificateOidcIssuer);
Assert.False(options.UseRekorTransparencyLog);
Assert.True(options.AllowUnsigned);
}
}

View File

@@ -0,0 +1,42 @@
using System.Threading.Tasks;
using StellaOps.Plugin.Security;
using Xunit;
namespace StellaOps.Plugin.Tests.Security;
public sealed class NullPluginVerifierTests
{
[Fact]
public async Task VerifyAsync_AlwaysReturnsValid()
{
var verifier = NullPluginVerifier.Instance;
var result = await verifier.VerifyAsync("/path/to/any/assembly.dll");
Assert.True(result.IsValid);
Assert.Null(result.SignerIdentity);
Assert.Null(result.SignatureTimestamp);
Assert.Null(result.FailureReason);
Assert.Null(result.CertificateChain);
}
[Fact]
public void Instance_ReturnsSameInstance()
{
var instance1 = NullPluginVerifier.Instance;
var instance2 = NullPluginVerifier.Instance;
Assert.Same(instance1, instance2);
}
[Fact]
public async Task VerifyAsync_HandlesNullPath()
{
var verifier = NullPluginVerifier.Instance;
// NullPluginVerifier doesn't validate the path, just returns success
var result = await verifier.VerifyAsync(null!);
Assert.True(result.IsValid);
}
}

View File

@@ -0,0 +1,145 @@
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);
}
}

View File

@@ -0,0 +1,107 @@
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);
}
}