Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -9,6 +9,8 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.AuditPack.Services;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.AuditPack.Tests;
|
||||
|
||||
public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
@@ -29,7 +31,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadFromDirectoryAsync_LoadsPemFiles()
|
||||
{
|
||||
// Arrange
|
||||
@@ -46,7 +49,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("test-key", result.KeyIds!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadFromDirectoryAsync_FailsWithNonExistentDirectory()
|
||||
{
|
||||
// Arrange
|
||||
@@ -60,7 +64,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadFromDirectoryAsync_FailsWithEmptyPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -74,7 +79,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("required", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoadFromDirectoryAsync_LoadsFromManifest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -108,7 +114,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("stella-signing-key-001", result.KeyIds!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LoadFromBundle_ParsesJsonBundle()
|
||||
{
|
||||
// Arrange
|
||||
@@ -139,7 +146,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("bundle-key-001", result.KeyIds!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LoadFromBundle_FailsWithEmptyContent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -153,7 +161,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("empty", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LoadFromBundle_FailsWithInvalidJson()
|
||||
{
|
||||
// Arrange
|
||||
@@ -167,7 +176,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.False(result.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetPublicKey_ReturnsKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -185,7 +195,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.NotNull(result.KeyBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetPublicKey_ReturnsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
@@ -200,7 +211,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Equal("nonexistent-key", result.KeyId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetPublicKey_DetectsExpiredKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -236,7 +248,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("expired", result.Warning);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateVerificationKey_ReturnsEcdsaKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -273,7 +286,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
key.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateVerificationKey_ReturnsNullForMissingKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -287,7 +301,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Null(key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetAvailableKeyIds_ReturnsAllKeys()
|
||||
{
|
||||
// Arrange
|
||||
@@ -305,7 +320,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
|
||||
Assert.Contains("key2", keyIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Count_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.AuditPack.Services;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.AuditPack.Tests;
|
||||
|
||||
public class AuditBundleWriterTests : IDisposable
|
||||
@@ -28,7 +29,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_CreatesValidBundle()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +52,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
Assert.True(result.FileCount > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_ComputesMerkleRoot()
|
||||
{
|
||||
// Arrange
|
||||
@@ -69,7 +72,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
Assert.Equal(71, result.MerkleRoot.Length); // sha256: + 64 hex chars
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_SignsManifest_WhenSignIsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +92,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
Assert.NotNull(result.SigningAlgorithm);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_DoesNotSign_WhenSignIsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -106,7 +111,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
Assert.Null(result.SigningKeyId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_FailsWithoutSbom()
|
||||
{
|
||||
// Arrange
|
||||
@@ -134,7 +140,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
Assert.Contains("SBOM", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_IncludesOptionalVex()
|
||||
{
|
||||
// Arrange
|
||||
@@ -163,7 +170,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
Assert.True(result.FileCount >= 5); // sbom, feeds, policy, verdict, vex
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_AddsTimeAnchor()
|
||||
{
|
||||
// Arrange
|
||||
@@ -186,7 +194,8 @@ public class AuditBundleWriterTests : IDisposable
|
||||
Assert.True(result.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_DeterministicMerkleRoot()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -11,6 +11,8 @@ using System.Text.Json;
|
||||
using StellaOps.AuditPack.Models;
|
||||
using StellaOps.AuditPack.Services;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.AuditPack.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,7 +43,8 @@ public class AuditReplayE2ETests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task E2E_ExportTransferReplayOffline_MatchingVerdict()
|
||||
{
|
||||
// ===== PHASE 1: EXPORT =====
|
||||
@@ -146,7 +149,8 @@ public class AuditReplayE2ETests : IDisposable
|
||||
Assert.Equal(decision, replayResult.OriginalDecision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task E2E_ReplayDetectsTamperedSbom()
|
||||
{
|
||||
// Setup
|
||||
@@ -222,7 +226,8 @@ public class AuditReplayE2ETests : IDisposable
|
||||
tamperedRead.Manifest?.Inputs.SbomDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task E2E_DeterministicMerkleRoot_SameInputs()
|
||||
{
|
||||
// Create identical inputs
|
||||
@@ -271,7 +276,8 @@ public class AuditReplayE2ETests : IDisposable
|
||||
Assert.Equal(result1.MerkleRoot, result2.MerkleRoot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task E2E_BundleContainsAllRequiredFiles()
|
||||
{
|
||||
// Setup
|
||||
@@ -323,7 +329,8 @@ public class AuditReplayE2ETests : IDisposable
|
||||
Assert.Contains(filePaths, p => p.Contains("vex"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task E2E_FullCycleWithTimeAnchor()
|
||||
{
|
||||
// Setup with explicit time anchor
|
||||
|
||||
@@ -22,5 +22,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.AuditPack/StellaOps.AuditPack.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.Canonicalization.Json;
|
||||
using StellaOps.Canonicalization.Ordering;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Canonicalization.Tests;
|
||||
|
||||
public class CanonicalJsonSerializerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialize_Dictionary_OrdersKeysAlphabetically()
|
||||
{
|
||||
var dict = new Dictionary<string, int> { ["z"] = 1, ["a"] = 2, ["m"] = 3 };
|
||||
@@ -15,7 +17,8 @@ public class CanonicalJsonSerializerTests
|
||||
json.Should().Be("{\"a\":2,\"m\":3,\"z\":1}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialize_DateTimeOffset_UsesUtcIso8601()
|
||||
{
|
||||
var dt = new DateTimeOffset(2024, 1, 15, 10, 30, 0, TimeSpan.FromHours(5));
|
||||
@@ -24,7 +27,8 @@ public class CanonicalJsonSerializerTests
|
||||
json.Should().Contain("2024-01-15T05:30:00.000Z");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialize_NullValues_AreOmitted()
|
||||
{
|
||||
var obj = new { Name = "test", Value = (string?)null };
|
||||
@@ -32,7 +36,8 @@ public class CanonicalJsonSerializerTests
|
||||
json.Should().NotContain("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SerializeWithDigest_ProducesConsistentDigest()
|
||||
{
|
||||
var obj = new { Name = "test", Value = 123 };
|
||||
@@ -44,7 +49,8 @@ public class CanonicalJsonSerializerTests
|
||||
|
||||
public class PackageOrdererTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StableOrder_OrdersByPurlFirst()
|
||||
{
|
||||
var packages = new[]
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Canonicalization\StellaOps.Canonicalization.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,7 @@ using StellaOps.Authority.Plugins.Abstractions;
|
||||
using StellaOps.Configuration;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
@@ -17,7 +18,8 @@ public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_ReturnsConfiguration_ForEnabledPlugin()
|
||||
{
|
||||
var pluginDir = Path.Combine(tempRoot, "etc", "authority.plugins");
|
||||
@@ -43,7 +45,8 @@ public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
Assert.True(context.Manifest.Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_Throws_WhenEnabledConfigMissing()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
@@ -62,7 +65,8 @@ public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
Assert.Contains("standard.yaml", ex.FileName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_SkipsMissingFile_ForDisabledPlugin()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
@@ -83,7 +87,8 @@ public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
Assert.Null(context.Configuration["connection:host"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ThrowsForUnknownCapability()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
@@ -98,7 +103,8 @@ public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
Assert.Contains("unknown capability", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_ReturnsWarning_WhenStandardPasswordPolicyWeaker()
|
||||
{
|
||||
var pluginDir = Path.Combine(tempRoot, "etc", "authority.plugins");
|
||||
@@ -127,7 +133,8 @@ public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
Assert.Contains("symbol requirement disabled", diagnostic.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_ReturnsNoDiagnostics_WhenPasswordPolicyMatchesBaseline()
|
||||
{
|
||||
var pluginDir = Path.Combine(tempRoot, "etc", "authority.plugins");
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
using StellaOps.Auth;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public class AuthorityTelemetryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceName_AndNamespace_MatchExpectations()
|
||||
{
|
||||
Assert.Equal("stellaops-authority", AuthorityTelemetry.ServiceName);
|
||||
Assert.Equal("stellaops", AuthorityTelemetry.ServiceNamespace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildDefaultResourceAttributes_ContainsExpectedKeys()
|
||||
{
|
||||
var attributes = AuthorityTelemetry.BuildDefaultResourceAttributes();
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Configuration/StellaOps.Configuration.csproj" />
|
||||
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -5,11 +5,13 @@ using Microsoft.Extensions.Configuration;
|
||||
using StellaOps.Configuration;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_IssuerMissing()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions();
|
||||
@@ -19,7 +21,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Contains("issuer", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_Collections()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -52,7 +55,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Equal(new[] { "cloud-openai", "sovereign-local" }, options.AdvisoryAi.RemoteInference.AllowedProfiles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_RemoteInferenceEnabledWithoutProfiles()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -72,7 +76,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Contains("remote inference", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_PluginDescriptors()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -105,7 +110,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Equal("password", normalized.Capabilities[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Allows_TenantRemoteInferenceConsent_WhenConfigured()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -141,7 +147,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Equal(DateTimeOffset.Parse("2025-10-31T12:34:56Z", CultureInfo.InvariantCulture), tenant.AdvisoryAi.RemoteInference.ConsentedAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_TenantRemoteInferenceConsentMissingVersion()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -174,7 +181,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Contains("consentVersion", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_StorageConnectionStringMissing()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -192,7 +200,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Contains("connection string", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_Binds_From_Configuration()
|
||||
{
|
||||
var context = StellaOpsAuthorityConfiguration.Build(options =>
|
||||
@@ -247,7 +256,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Equal("file", options.Signing.KeySource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_ExceptionRoutingTemplates()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -280,7 +290,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.True(template.Value.RequireMfa);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_ExceptionRoutingTemplatesDuplicate()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
@@ -309,7 +320,8 @@ public class StellaOpsAuthorityOptionsTests
|
||||
Assert.Contains("secops", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_RateLimitingInvalid()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
|
||||
@@ -6,11 +6,14 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Kms;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Kms.Tests;
|
||||
|
||||
public sealed class CloudKmsClientTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AwsClient_Signs_Verifies_And_Exports_Metadata()
|
||||
{
|
||||
using var fixture = new EcdsaFixture();
|
||||
@@ -53,7 +56,8 @@ public sealed class CloudKmsClientTests
|
||||
Assert.Equal(fixture.Parameters.Q.Y, exported.Qy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GcpClient_Uses_Primary_When_Version_Not_Specified()
|
||||
{
|
||||
using var fixture = new EcdsaFixture();
|
||||
@@ -90,7 +94,8 @@ public sealed class CloudKmsClientTests
|
||||
Assert.Equal(fixture.Parameters.Q.Y, exported.Qy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void KmsCryptoProvider_Skips_NonExportable_Keys()
|
||||
{
|
||||
using var fixture = new EcdsaFixture();
|
||||
@@ -113,7 +118,8 @@ public sealed class CloudKmsClientTests
|
||||
Assert.Empty(keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Pkcs11Client_Signs_Verifies_And_Exports()
|
||||
{
|
||||
using var fixture = new EcdsaFixture();
|
||||
@@ -150,7 +156,8 @@ public sealed class CloudKmsClientTests
|
||||
Assert.Equal(fixture.Parameters.Q.Y, exported.Qy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fido2Client_Signs_Verifies_And_Exports()
|
||||
{
|
||||
using var fixture = new EcdsaFixture();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
using StellaOps.Cryptography.Kms;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Kms.Tests;
|
||||
|
||||
public sealed class FileKmsClientTests : IDisposable
|
||||
@@ -12,7 +14,8 @@ public sealed class FileKmsClientTests : IDisposable
|
||||
_rootPath = Path.Combine(Path.GetTempPath(), $"kms-tests-{Guid.NewGuid():N}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RotateSignVerifyLifecycle_Works()
|
||||
{
|
||||
using var client = CreateClient();
|
||||
@@ -49,7 +52,8 @@ public sealed class FileKmsClientTests : IDisposable
|
||||
Assert.True(await client.VerifyAsync(keyId, previousVersion.VersionId, firstData, firstSignature.Signature));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RevokePreventsSigning()
|
||||
{
|
||||
using var client = CreateClient();
|
||||
@@ -66,7 +70,8 @@ public sealed class FileKmsClientTests : IDisposable
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => client.SignAsync(keyId, null, data));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_ReturnsKeyMaterial()
|
||||
{
|
||||
using var client = CreateClient();
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Cryptography.Kms/StellaOps.Cryptography.Kms.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -15,14 +15,16 @@ public class OfflineVerificationProviderTests
|
||||
_provider = new OfflineVerificationCryptoProvider();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Name_ReturnsCorrectValue()
|
||||
{
|
||||
// Assert
|
||||
_provider.Name.Should().Be("offline-verification");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(CryptoCapability.Signing, "ES256", true)]
|
||||
[InlineData(CryptoCapability.Signing, "ES384", true)]
|
||||
[InlineData(CryptoCapability.Signing, "ES512", true)]
|
||||
@@ -52,7 +54,8 @@ public class OfflineVerificationProviderTests
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("SHA-256", "hello world", "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")]
|
||||
[InlineData("SHA-384", "hello world", "fdbd8e75a67f29f701a4e040385e2e23986303ea10239211af907fcbb83578b3e417cb71ce646efd0819dd8c088de1bd")]
|
||||
[InlineData("SHA-512", "hello world", "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f")]
|
||||
@@ -71,7 +74,8 @@ public class OfflineVerificationProviderTests
|
||||
actualHex.Should().Be(expectedHex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetHasher_WithUnsupportedAlgorithm_ThrowsNotSupportedException()
|
||||
{
|
||||
// Act
|
||||
@@ -82,7 +86,8 @@ public class OfflineVerificationProviderTests
|
||||
.WithMessage("*MD5*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetPasswordHasher_ThrowsNotSupportedException()
|
||||
{
|
||||
// Act
|
||||
@@ -93,7 +98,8 @@ public class OfflineVerificationProviderTests
|
||||
.WithMessage("*Password hashing*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256")]
|
||||
[InlineData("ES384")]
|
||||
[InlineData("ES512")]
|
||||
@@ -132,7 +138,8 @@ public class OfflineVerificationProviderTests
|
||||
isValid.Should().BeTrue("ephemeral verifier should verify signature from original key");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateEphemeralVerifier_ForRsaPkcs1_VerifiesSignatureCorrectly()
|
||||
{
|
||||
// Arrange - Create a real RSA key, sign a message
|
||||
@@ -151,7 +158,8 @@ public class OfflineVerificationProviderTests
|
||||
isValid.Should().BeTrue("ephemeral RSA verifier should verify PKCS1 signature from original key");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateEphemeralVerifier_ForRsaPss_VerifiesSignatureCorrectly()
|
||||
{
|
||||
// Arrange - Create a real RSA key, sign a message
|
||||
@@ -170,7 +178,8 @@ public class OfflineVerificationProviderTests
|
||||
isValid.Should().BeTrue("ephemeral RSA verifier should verify PSS signature from original key");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256")]
|
||||
[InlineData("PS256")]
|
||||
public void EphemeralVerifier_SignAsync_ThrowsNotSupportedException(string algorithmId)
|
||||
@@ -199,7 +208,8 @@ public class OfflineVerificationProviderTests
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256")]
|
||||
[InlineData("PS256")]
|
||||
public void EphemeralVerifier_WithTamperedMessage_FailsVerification(string algorithmId)
|
||||
@@ -233,7 +243,8 @@ public class OfflineVerificationProviderTests
|
||||
isValid.Should().BeFalse("ephemeral verifier should fail with tampered message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateEphemeralVerifier_WithUnsupportedAlgorithm_ThrowsNotSupportedException()
|
||||
{
|
||||
// Arrange - Create a dummy public key
|
||||
@@ -248,7 +259,8 @@ public class OfflineVerificationProviderTests
|
||||
.WithMessage("*UNSUPPORTED*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256")]
|
||||
[InlineData("PS256")]
|
||||
public void EphemeralVerifier_HasCorrectProperties(string algorithmId)
|
||||
@@ -258,6 +270,7 @@ public class OfflineVerificationProviderTests
|
||||
using (var ecdsa = ECDsa.Create())
|
||||
{
|
||||
publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
|
||||
using StellaOps.TestKit;
|
||||
}
|
||||
|
||||
// Act
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.OfflineVerification\StellaOps.Cryptography.Plugin.OfflineVerification.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,13 +2,15 @@ using System;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class Argon2idPasswordHasherTests
|
||||
{
|
||||
private readonly Argon2idPasswordHasher hasher = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Hash_ProducesPhcEncodedString()
|
||||
{
|
||||
var options = new PasswordHashOptions();
|
||||
@@ -17,7 +19,8 @@ public class Argon2idPasswordHasherTests
|
||||
Assert.StartsWith("$argon2id$", encoded, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Verify_ReturnsTrue_ForCorrectPassword()
|
||||
{
|
||||
var options = new PasswordHashOptions();
|
||||
@@ -27,7 +30,8 @@ public class Argon2idPasswordHasherTests
|
||||
Assert.False(hasher.Verify("wrong", encoded));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NeedsRehash_ReturnsTrue_WhenParametersChange()
|
||||
{
|
||||
var options = new PasswordHashOptions();
|
||||
|
||||
@@ -5,11 +5,14 @@ using StellaOps.Cryptography.DependencyInjection;
|
||||
using StellaOps.Cryptography.Plugin.BouncyCastle;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public sealed class BouncyCastleEd25519CryptoProviderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAndVerify_WithBouncyCastleProvider_Succeeds()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder().Build();
|
||||
|
||||
@@ -7,11 +7,14 @@ using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.CryptoPro;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class CryptoProGostSignerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExportPublicJsonWebKey_ContainsCertificateChain()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
|
||||
@@ -6,11 +6,13 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class CryptoProviderRegistryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResolveOrThrow_RespectsPreferredProviderOrder()
|
||||
{
|
||||
var providerA = new FakeCryptoProvider("providerA")
|
||||
@@ -28,7 +30,8 @@ public class CryptoProviderRegistryTests
|
||||
Assert.Same(providerB, resolved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResolveSigner_UsesPreferredProviderHint()
|
||||
{
|
||||
var providerA = new FakeCryptoProvider("providerA")
|
||||
@@ -59,7 +62,8 @@ public class CryptoProviderRegistryTests
|
||||
Assert.Equal("key-a", fallbackResolution.Signer.KeyId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegistryOptions_UsesActiveProfileOrder()
|
||||
{
|
||||
var options = new StellaOps.Cryptography.DependencyInjection.CryptoProviderRegistryOptions();
|
||||
|
||||
@@ -8,13 +8,16 @@ using Org.BouncyCastle.Crypto.Digests;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public sealed class DefaultCryptoHashTests
|
||||
{
|
||||
private static readonly byte[] Sample = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_Sha256_MatchesBcl()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
@@ -23,7 +26,8 @@ public sealed class DefaultCryptoHashTests
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_Sha512_MatchesBcl()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
@@ -32,7 +36,8 @@ public sealed class DefaultCryptoHashTests
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_Gost256_MatchesBouncyCastle()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
@@ -41,7 +46,8 @@ public sealed class DefaultCryptoHashTests
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_Gost512_MatchesBouncyCastle()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
@@ -50,7 +56,8 @@ public sealed class DefaultCryptoHashTests
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ComputeHashAsync_Stream_MatchesBuffer()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
@@ -60,7 +67,8 @@ public sealed class DefaultCryptoHashTests
|
||||
Assert.Equal(Convert.ToHexString(bufferDigest), Convert.ToHexString(streamDigest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHashHex_Sha256_MatchesBclLowerHex()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
@@ -69,7 +77,8 @@ public sealed class DefaultCryptoHashTests
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ComputeHashHexAsync_Sha256_MatchesBclLowerHex()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Threading.Tasks;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public sealed class DefaultCryptoHmacTests
|
||||
@@ -13,7 +15,8 @@ public sealed class DefaultCryptoHmacTests
|
||||
private static readonly byte[] Sample = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
|
||||
private static readonly byte[] Key = Encoding.UTF8.GetBytes("test-key");
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHmacHexForPurpose_WebhookInterop_MatchesBclLowerHex()
|
||||
{
|
||||
var hmac = DefaultCryptoHmac.CreateForTests();
|
||||
@@ -22,7 +25,8 @@ public sealed class DefaultCryptoHmacTests
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ComputeHmacHexForPurposeAsync_WebhookInterop_MatchesBclLowerHex()
|
||||
{
|
||||
var hmac = DefaultCryptoHmac.CreateForTests();
|
||||
|
||||
@@ -7,11 +7,14 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class DefaultCryptoProviderSigningTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task UpsertSigningKey_AllowsSignAndVerifyEs256()
|
||||
{
|
||||
var provider = new DefaultCryptoProvider();
|
||||
@@ -52,7 +55,8 @@ public class DefaultCryptoProviderSigningTests
|
||||
Assert.False(tamperedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RemoveSigningKey_PreventsRetrieval()
|
||||
{
|
||||
var provider = new DefaultCryptoProvider();
|
||||
|
||||
@@ -5,11 +5,13 @@ using System.Security.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.CryptoPro;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class GostSignatureEncodingTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(32)]
|
||||
[InlineData(64)]
|
||||
public void RawAndDer_RoundTrip(int coordinateLength)
|
||||
@@ -25,14 +27,16 @@ public class GostSignatureEncodingTests
|
||||
Assert.Equal(raw, roundTrip);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToDer_Throws_When_Length_Invalid()
|
||||
{
|
||||
var raw = new byte[10];
|
||||
Assert.Throws<CryptographicException>(() => GostSignatureEncoding.ToDer(raw, coordinateLength: 32));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRaw_Throws_When_Not_Der()
|
||||
{
|
||||
var raw = new byte[64];
|
||||
|
||||
@@ -5,11 +5,14 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class LibsodiumCryptoProviderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LibsodiumProvider_SignsAndVerifiesEs256()
|
||||
{
|
||||
var provider = new LibsodiumCryptoProvider();
|
||||
|
||||
@@ -3,6 +3,7 @@ using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.OfflineVerification;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public sealed class OfflineVerificationCryptoProviderTests
|
||||
@@ -14,7 +15,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
_provider = new OfflineVerificationCryptoProvider();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Name_ReturnsOfflineVerification()
|
||||
{
|
||||
// Act
|
||||
@@ -24,7 +26,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
name.Should().Be("offline-verification");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256")]
|
||||
[InlineData("ES384")]
|
||||
[InlineData("ES512")]
|
||||
@@ -43,7 +46,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
supports.Should().BeTrue($"{algorithmId} should be supported for signing");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256")]
|
||||
[InlineData("ES384")]
|
||||
[InlineData("ES512")]
|
||||
@@ -62,7 +66,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
supports.Should().BeTrue($"{algorithmId} should be supported for verification");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("SHA-256")]
|
||||
[InlineData("SHA-384")]
|
||||
[InlineData("SHA-512")]
|
||||
@@ -78,7 +83,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
supports.Should().BeTrue($"{algorithmId} should be supported for content hashing");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("PBKDF2")]
|
||||
[InlineData("Argon2id")]
|
||||
public void Supports_PasswordHashingAlgorithms_ReturnsTrue(string algorithmId)
|
||||
@@ -90,7 +96,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
supports.Should().BeTrue($"{algorithmId} should be reported as supported for password hashing");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256K")]
|
||||
[InlineData("EdDSA")]
|
||||
[InlineData("UNKNOWN")]
|
||||
@@ -103,7 +110,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
supports.Should().BeFalse($"{algorithmId} should not be supported");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Supports_SymmetricEncryption_ReturnsFalse()
|
||||
{
|
||||
// Act
|
||||
@@ -113,7 +121,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
supports.Should().BeFalse("Symmetric encryption should not be supported");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("SHA-256")]
|
||||
[InlineData("SHA-384")]
|
||||
[InlineData("SHA-512")]
|
||||
@@ -130,7 +139,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
hasher.AlgorithmId.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetHasher_UnsupportedAlgorithm_ThrowsNotSupportedException()
|
||||
{
|
||||
// Act
|
||||
@@ -141,7 +151,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
.WithMessage("*MD5*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetHasher_SHA256_ComputesCorrectHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -156,7 +167,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
hash.Length.Should().Be(32); // SHA-256 produces 32 bytes
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetHasher_SHA256_ProducesDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +184,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
hash1.Should().Equal(hash2, "Same data should produce same hash");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetPasswordHasher_ThrowsNotSupportedException()
|
||||
{
|
||||
// Act
|
||||
@@ -183,7 +196,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
.WithMessage("*not supported*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSigner_UnsupportedAlgorithm_ThrowsNotSupportedException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -197,7 +211,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
.WithMessage("*UNKNOWN*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateEphemeralVerifier_UnsupportedAlgorithm_ThrowsNotSupportedException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -211,7 +226,8 @@ public sealed class OfflineVerificationCryptoProviderTests
|
||||
.WithMessage("*UNKNOWN*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("ES256")]
|
||||
[InlineData("ES384")]
|
||||
[InlineData("ES512")]
|
||||
|
||||
@@ -11,11 +11,13 @@ using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.OpenSslGost;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class OpenSslGostSignerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAndVerify_WithManagedProvider_Succeeds()
|
||||
{
|
||||
var keyPair = GenerateKeyPair();
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class PasswordHashOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_DoesNotThrow_ForDefaults()
|
||||
{
|
||||
var options = new PasswordHashOptions();
|
||||
options.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_WhenMemoryInvalid()
|
||||
{
|
||||
var options = new PasswordHashOptions
|
||||
|
||||
@@ -2,13 +2,15 @@ using System;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class Pbkdf2PasswordHasherTests
|
||||
{
|
||||
private readonly Pbkdf2PasswordHasher hasher = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Hash_ProducesLegacyFormat()
|
||||
{
|
||||
var options = new PasswordHashOptions
|
||||
@@ -22,7 +24,8 @@ public class Pbkdf2PasswordHasherTests
|
||||
Assert.StartsWith("PBKDF2.", encoded, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Verify_Succeeds_ForCorrectPassword()
|
||||
{
|
||||
var options = new PasswordHashOptions
|
||||
@@ -37,7 +40,8 @@ public class Pbkdf2PasswordHasherTests
|
||||
Assert.False(hasher.Verify("other", encoded));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NeedsRehash_DetectsIterationChange()
|
||||
{
|
||||
var options = new PasswordHashOptions
|
||||
|
||||
@@ -7,11 +7,14 @@ using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.Pkcs11Gost;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class Pkcs11GostProviderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DescribeKeys_ExposesLibraryPathAndThumbprint()
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("STELLAOPS_PKCS11_ENABLED"), "1", StringComparison.Ordinal))
|
||||
|
||||
@@ -4,18 +4,21 @@ using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Digests;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public sealed class Sha256DigestTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Normalize_AllowsBareHex_WhenPrefixNotRequired()
|
||||
{
|
||||
var hex = new string('a', Sha256Digest.HexLength);
|
||||
Assert.Equal($"sha256:{hex}", Sha256Digest.Normalize(hex));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Normalize_NormalizesPrefixAndHexToLower()
|
||||
{
|
||||
var hexUpper = new string('A', Sha256Digest.HexLength);
|
||||
@@ -24,7 +27,8 @@ public sealed class Sha256DigestTests
|
||||
Sha256Digest.Normalize($"SHA256:{hexUpper}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Normalize_RequiresPrefix_WhenConfigured()
|
||||
{
|
||||
var hex = new string('a', Sha256Digest.HexLength);
|
||||
@@ -33,14 +37,16 @@ public sealed class Sha256DigestTests
|
||||
Assert.Contains("sha256:", ex.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExtractHex_ReturnsLowercaseHex()
|
||||
{
|
||||
var hexUpper = new string('A', Sha256Digest.HexLength);
|
||||
Assert.Equal(new string('a', Sha256Digest.HexLength), Sha256Digest.ExtractHex($"sha256:{hexUpper}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Compute_UsesCryptoHashStack()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
|
||||
@@ -13,6 +13,7 @@ using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.SmSoft;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class SmSoftCryptoProviderTests : IDisposable
|
||||
@@ -25,7 +26,8 @@ public class SmSoftCryptoProviderTests : IDisposable
|
||||
Environment.SetEnvironmentVariable("SM_SOFT_ALLOWED", "1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAndVerify_Sm2_Works()
|
||||
{
|
||||
var provider = new SmSoftCryptoProvider();
|
||||
@@ -47,7 +49,8 @@ public class SmSoftCryptoProviderTests : IDisposable
|
||||
Assert.False(string.IsNullOrEmpty(jwk.Y));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Hash_Sm3_Works()
|
||||
{
|
||||
var provider = new SmSoftCryptoProvider();
|
||||
|
||||
@@ -21,5 +21,6 @@
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.OfflineVerification\StellaOps.Cryptography.Plugin.OfflineVerification.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.OpenSslGost\StellaOps.Cryptography.Plugin.OpenSslGost.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.SmSoft\StellaOps.Cryptography.Plugin.SmSoft.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -8,11 +8,13 @@ using StellaOps.DeltaVerdict.Serialization;
|
||||
using StellaOps.DeltaVerdict.Signing;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.DeltaVerdict.Tests;
|
||||
|
||||
public class DeltaVerdictTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDelta_TracksComponentAndVulnerabilityChanges()
|
||||
{
|
||||
var baseVerdict = CreateVerdict(
|
||||
@@ -52,7 +54,8 @@ public class DeltaVerdictTests
|
||||
delta.Summary.TotalChanges.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RiskBudgetEvaluator_FlagsCriticalViolations()
|
||||
{
|
||||
var delta = new DeltaVerdict.Models.DeltaVerdict
|
||||
@@ -85,7 +88,8 @@ public class DeltaVerdictTests
|
||||
result.Violations.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SigningService_RoundTrip_VerifiesEnvelope()
|
||||
{
|
||||
var delta = new DeltaVerdict.Models.DeltaVerdict
|
||||
@@ -122,7 +126,8 @@ public class DeltaVerdictTests
|
||||
verify.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serializer_ComputesDeterministicDigest()
|
||||
{
|
||||
var verdict = CreateVerdict(
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.DeltaVerdict\StellaOps.DeltaVerdict.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -16,6 +16,7 @@ using StellaOps.Evidence.Storage.Postgres.Tests.Fixtures;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Evidence.Storage.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -52,7 +53,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
|
||||
#region Multi-Module Evidence for Same Subject
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SameSubject_MultipleEvidenceTypes_AllLinked()
|
||||
{
|
||||
// Arrange - A container image subject with evidence from multiple modules
|
||||
@@ -87,7 +89,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
_output.WriteLine($"Subject {subjectNodeId} has {allEvidence.Count} evidence records from different modules");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SameSubject_FilterByType_ReturnsCorrectEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -111,7 +114,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
|
||||
#region Evidence Chain Scenarios
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EvidenceChain_ScanToVexToPolicy_LinkedCorrectly()
|
||||
{
|
||||
// Scenario: Vulnerability scan → VEX assessment → Policy decision
|
||||
@@ -147,7 +151,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
_output.WriteLine($"Evidence chain: Scan({scan.EvidenceId}) → VEX({vex.EvidenceId}) → Policy({policy.EvidenceId})");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EvidenceChain_ReachabilityToEpssToPolicy_LinkedCorrectly()
|
||||
{
|
||||
// Scenario: Reachability analysis + EPSS score → Policy decision
|
||||
@@ -178,7 +183,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
|
||||
#region Multi-Tenant Evidence Isolation
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MultiTenant_SameSubject_IsolatedByTenant()
|
||||
{
|
||||
// Arrange - Two tenants with evidence for the same subject
|
||||
@@ -214,7 +220,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
|
||||
#region Evidence Graph Queries
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EvidenceGraph_AllTypesForArtifact_ReturnsComplete()
|
||||
{
|
||||
// Arrange - Simulate a complete evidence graph for a container artifact
|
||||
@@ -251,7 +258,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EvidenceGraph_ExistsCheck_ForAllTypes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -272,7 +280,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
|
||||
#region Cross-Module Evidence Correlation
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Correlation_SameCorrelationId_FindsRelatedEvidence()
|
||||
{
|
||||
// Arrange - Evidence from different modules with same correlation ID
|
||||
@@ -295,7 +304,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
allEvidence.Should().OnlyContain(e => e.Provenance.CorrelationId == correlationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Generators_MultiplePerSubject_AllPreserved()
|
||||
{
|
||||
// Arrange - Evidence from different generators
|
||||
@@ -322,7 +332,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
|
||||
#region Evidence Count and Statistics
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CountBySubject_AfterMultiModuleInserts_ReturnsCorrectCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -339,7 +350,8 @@ public sealed class CrossModuleEvidenceLinkingTests : IAsyncLifetime
|
||||
count.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByType_AcrossSubjects_ReturnsAll()
|
||||
{
|
||||
// Arrange - Multiple subjects with same evidence type
|
||||
|
||||
@@ -12,6 +12,7 @@ using StellaOps.Evidence.Storage.Postgres.Tests.Fixtures;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Evidence.Storage.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -47,7 +48,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Store Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreAsync_NewEvidence_ReturnsEvidenceId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -61,7 +63,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
_output.WriteLine($"Stored evidence: {storedId}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreAsync_DuplicateEvidence_IsIdempotent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -80,7 +83,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreBatchAsync_MultipleRecords_StoresAllSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
@@ -98,7 +102,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreBatchAsync_WithDuplicates_StoresOnlyUnique()
|
||||
{
|
||||
// Arrange
|
||||
@@ -116,7 +121,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region GetById Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ExistingEvidence_ReturnsEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -136,7 +142,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
retrieved.Provenance.GeneratorId.Should().Be(evidence.Provenance.GeneratorId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_NonExistingEvidence_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -149,7 +156,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
retrieved.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_WithSignatures_PreservesSignatures()
|
||||
{
|
||||
// Arrange
|
||||
@@ -170,7 +178,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region GetBySubject Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetBySubjectAsync_MultipleEvidence_ReturnsAll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -196,7 +205,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
.Contain(new[] { EvidenceType.Scan, EvidenceType.Reachability, EvidenceType.Policy });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetBySubjectAsync_WithTypeFilter_ReturnsFiltered()
|
||||
{
|
||||
// Arrange
|
||||
@@ -213,7 +223,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
retrieved[0].EvidenceType.Should().Be(EvidenceType.Scan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetBySubjectAsync_NoEvidence_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
@@ -230,7 +241,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region GetByType Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByTypeAsync_MultipleEvidence_ReturnsMatchingType()
|
||||
{
|
||||
// Arrange
|
||||
@@ -246,7 +258,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
retrieved.Should().OnlyContain(e => e.EvidenceType == EvidenceType.Scan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByTypeAsync_WithLimit_RespectsLimit()
|
||||
{
|
||||
// Arrange
|
||||
@@ -266,7 +279,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Exists Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExistsAsync_ExistingEvidence_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -280,7 +294,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
exists.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExistsAsync_NonExistingEvidence_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -294,7 +309,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
exists.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExistsAsync_NonExistingSubject_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -311,7 +327,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Delete Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ExistingEvidence_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -329,7 +346,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
retrieved.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeleteAsync_NonExistingEvidence_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -346,7 +364,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Count Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CountBySubjectAsync_MultipleEvidence_ReturnsCorrectCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -362,7 +381,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
count.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CountBySubjectAsync_NoEvidence_ReturnsZero()
|
||||
{
|
||||
// Arrange
|
||||
@@ -379,7 +399,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Integrity Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RoundTrip_EvidenceRecord_PreservesIntegrity()
|
||||
{
|
||||
// Arrange
|
||||
@@ -394,7 +415,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
retrieved!.VerifyIntegrity().Should().BeTrue("evidence ID should match computed hash");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RoundTrip_BinaryPayload_PreservesData()
|
||||
{
|
||||
// Arrange
|
||||
@@ -417,7 +439,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
retrieved!.Payload.ToArray().Should().BeEquivalentTo(binaryPayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RoundTrip_UnicodePayload_PreservesData()
|
||||
{
|
||||
// Arrange
|
||||
@@ -446,7 +469,8 @@ public sealed class PostgresEvidenceStoreIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Factory Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Factory_CreateStore_ReturnsTenantScopedStore()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Evidence.Storage.Postgres\StellaOps.Evidence.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,11 +6,13 @@ using StellaOps.Evidence.Services;
|
||||
using StellaOps.Evidence.Validation;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Evidence.Tests;
|
||||
|
||||
public class EvidenceIndexTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidenceLinker_BuildsIndexWithDigest()
|
||||
{
|
||||
var linker = new EvidenceLinker();
|
||||
@@ -24,7 +26,8 @@ public class EvidenceIndexTests
|
||||
index.Sboms.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidenceValidator_FlagsMissingSbom()
|
||||
{
|
||||
var index = CreateIndex() with { Sboms = [] };
|
||||
@@ -34,7 +37,8 @@ public class EvidenceIndexTests
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidenceSerializer_RoundTrip_PreservesFields()
|
||||
{
|
||||
var index = CreateIndex();
|
||||
@@ -43,7 +47,8 @@ public class EvidenceIndexTests
|
||||
deserialized.Should().BeEquivalentTo(index);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidenceQueryService_BuildsSummary()
|
||||
{
|
||||
var index = CreateIndex();
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Evidence\StellaOps.Evidence.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,8 @@ using StellaOps.Infrastructure.Postgres.Testing;
|
||||
using Testcontainers.PostgreSql;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Infrastructure.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -30,7 +32,8 @@ public sealed class PostgresFixtureTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Initialize_CreatesSchema()
|
||||
{
|
||||
// Arrange
|
||||
@@ -45,7 +48,8 @@ public sealed class PostgresFixtureTests : IAsyncLifetime
|
||||
options.SchemaName.Should().StartWith("test_initialize_createsschema_");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TruncateAllTables_ClearsTables()
|
||||
{
|
||||
// Arrange
|
||||
@@ -74,7 +78,8 @@ public sealed class PostgresFixtureTests : IAsyncLifetime
|
||||
count.Should().Be(0L);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispose_DropsSchema()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using StellaOps.Microservice.AspNetCore;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.AspNetCore.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -30,7 +31,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
|
||||
#region Route Normalization
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("api/items", "/api/items")]
|
||||
[InlineData("/api/items", "/api/items")]
|
||||
[InlineData("/api/items/", "/api/items")]
|
||||
@@ -51,7 +53,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
Assert.Equal(expected, endpoints[0].Path);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("/api/items/{id:int}", "/api/items/{id}")]
|
||||
[InlineData("/api/items/{id:guid}", "/api/items/{id}")]
|
||||
[InlineData("/api/items/{name:alpha:minlength(3)}", "/api/items/{name}")]
|
||||
@@ -71,7 +74,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
Assert.Equal(expected, endpoints[0].Path);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("/api/{**path}", "/api/{path}")]
|
||||
[InlineData("/files/{*filepath}", "/files/{filepath}")]
|
||||
public void NormalizeRoutePattern_NormalizesCatchAll(string input, string expected)
|
||||
@@ -93,7 +97,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
|
||||
#region Deterministic Ordering
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_OrdersByPathThenMethod()
|
||||
{
|
||||
// Arrange - create endpoints in random order
|
||||
@@ -134,7 +139,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
Assert.Equal("POST", discovered[5].Method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_IsDeterministicAcrossMultipleCalls()
|
||||
{
|
||||
// Arrange
|
||||
@@ -167,7 +173,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
|
||||
#region Duplicate Detection
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_SkipsDuplicates()
|
||||
{
|
||||
// Arrange - same path and method twice
|
||||
@@ -193,7 +200,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
|
||||
#region Excluded Paths
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_ExcludesConfiguredPaths()
|
||||
{
|
||||
// Arrange
|
||||
@@ -226,7 +234,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
|
||||
#region HTTP Method Handling
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_HandlesMultipleMethodsPerRoute()
|
||||
{
|
||||
// Arrange - endpoint with multiple HTTP methods
|
||||
@@ -245,7 +254,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
Assert.Contains(discovered, e => e.Method == "DELETE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_NormalizesMethodToUpperCase()
|
||||
{
|
||||
// Arrange
|
||||
@@ -265,7 +275,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
|
||||
#region Metadata Extraction
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_SetsServiceNameAndVersion()
|
||||
{
|
||||
// Arrange
|
||||
@@ -284,7 +295,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
Assert.Equal("2.5.0", discovered[0].Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_ExtractsRouteParameters()
|
||||
{
|
||||
// Arrange
|
||||
@@ -310,7 +322,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
Assert.Equal(ParameterSource.Route, sectionParam.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_ExtractsOptionalRouteParameters()
|
||||
{
|
||||
// Arrange
|
||||
@@ -334,7 +347,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
|
||||
#region Caching
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_CachesResults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -349,7 +363,8 @@ public sealed class AspNetCoreEndpointDiscoveryProviderTests
|
||||
Assert.Same(result1, result2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RefreshEndpoints_ClearsCache()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Microservice.AspNetCore;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.AspNetCore.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +13,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
{
|
||||
#region No YAML Config
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_NullYamlConfig_ReturnsCodeEndpointsUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
@@ -29,7 +31,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Equal("admin", result[0].RequiringClaims[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_EmptyYamlEndpoints_ReturnsCodeEndpointsUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +53,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
|
||||
#region YamlOnly Strategy
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_YamlOnlyStrategy_NoOverrides_ClearsCodeClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -69,7 +73,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Empty(result[0].RequiringClaims); // Code claims cleared
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_YamlOnlyStrategy_WithOverrides_UsesOnlyYamlClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -95,7 +100,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Contains(result[0].RequiringClaims, c => c.Value == "write");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_YamlOnlyStrategy_NonMatchingOverride_ClearsCodeClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -119,7 +125,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
|
||||
#region AspNetMetadataOnly Strategy
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_AspNetMetadataOnlyStrategy_NoOverrides_KeepsCodeClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -138,7 +145,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Equal("admin", result[0].RequiringClaims[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_AspNetMetadataOnlyStrategy_WithOverrides_IgnoresYamlClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -163,7 +171,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Equal("admin", result[0].RequiringClaims[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_AspNetMetadataOnlyStrategy_StillAppliesNonClaimOverrides()
|
||||
{
|
||||
// Arrange
|
||||
@@ -186,7 +195,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
|
||||
#region Hybrid Strategy
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_HybridStrategy_NoOverrides_KeepsCodeClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -205,7 +215,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Equal("admin", result[0].RequiringClaims[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_HybridStrategy_YamlAddsNewClaimType_BothTypesPresent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -229,7 +240,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Contains(result[0].RequiringClaims, c => c.Type == "scope" && c.Value == "read");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_HybridStrategy_YamlOverridesSameClaimType_YamlTakesPrecedence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -258,7 +270,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.DoesNotContain(result[0].RequiringClaims, c => c.Value == "admin");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_HybridStrategy_YamlEmptyClaims_KeepsCodeClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -289,7 +302,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Equal("admin", result[0].RequiringClaims[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_HybridStrategy_MultipleClaimTypesInYaml_OnlyOverridesMatchingTypes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -326,7 +340,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
|
||||
#region Timeout and Streaming Overrides
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_AppliesTimeoutOverride()
|
||||
{
|
||||
// Arrange
|
||||
@@ -343,7 +358,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.Equal(TimeSpan.FromSeconds(60), result[0].DefaultTimeout);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_AppliesStreamingOverride()
|
||||
{
|
||||
// Arrange
|
||||
@@ -360,7 +376,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
Assert.True(result[0].SupportsStreaming);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_NullOverrideProperties_KeepsCodeDefaults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -393,7 +410,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
|
||||
#region Case Insensitive Matching
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_MatchesEndpointsIgnoringCase()
|
||||
{
|
||||
// Arrange
|
||||
@@ -416,7 +434,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
|
||||
#region Multiple Endpoints
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_MultipleEndpoints_AppliesCorrectOverrides()
|
||||
{
|
||||
// Arrange
|
||||
@@ -457,7 +476,8 @@ public sealed class AspNetEndpointOverrideMergerTests
|
||||
|
||||
#region Determinism
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_ProducesDeterministicOrder()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Microservice.AspNetCore;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.AspNetCore.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,7 +28,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
|
||||
#region AllowAnonymous
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_AllowAnonymous_ReturnsAllowAnonymousResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -47,7 +49,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.Equal(AuthorizationSource.AspNetMetadata, result.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_AllowAnonymousWithOtherAttributes_AllowAnonymousTakesPrecedence()
|
||||
{
|
||||
// Arrange - [AllowAnonymous] should override [Authorize]
|
||||
@@ -69,7 +72,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
|
||||
#region Role Extraction
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_AuthorizeWithSingleRole_ExtractsRole()
|
||||
{
|
||||
// Arrange
|
||||
@@ -90,7 +94,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.Equal(AuthorizationSource.AspNetMetadata, result.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_AuthorizeWithMultipleRoles_ExtractsAllRoles()
|
||||
{
|
||||
// Arrange
|
||||
@@ -112,7 +117,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.All(result.Claims, c => Assert.Equal(ClaimTypes.Role, c.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_MultipleAuthorizeAttributes_CombinesRoles()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +137,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.Contains("Moderator", result.Roles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_DuplicateRoles_Deduplicated()
|
||||
{
|
||||
// Arrange
|
||||
@@ -154,7 +161,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
|
||||
#region Policy Extraction
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_AuthorizeWithPolicy_ExtractsPolicy()
|
||||
{
|
||||
// Arrange
|
||||
@@ -173,7 +181,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.Equal(AuthorizationSource.AspNetMetadata, result.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_MultipleAuthorizeWithPolicies_ExtractsAllPolicies()
|
||||
{
|
||||
// Arrange
|
||||
@@ -192,7 +201,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.Contains("Policy2", result.Policies);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_DuplicatePolicies_Deduplicated()
|
||||
{
|
||||
// Arrange
|
||||
@@ -214,7 +224,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
|
||||
#region No Authorization
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_NoAuthorization_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -232,7 +243,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.Equal(AuthorizationSource.None, result.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_AuthorizeWithoutRolesOrPolicy_HasNoClaimsButSourceIsAspNet()
|
||||
{
|
||||
// Arrange - [Authorize] without roles or policy means "authenticated only"
|
||||
@@ -258,7 +270,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
|
||||
#region Combined Roles and Policies
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Map_AuthorizeWithRolesAndPolicy_ExtractsBoth()
|
||||
{
|
||||
// Arrange
|
||||
@@ -282,28 +295,32 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
|
||||
#region HasAuthorization
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HasAuthorization_WhenAllowAnonymous_ReturnsTrue()
|
||||
{
|
||||
var result = new AuthorizationMappingResult { AllowAnonymous = true };
|
||||
Assert.True(result.HasAuthorization);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HasAuthorization_WhenHasRoles_ReturnsTrue()
|
||||
{
|
||||
var result = new AuthorizationMappingResult { Roles = ["Admin"] };
|
||||
Assert.True(result.HasAuthorization);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HasAuthorization_WhenHasPolicies_ReturnsTrue()
|
||||
{
|
||||
var result = new AuthorizationMappingResult { Policies = ["Policy1"] };
|
||||
Assert.True(result.HasAuthorization);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HasAuthorization_WhenHasClaims_ReturnsTrue()
|
||||
{
|
||||
var result = new AuthorizationMappingResult
|
||||
@@ -313,7 +330,8 @@ public sealed class DefaultAuthorizationClaimMapperTests
|
||||
Assert.True(result.HasAuthorization);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HasAuthorization_WhenEmpty_ReturnsFalse()
|
||||
{
|
||||
var result = new AuthorizationMappingResult();
|
||||
|
||||
@@ -9,6 +9,8 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using StellaOps.Router.Common.Frames;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.AspNetCore.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -295,7 +297,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region FromQuery Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_StringParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -310,7 +313,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("test-search-term", body.GetProperty("query").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_IntParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -326,7 +330,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal(25, body.GetProperty("pageSize").GetInt32());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_BoolParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -341,7 +346,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.True(body.GetProperty("includeDeleted").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_MultipleParameters_BindCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -359,7 +365,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.False(body.GetProperty("includeDeleted").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_DefaultValues_UsedWhenNotProvided()
|
||||
{
|
||||
// Arrange
|
||||
@@ -377,7 +384,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("asc", body.GetProperty("sortOrder").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_OverrideDefaults_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -395,7 +403,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("desc", body.GetProperty("sortOrder").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_UrlEncodedValues_BindCorrectly()
|
||||
{
|
||||
// Arrange - URL encode "hello world & test"
|
||||
@@ -414,7 +423,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region FromRoute Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_SinglePathParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -429,7 +439,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("user-123", body.GetProperty("userId").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_MultiplePathParameters_BindCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -445,7 +456,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("widget-456", body.GetProperty("itemId").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_NumericPathParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -460,7 +472,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("12345", body.GetProperty("userId").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_GuidPathParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -480,7 +493,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region FromHeader Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromHeader_AuthorizationHeader_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -498,7 +512,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("Bearer test-token-12345", body.GetProperty("authorization").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromHeader_CustomHeaders_BindCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -520,7 +535,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("en-US", body.GetProperty("acceptLanguage").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromHeader_AllHeadersAccessible()
|
||||
{
|
||||
// Arrange
|
||||
@@ -545,7 +561,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region FromBody Tests (JSON)
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_SimpleJson_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -561,7 +578,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Contains("Hello, World!", body.GetProperty("echo").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_ComplexObject_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -579,7 +597,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("john@example.com", body.GetProperty("email").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_RawBody_HandledCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -597,7 +616,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal(textBody.Length.ToString(), response.Headers["X-Echo-Length"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_LargePayload_HandledCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -614,7 +634,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Contains(largeMessage, body.GetProperty("echo").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_UnicodeContent_HandledCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -635,7 +656,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region FromForm Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_SimpleFormData_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -653,7 +675,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.True(body.GetProperty("rememberMe").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_UrlEncodedSpecialChars_BindsCorrectly()
|
||||
{
|
||||
// Arrange - Special characters that need URL encoding
|
||||
@@ -669,7 +692,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("p@ss=word&special!", body.GetProperty("password").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_ContentType_IsCorrect()
|
||||
{
|
||||
// Arrange
|
||||
@@ -689,7 +713,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Combined Binding Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CombinedBinding_PathAndBody_BindCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -707,7 +732,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("New description", body.GetProperty("description").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CombinedBinding_PathQueryAndBody_BindCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -730,7 +756,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region HTTP Method Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpGet_ReturnsData()
|
||||
{
|
||||
// Arrange
|
||||
@@ -745,7 +772,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("get-test-user", body.GetProperty("userId").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPost_CreatesResource()
|
||||
{
|
||||
// Arrange
|
||||
@@ -761,7 +789,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.True(body.GetProperty("success").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPut_UpdatesResource()
|
||||
{
|
||||
// Arrange
|
||||
@@ -778,7 +807,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("Updated Name", body.GetProperty("name").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPatch_PartialUpdate()
|
||||
{
|
||||
// Arrange
|
||||
@@ -796,7 +826,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal(29.99m, body.GetProperty("price").GetDecimal());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPatch_OnlySpecifiedFields_Updated()
|
||||
{
|
||||
// Arrange - Only update name, not price
|
||||
@@ -814,7 +845,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.DoesNotContain("price", updatedFields.EnumerateArray().Select(e => e.GetString()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpDelete_RemovesResource()
|
||||
{
|
||||
// Arrange
|
||||
@@ -834,7 +866,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SimpleEndpoint_NoParameters()
|
||||
{
|
||||
// Arrange
|
||||
@@ -849,7 +882,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("OK", body.GetProperty("status").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NonExistentEndpoint_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
@@ -862,7 +896,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal(404, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WrongHttpMethod_Returns404()
|
||||
{
|
||||
// Arrange - /quick is GET only
|
||||
@@ -875,7 +910,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal(404, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConcurrentRequests_AllSucceed()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Microservice.AspNetCore\StellaOps.Microservice.AspNetCore.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -8,6 +8,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Router.Common.Frames;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.AspNetCore.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +126,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Service Registration Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_RegisteredCorrectly()
|
||||
{
|
||||
// Assert - All required services are registered
|
||||
@@ -135,7 +138,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.NotNull(_app.Services.GetService<IAspNetEndpointOverrideMerger>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BridgeOptions_ConfiguredCorrectly()
|
||||
{
|
||||
// Act
|
||||
@@ -149,7 +153,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), options.DefaultTimeout);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InstanceId_GeneratedIfNotProvided()
|
||||
{
|
||||
// Act
|
||||
@@ -165,7 +170,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Endpoint Discovery Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointDiscovery_FindsAllEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -186,7 +192,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.Contains(endpoints, e => e.Method == "POST" && e.Path == "/api/items");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointDiscovery_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -210,7 +217,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointDiscovery_IncludesServiceMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -229,7 +237,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Request Dispatch Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_SimpleGet_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
@@ -244,7 +253,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("Healthy", body.GetProperty("status").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_GetWithRouteParameter_BindsParameter()
|
||||
{
|
||||
// Arrange
|
||||
@@ -260,7 +270,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("Item-test-item-123", body.GetProperty("name").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_PostWithJsonBody_BindsBody()
|
||||
{
|
||||
// Arrange
|
||||
@@ -277,7 +288,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal("New Test Item", body.GetProperty("name").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_PutWithRouteAndBody_BindsBoth()
|
||||
{
|
||||
// Arrange
|
||||
@@ -295,7 +307,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.True(body.GetProperty("updated").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_Delete_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
@@ -311,7 +324,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.True(body.GetProperty("deleted").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_NotFound_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
@@ -324,7 +338,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.Equal(404, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_WrongMethod_Returns404()
|
||||
{
|
||||
// Arrange - /api/health is GET only
|
||||
@@ -341,7 +356,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Identity Population Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_WithIdentityHeaders_PopulatesUser()
|
||||
{
|
||||
// Arrange
|
||||
@@ -365,7 +381,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Concurrent Requests Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_ConcurrentRequests_AllSucceed()
|
||||
{
|
||||
// Arrange
|
||||
@@ -391,7 +408,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Response Capture Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_ResponseContainsAllExpectedFields()
|
||||
{
|
||||
// Arrange
|
||||
@@ -407,7 +425,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
Assert.False(response.HasMoreChunks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_ContentTypeHeader_Preserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -469,7 +488,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
|
||||
/// </summary>
|
||||
public sealed class StellaRouterBridgeOptionsValidationTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddStellaRouterBridge_MissingServiceName_ThrowsInvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -488,7 +508,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
|
||||
Assert.Contains("ServiceName", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddStellaRouterBridge_MissingVersion_ThrowsInvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -507,7 +528,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
|
||||
Assert.Contains("Version", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddStellaRouterBridge_MissingRegion_ThrowsInvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -526,7 +548,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
|
||||
Assert.Contains("Region", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddStellaRouterBridge_ZeroTimeout_ThrowsInvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -547,7 +570,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
|
||||
Assert.Contains("DefaultTimeout", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddStellaRouterBridge_ExcessiveTimeout_ThrowsInvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -568,7 +592,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
|
||||
Assert.Contains("DefaultTimeout", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddStellaRouterBridge_ValidOptions_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -61,7 +61,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
|
||||
#region Basic Generation Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithTypedEndpoint_GeneratesSource()
|
||||
{
|
||||
// Arrange
|
||||
@@ -98,7 +99,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
generatedSource.Should().Contain("GET");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithRawEndpoint_GeneratesSource()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +133,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
generatedSource.Should().Contain("/raw/upload");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithMultipleEndpoints_GeneratesAll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -175,7 +178,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
generatedSource.Should().Contain("/endpoint2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithNoEndpoints_GeneratesNothing()
|
||||
{
|
||||
// Arrange
|
||||
@@ -201,7 +205,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
|
||||
#region Attribute Property Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithTimeout_IncludesTimeoutInGeneration()
|
||||
{
|
||||
// Arrange
|
||||
@@ -232,7 +237,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
generatedSource.Should().Contain("FromSeconds(120)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithStreaming_IncludesStreamingFlag()
|
||||
{
|
||||
// Arrange
|
||||
@@ -260,7 +266,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
generatedSource.Should().Contain("SupportsStreaming = true");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithRequiredClaims_IncludesClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -297,7 +304,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
|
||||
#region HTTP Method Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
@@ -337,7 +345,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
|
||||
#region Error Cases Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithAbstractClass_ReportsDiagnostic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -368,7 +377,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
generatedSource.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithMissingInterface_ReportsDiagnostic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -399,7 +409,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
|
||||
#region Generated Provider Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_GeneratesProviderClass()
|
||||
{
|
||||
// Arrange
|
||||
@@ -439,7 +450,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
|
||||
#region Namespace Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithGlobalNamespace_HandlesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -468,7 +480,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
generatedSource.Should().Contain("GlobalEndpoint");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithNestedNamespace_HandlesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -504,7 +517,8 @@ public sealed class StellaEndpointGeneratorTests
|
||||
|
||||
#region Path Escaping Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generator_WithSpecialCharactersInPath_EscapesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -513,6 +527,7 @@ public sealed class StellaEndpointGeneratorTests
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Microservice;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace TestNamespace
|
||||
{
|
||||
public record Req();
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<ProjectReference Include="..\..\StellaOps.Microservice.SourceGen\StellaOps.Microservice.SourceGen.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Microservice\StellaOps.Microservice.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,7 +40,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
|
||||
#region DiscoverEndpoints Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_CallsDiscoveryProvider()
|
||||
{
|
||||
// Arrange
|
||||
@@ -52,7 +54,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
_discoveryProviderMock.Verify(d => d.DiscoverEndpoints(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_LoadsYamlConfig()
|
||||
{
|
||||
// Arrange
|
||||
@@ -65,7 +68,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
_yamlLoaderMock.Verify(l => l.Load(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_MergesCodeAndYaml()
|
||||
{
|
||||
// Arrange
|
||||
@@ -93,7 +97,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
_mergerMock.Verify(m => m.Merge(codeEndpoints, yamlConfig), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_ReturnsMergedEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -119,7 +124,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
result.Should().BeSameAs(mergedEndpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_WhenYamlLoadFails_UsesCodeEndpointsOnly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -139,7 +145,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
_mergerMock.Verify(m => m.Merge(codeEndpoints, null), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_WithMultipleEndpoints_ReturnsAll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -162,7 +169,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
result.Should().HaveCount(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_EmptyEndpoints_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
@@ -179,7 +187,8 @@ public sealed class EndpointDiscoveryServiceTests
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiscoverEndpoints_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -20,7 +21,8 @@ public sealed class EndpointRegistryTests
|
||||
|
||||
#region Register Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Register_SingleEndpoint_AddsToRegistry()
|
||||
{
|
||||
// Arrange
|
||||
@@ -35,7 +37,8 @@ public sealed class EndpointRegistryTests
|
||||
registry.GetAllEndpoints()[0].Should().Be(endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Register_MultipleEndpoints_AddsAllToRegistry()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +53,8 @@ public sealed class EndpointRegistryTests
|
||||
registry.GetAllEndpoints().Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegisterAll_AddsAllEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -69,7 +73,8 @@ public sealed class EndpointRegistryTests
|
||||
registry.GetAllEndpoints().Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegisterAll_WithEmptyCollection_DoesNotAddAny()
|
||||
{
|
||||
// Arrange
|
||||
@@ -86,7 +91,8 @@ public sealed class EndpointRegistryTests
|
||||
|
||||
#region TryMatch Method Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ExactMethodAndPath_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -102,7 +108,8 @@ public sealed class EndpointRegistryTests
|
||||
match!.Endpoint.Path.Should().Be("/api/users");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_NonMatchingMethod_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -117,7 +124,8 @@ public sealed class EndpointRegistryTests
|
||||
match.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_NonMatchingPath_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -132,7 +140,8 @@ public sealed class EndpointRegistryTests
|
||||
match.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_MethodIsCaseInsensitive()
|
||||
{
|
||||
// Arrange
|
||||
@@ -145,7 +154,8 @@ public sealed class EndpointRegistryTests
|
||||
registry.TryMatch("GET", "/api/users", out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_PathIsCaseInsensitive_WhenEnabled()
|
||||
{
|
||||
// Arrange
|
||||
@@ -157,7 +167,8 @@ public sealed class EndpointRegistryTests
|
||||
registry.TryMatch("GET", "/Api/Users", out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_PathIsCaseSensitive_WhenDisabled()
|
||||
{
|
||||
// Arrange
|
||||
@@ -173,7 +184,8 @@ public sealed class EndpointRegistryTests
|
||||
|
||||
#region TryMatch Path Parameter Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_PathWithParameter_ExtractsParameter()
|
||||
{
|
||||
// Arrange
|
||||
@@ -190,7 +202,8 @@ public sealed class EndpointRegistryTests
|
||||
match.PathParameters["id"].Should().Be("123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_PathWithMultipleParameters_ExtractsAll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -208,7 +221,8 @@ public sealed class EndpointRegistryTests
|
||||
match.PathParameters["orderId"].Should().Be("789");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_PathParameterWithSpecialChars_ExtractsParameter()
|
||||
{
|
||||
// Arrange
|
||||
@@ -223,7 +237,8 @@ public sealed class EndpointRegistryTests
|
||||
match!.PathParameters["itemId"].Should().Be("item-with-dashes");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_EmptyPathParameter_DoesNotMatch()
|
||||
{
|
||||
// Arrange
|
||||
@@ -241,7 +256,8 @@ public sealed class EndpointRegistryTests
|
||||
|
||||
#region TryMatch Multiple Endpoints Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_FirstMatchingEndpoint_ReturnsFirst()
|
||||
{
|
||||
// Arrange
|
||||
@@ -256,7 +272,8 @@ public sealed class EndpointRegistryTests
|
||||
match.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_SelectsCorrectEndpointByMethod()
|
||||
{
|
||||
// Arrange
|
||||
@@ -291,7 +308,8 @@ public sealed class EndpointRegistryTests
|
||||
|
||||
#region GetAllEndpoints Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetAllEndpoints_EmptyRegistry_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
@@ -304,7 +322,8 @@ public sealed class EndpointRegistryTests
|
||||
endpoints.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetAllEndpoints_ReturnsAllRegisteredEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -324,7 +343,8 @@ public sealed class EndpointRegistryTests
|
||||
endpoints.Should().Contain(endpoint3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetAllEndpoints_PreservesRegistrationOrder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -349,7 +369,8 @@ public sealed class EndpointRegistryTests
|
||||
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_DefaultCaseInsensitive_IsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -360,7 +381,8 @@ public sealed class EndpointRegistryTests
|
||||
registry.TryMatch("GET", "/api/test", out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_ExplicitCaseInsensitiveFalse_IsCaseSensitive()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,7 +8,8 @@ public sealed class HeaderCollectionTests
|
||||
{
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Default_CreatesEmptyCollection()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -17,7 +19,8 @@ public sealed class HeaderCollectionTests
|
||||
headers.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_WithKeyValuePairs_AddsAllHeaders()
|
||||
{
|
||||
// Arrange
|
||||
@@ -35,7 +38,8 @@ public sealed class HeaderCollectionTests
|
||||
headers["Accept"].Should().Be("application/json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_WithDuplicateKeys_AddsMultipleValues()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +60,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region Empty Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Empty_IsSharedInstance()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -67,7 +72,8 @@ public sealed class HeaderCollectionTests
|
||||
empty1.Should().BeSameAs(empty2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Empty_HasNoHeaders()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -81,7 +87,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region Indexer Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Indexer_ExistingKey_ReturnsFirstValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -95,7 +102,8 @@ public sealed class HeaderCollectionTests
|
||||
value.Should().Be("application/json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Indexer_MultipleValues_ReturnsFirstValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -110,7 +118,8 @@ public sealed class HeaderCollectionTests
|
||||
value.Should().Be("application/json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Indexer_NonexistentKey_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -123,7 +132,8 @@ public sealed class HeaderCollectionTests
|
||||
value.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Indexer_IsCaseInsensitive()
|
||||
{
|
||||
// Arrange
|
||||
@@ -140,7 +150,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region Add Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Add_NewKey_AddsHeader()
|
||||
{
|
||||
// Arrange
|
||||
@@ -153,7 +164,8 @@ public sealed class HeaderCollectionTests
|
||||
headers["Content-Type"].Should().Be("application/json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Add_ExistingKey_AppendsValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -167,7 +179,8 @@ public sealed class HeaderCollectionTests
|
||||
headers.GetValues("Accept").Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Add_CaseInsensitiveKey_AppendsToExisting()
|
||||
{
|
||||
// Arrange
|
||||
@@ -185,7 +198,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region Set Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Set_NewKey_AddsHeader()
|
||||
{
|
||||
// Arrange
|
||||
@@ -198,7 +212,8 @@ public sealed class HeaderCollectionTests
|
||||
headers["Content-Type"].Should().Be("application/json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Set_ExistingKey_ReplacesValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -217,7 +232,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region GetValues Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetValues_ExistingKey_ReturnsAllValues()
|
||||
{
|
||||
// Arrange
|
||||
@@ -233,7 +249,8 @@ public sealed class HeaderCollectionTests
|
||||
values.Should().BeEquivalentTo(["application/json", "text/plain", "text/html"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetValues_NonexistentKey_ReturnsEmptyEnumerable()
|
||||
{
|
||||
// Arrange
|
||||
@@ -246,7 +263,8 @@ public sealed class HeaderCollectionTests
|
||||
values.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetValues_IsCaseInsensitive()
|
||||
{
|
||||
// Arrange
|
||||
@@ -262,7 +280,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region TryGetValue Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryGetValue_ExistingKey_ReturnsTrueAndValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -277,7 +296,8 @@ public sealed class HeaderCollectionTests
|
||||
value.Should().Be("application/json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryGetValue_NonexistentKey_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -291,7 +311,8 @@ public sealed class HeaderCollectionTests
|
||||
value.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryGetValue_IsCaseInsensitive()
|
||||
{
|
||||
// Arrange
|
||||
@@ -310,7 +331,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region ContainsKey Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ContainsKey_ExistingKey_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -321,7 +343,8 @@ public sealed class HeaderCollectionTests
|
||||
headers.ContainsKey("Content-Type").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ContainsKey_NonexistentKey_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -331,7 +354,8 @@ public sealed class HeaderCollectionTests
|
||||
headers.ContainsKey("X-Missing").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ContainsKey_IsCaseInsensitive()
|
||||
{
|
||||
// Arrange
|
||||
@@ -347,7 +371,8 @@ public sealed class HeaderCollectionTests
|
||||
|
||||
#region Enumeration Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetEnumerator_EnumeratesAllHeaderValues()
|
||||
{
|
||||
// Arrange
|
||||
@@ -366,7 +391,8 @@ public sealed class HeaderCollectionTests
|
||||
list.Should().Contain(kvp => kvp.Key == "Accept" && kvp.Value == "text/html");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetEnumerator_EmptyCollection_EnumeratesNothing()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -22,7 +24,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
|
||||
#region Track Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Track_NewRequest_ReturnsNonCancelledToken()
|
||||
{
|
||||
// Arrange
|
||||
@@ -35,7 +38,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
token.IsCancellationRequested.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Track_NewRequest_IncreasesCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -48,7 +52,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
_tracker.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Track_MultipleRequests_TracksAll()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -60,7 +65,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
_tracker.Count.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Track_DuplicateCorrelationId_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -75,7 +81,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
.WithMessage($"*{correlationId}*already being tracked*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Track_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -92,7 +99,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
|
||||
#region Cancel Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Cancel_TrackedRequest_CancelsToken()
|
||||
{
|
||||
// Arrange
|
||||
@@ -107,7 +115,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
token.IsCancellationRequested.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Cancel_UntrackedRequest_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -120,7 +129,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Cancel_WithNullReason_Works()
|
||||
{
|
||||
// Arrange
|
||||
@@ -134,7 +144,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Cancel_CompletedRequest_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -153,7 +164,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
|
||||
#region Complete Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Complete_TrackedRequest_RemovesFromTracking()
|
||||
{
|
||||
// Arrange
|
||||
@@ -167,7 +179,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
_tracker.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Complete_UntrackedRequest_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -180,7 +193,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Complete_MultipleCompletions_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -202,7 +216,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
|
||||
#region CancelAll Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CancelAll_CancelsAllTrackedRequests()
|
||||
{
|
||||
// Arrange
|
||||
@@ -219,7 +234,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
token3.IsCancellationRequested.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CancelAll_ClearsTrackedRequests()
|
||||
{
|
||||
// Arrange
|
||||
@@ -233,7 +249,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
_tracker.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CancelAll_WithNoRequests_DoesNotThrow()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -247,7 +264,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
|
||||
#region Dispose Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CancelsAllRequests()
|
||||
{
|
||||
// Arrange
|
||||
@@ -260,7 +278,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
token.IsCancellationRequested.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -279,7 +298,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
|
||||
#region Count Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Count_InitiallyZero()
|
||||
{
|
||||
// Arrange - use a fresh tracker
|
||||
@@ -289,7 +309,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
|
||||
tracker.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Count_ReflectsActiveRequests()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,7 +8,8 @@ public sealed class RawRequestContextTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Method_DefaultsToEmptyString()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -17,7 +19,8 @@ public sealed class RawRequestContextTests
|
||||
context.Method.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Path_DefaultsToEmptyString()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -27,7 +30,8 @@ public sealed class RawRequestContextTests
|
||||
context.Path.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_PathParameters_DefaultsToEmptyDictionary()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -38,7 +42,8 @@ public sealed class RawRequestContextTests
|
||||
context.PathParameters.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Headers_DefaultsToEmptyCollection()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -48,7 +53,8 @@ public sealed class RawRequestContextTests
|
||||
context.Headers.Should().BeSameAs(HeaderCollection.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Body_DefaultsToStreamNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -58,7 +64,8 @@ public sealed class RawRequestContextTests
|
||||
context.Body.Should().BeSameAs(Stream.Null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_CancellationToken_DefaultsToNone()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -68,7 +75,8 @@ public sealed class RawRequestContextTests
|
||||
context.CancellationToken.Should().Be(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_CorrelationId_DefaultsToNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -82,7 +90,8 @@ public sealed class RawRequestContextTests
|
||||
|
||||
#region Property Initialization Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Method_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -92,7 +101,8 @@ public sealed class RawRequestContextTests
|
||||
context.Method.Should().Be("POST");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Path_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -102,7 +112,8 @@ public sealed class RawRequestContextTests
|
||||
context.Path.Should().Be("/api/users/123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathParameters_CanBeInitialized()
|
||||
{
|
||||
// Arrange
|
||||
@@ -121,7 +132,8 @@ public sealed class RawRequestContextTests
|
||||
context.PathParameters["action"].Should().Be("update");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Headers_CanBeInitialized()
|
||||
{
|
||||
// Arrange
|
||||
@@ -137,7 +149,8 @@ public sealed class RawRequestContextTests
|
||||
context.Headers["Authorization"].Should().Be("Bearer token");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Body_CanBeInitialized()
|
||||
{
|
||||
// Arrange
|
||||
@@ -150,7 +163,8 @@ public sealed class RawRequestContextTests
|
||||
context.Body.Should().BeSameAs(body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CancellationToken_CanBeInitialized()
|
||||
{
|
||||
// Arrange
|
||||
@@ -163,7 +177,8 @@ public sealed class RawRequestContextTests
|
||||
context.CancellationToken.Should().Be(cts.Token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CorrelationId_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -177,7 +192,8 @@ public sealed class RawRequestContextTests
|
||||
|
||||
#region Complete Context Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CompleteContext_AllPropertiesSet_Works()
|
||||
{
|
||||
// Arrange
|
||||
@@ -210,7 +226,8 @@ public sealed class RawRequestContextTests
|
||||
context.CorrelationId.Should().Be("corr-789");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Context_WithCancelledToken_HasCancellationRequested()
|
||||
{
|
||||
// Arrange
|
||||
@@ -228,7 +245,8 @@ public sealed class RawRequestContextTests
|
||||
|
||||
#region Typical Use Case Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TypicalGetRequest_HasMinimalProperties()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -245,7 +263,8 @@ public sealed class RawRequestContextTests
|
||||
context.Headers.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TypicalPostRequest_HasBodyAndHeaders()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Text;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -9,7 +11,8 @@ public sealed class RawResponseTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_StatusCode_DefaultsTo200()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -19,7 +22,8 @@ public sealed class RawResponseTests
|
||||
response.StatusCode.Should().Be(200);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Headers_DefaultsToEmpty()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -29,7 +33,8 @@ public sealed class RawResponseTests
|
||||
response.Headers.Should().BeSameAs(HeaderCollection.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Body_DefaultsToStreamNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -43,7 +48,8 @@ public sealed class RawResponseTests
|
||||
|
||||
#region Ok Factory Method Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Ok_WithStream_CreatesOkResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +63,8 @@ public sealed class RawResponseTests
|
||||
response.Body.Should().BeSameAs(stream);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Ok_WithByteArray_CreatesOkResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -72,7 +79,8 @@ public sealed class RawResponseTests
|
||||
((MemoryStream)response.Body).ToArray().Should().BeEquivalentTo(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Ok_WithString_CreatesOkResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -87,7 +95,8 @@ public sealed class RawResponseTests
|
||||
reader.ReadToEnd().Should().Be(text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Ok_WithEmptyString_CreatesOkResponse()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -102,7 +111,8 @@ public sealed class RawResponseTests
|
||||
|
||||
#region NoContent Factory Method Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NoContent_Creates204Response()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -112,7 +122,8 @@ public sealed class RawResponseTests
|
||||
response.StatusCode.Should().Be(204);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NoContent_HasDefaultHeaders()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -122,7 +133,8 @@ public sealed class RawResponseTests
|
||||
response.Headers.Should().BeSameAs(HeaderCollection.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NoContent_HasDefaultBody()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -136,7 +148,8 @@ public sealed class RawResponseTests
|
||||
|
||||
#region BadRequest Factory Method Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BadRequest_Creates400Response()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -146,7 +159,8 @@ public sealed class RawResponseTests
|
||||
response.StatusCode.Should().Be(400);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BadRequest_WithDefaultMessage_HasBadRequestText()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -157,7 +171,8 @@ public sealed class RawResponseTests
|
||||
reader.ReadToEnd().Should().Be("Bad Request");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BadRequest_WithCustomMessage_HasCustomText()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -168,7 +183,8 @@ public sealed class RawResponseTests
|
||||
reader.ReadToEnd().Should().Be("Invalid input");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BadRequest_SetsTextPlainContentType()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -182,7 +198,8 @@ public sealed class RawResponseTests
|
||||
|
||||
#region NotFound Factory Method Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NotFound_Creates404Response()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -192,7 +209,8 @@ public sealed class RawResponseTests
|
||||
response.StatusCode.Should().Be(404);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NotFound_WithDefaultMessage_HasNotFoundText()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -203,7 +221,8 @@ public sealed class RawResponseTests
|
||||
reader.ReadToEnd().Should().Be("Not Found");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NotFound_WithCustomMessage_HasCustomText()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -218,7 +237,8 @@ public sealed class RawResponseTests
|
||||
|
||||
#region InternalError Factory Method Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InternalError_Creates500Response()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -228,7 +248,8 @@ public sealed class RawResponseTests
|
||||
response.StatusCode.Should().Be(500);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InternalError_WithDefaultMessage_HasInternalServerErrorText()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -239,7 +260,8 @@ public sealed class RawResponseTests
|
||||
reader.ReadToEnd().Should().Be("Internal Server Error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InternalError_WithCustomMessage_HasCustomText()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -254,7 +276,8 @@ public sealed class RawResponseTests
|
||||
|
||||
#region Error Factory Method Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(400, "Bad Request")]
|
||||
[InlineData(401, "Unauthorized")]
|
||||
[InlineData(403, "Forbidden")]
|
||||
@@ -271,7 +294,8 @@ public sealed class RawResponseTests
|
||||
response.StatusCode.Should().Be(statusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Error_SetsCorrectContentType()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -281,7 +305,8 @@ public sealed class RawResponseTests
|
||||
response.Headers["Content-Type"].Should().Be("text/plain; charset=utf-8");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Error_SetsMessageInBody()
|
||||
{
|
||||
// Arrange
|
||||
@@ -295,7 +320,8 @@ public sealed class RawResponseTests
|
||||
reader.ReadToEnd().Should().Be(message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Error_WithUnicodeMessage_EncodesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -313,7 +339,8 @@ public sealed class RawResponseTests
|
||||
|
||||
#region Property Initialization Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StatusCode_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -323,7 +350,8 @@ public sealed class RawResponseTests
|
||||
response.StatusCode.Should().Be(201);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Headers_CanBeInitialized()
|
||||
{
|
||||
// Arrange
|
||||
@@ -337,7 +365,8 @@ public sealed class RawResponseTests
|
||||
response.Headers["X-Custom"].Should().Be("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Body_CanBeInitialized()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -5,6 +5,8 @@ using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Microservice.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -51,7 +53,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_InitializesCorrectly()
|
||||
{
|
||||
// Act
|
||||
@@ -68,7 +71,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region CurrentStatus Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CurrentStatus_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -81,7 +85,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
manager.CurrentStatus.Should().Be(InstanceHealthStatus.Draining);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(InstanceHealthStatus.Healthy)]
|
||||
[InlineData(InstanceHealthStatus.Degraded)]
|
||||
[InlineData(InstanceHealthStatus.Draining)]
|
||||
@@ -102,7 +107,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region InFlightRequestCount Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InFlightRequestCount_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -119,7 +125,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region ErrorRate Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ErrorRate_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -136,7 +143,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region StartAsync Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StartAsync_DiscoversEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -157,7 +165,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
_discoveryProviderMock.Verify(d => d.DiscoverEndpoints(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StartAsync_WithRouters_CreatesConnections()
|
||||
{
|
||||
// Arrange
|
||||
@@ -180,7 +189,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
await manager.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StartAsync_RegistersEndpointsInConnection()
|
||||
{
|
||||
// Arrange
|
||||
@@ -210,7 +220,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
await manager.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StartAsync_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -228,7 +239,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region StopAsync Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StopAsync_ClearsConnections()
|
||||
{
|
||||
// Arrange
|
||||
@@ -252,7 +264,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region Heartbeat Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Heartbeat_SendsViaTransport()
|
||||
{
|
||||
// Arrange
|
||||
@@ -275,7 +288,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
Times.AtLeastOnce);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Heartbeat_IncludesCurrentMetrics()
|
||||
{
|
||||
// Arrange
|
||||
@@ -311,7 +325,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
|
||||
|
||||
#region Dispose Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Microservice\StellaOps.Microservice.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Router.Testing\StellaOps.Router.Testing.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.Provcache.Api;
|
||||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -22,7 +23,8 @@ public sealed class ApiContractTests
|
||||
|
||||
#region CacheSource Contract Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("none")]
|
||||
[InlineData("inMemory")]
|
||||
[InlineData("redis")]
|
||||
@@ -45,7 +47,8 @@ public sealed class ApiContractTests
|
||||
|
||||
#region TrustScoreBreakdown Contract Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TrustScoreBreakdown_DefaultWeights_SumToOne()
|
||||
{
|
||||
// Verify the standard weights sum to 1.0 (100%)
|
||||
@@ -60,7 +63,8 @@ public sealed class ApiContractTests
|
||||
totalWeight.Should().Be(1.00m, "standard weights must sum to 100%");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TrustScoreBreakdown_StandardWeights_MatchDocumentation()
|
||||
{
|
||||
// Verify weights match the documented percentages
|
||||
@@ -74,7 +78,8 @@ public sealed class ApiContractTests
|
||||
breakdown.SignerTrust.Weight.Should().Be(0.20m, "Signer trust weight should be 20%");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TrustScoreBreakdown_ComputeTotal_ReturnsCorrectWeightedSum()
|
||||
{
|
||||
// Given all scores at 100, total should be 100
|
||||
@@ -88,7 +93,8 @@ public sealed class ApiContractTests
|
||||
breakdown.ComputeTotal().Should().Be(100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TrustScoreBreakdown_ComputeTotal_WithZeroScores_ReturnsZero()
|
||||
{
|
||||
var breakdown = TrustScoreBreakdown.CreateDefault();
|
||||
@@ -96,7 +102,8 @@ public sealed class ApiContractTests
|
||||
breakdown.ComputeTotal().Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TrustScoreBreakdown_ComputeTotal_WithMixedScores_ComputesCorrectly()
|
||||
{
|
||||
// Specific test case:
|
||||
@@ -116,7 +123,8 @@ public sealed class ApiContractTests
|
||||
breakdown.ComputeTotal().Should().Be(79);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TrustScoreBreakdown_Serialization_IncludesAllComponents()
|
||||
{
|
||||
var breakdown = TrustScoreBreakdown.CreateDefault(50, 60, 70, 80, 90);
|
||||
@@ -130,7 +138,8 @@ public sealed class ApiContractTests
|
||||
json.Should().Contain("\"signerTrust\":");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TrustScoreComponent_Contribution_CalculatesCorrectly()
|
||||
{
|
||||
var component = new TrustScoreComponent { Score = 80, Weight = 0.25m };
|
||||
@@ -142,7 +151,8 @@ public sealed class ApiContractTests
|
||||
|
||||
#region DecisionDigest Contract Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DecisionDigest_TrustScoreBreakdown_IsOptional()
|
||||
{
|
||||
// DecisionDigest should serialize correctly without TrustScoreBreakdown
|
||||
@@ -168,7 +178,8 @@ public sealed class ApiContractTests
|
||||
digest.TrustScoreBreakdown.Should().BeNull("TrustScoreBreakdown should be optional");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DecisionDigest_WithBreakdown_SerializesCorrectly()
|
||||
{
|
||||
var digest = new DecisionDigest
|
||||
@@ -194,7 +205,8 @@ public sealed class ApiContractTests
|
||||
|
||||
#region InputManifest Contract Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InputManifestResponse_RequiredFields_NotNull()
|
||||
{
|
||||
var manifest = new InputManifestResponse
|
||||
@@ -218,7 +230,8 @@ public sealed class ApiContractTests
|
||||
manifest.TimeWindow.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InputManifestResponse_Serialization_IncludesAllComponents()
|
||||
{
|
||||
var manifest = new InputManifestResponse
|
||||
@@ -245,7 +258,8 @@ public sealed class ApiContractTests
|
||||
json.Should().Contain("\"generatedAt\":");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomInfoDto_OptionalFields_CanBeNull()
|
||||
{
|
||||
var sbom = new SbomInfoDto
|
||||
@@ -262,7 +276,8 @@ public sealed class ApiContractTests
|
||||
// Optional fields should not be serialized as null (default JsonSerializer behavior with ignore defaults)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexInfoDto_Sources_CanBeEmpty()
|
||||
{
|
||||
var vex = new VexInfoDto
|
||||
@@ -276,7 +291,8 @@ public sealed class ApiContractTests
|
||||
vex.StatementCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PolicyInfoDto_OptionalFields_PreserveValues()
|
||||
{
|
||||
var policy = new PolicyInfoDto
|
||||
@@ -293,7 +309,8 @@ public sealed class ApiContractTests
|
||||
policy.Name.Should().Be("Organization Security Policy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SignerInfoDto_Certificates_CanBeNull()
|
||||
{
|
||||
var signers = new SignerInfoDto
|
||||
@@ -306,7 +323,8 @@ public sealed class ApiContractTests
|
||||
signers.Certificates.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SignerCertificateDto_AllFields_AreOptional()
|
||||
{
|
||||
var cert = new SignerCertificateDto
|
||||
@@ -321,7 +339,8 @@ public sealed class ApiContractTests
|
||||
json.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TimeWindowInfoDto_Bucket_IsRequired()
|
||||
{
|
||||
var timeWindow = new TimeWindowInfoDto
|
||||
@@ -338,7 +357,8 @@ public sealed class ApiContractTests
|
||||
|
||||
#region API Response Backwards Compatibility
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ProvcacheGetResponse_Status_ValidValues()
|
||||
{
|
||||
// Verify status field uses expected values
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -22,7 +23,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2024, 12, 24, 12, 0, 0, TimeSpan.Zero));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_SameInputs_ProducesSameDigest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +59,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest1.TrustScore.Should().Be(digest2.TrustScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DispositionsInDifferentOrder_ProducesSameVerdictHash()
|
||||
{
|
||||
// Arrange - Same dispositions, different insertion order
|
||||
@@ -83,7 +86,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest1.VerdictHash.Should().Be(digest2.VerdictHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentDispositions_ProducesDifferentVerdictHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -98,7 +102,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest1.VerdictHash.Should().NotBe(digest2.VerdictHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_SameEvidenceChunks_ProducesSameMerkleRoot()
|
||||
{
|
||||
// Arrange - valid SHA256 hex hashes (64 characters each)
|
||||
@@ -118,7 +123,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest1.ProofRoot.Should().Be(digest2.ProofRoot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentEvidenceChunkOrder_ProducesDifferentMerkleRoot()
|
||||
{
|
||||
// Arrange - Merkle tree is order-sensitive (valid SHA256 hex hashes)
|
||||
@@ -141,7 +147,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest1.ProofRoot.Should().NotBe(digest2.ProofRoot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WithTrustScore_ComponentWeights_ProducesConsistentScore()
|
||||
{
|
||||
// Arrange - Using weighted formula: 25% reach + 20% sbom + 20% vex + 15% policy + 20% signer
|
||||
@@ -161,7 +168,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest.TrustScore.Should().Be(100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WithTrustScore_MixedScores_CalculatesCorrectWeight()
|
||||
{
|
||||
// Arrange - 80 * 0.25 + 60 * 0.20 + 70 * 0.20 + 50 * 0.15 + 90 * 0.20
|
||||
@@ -181,7 +189,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest.TrustScore.Should().Be(72);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WithDefaultTimestamps_UsesFrozenTime()
|
||||
{
|
||||
// Arrange
|
||||
@@ -204,7 +213,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest.ExpiresAt.Should().Be(frozenTime.Add(_options.DefaultTtl));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_MultipleTimes_ReturnsConsistentDigest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -222,7 +232,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digests.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_EmptyDispositions_ProducesConsistentHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -238,7 +249,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest1.VerdictHash.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_EmptyEvidenceChunks_ProducesConsistentHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -254,7 +266,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest1.ProofRoot.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_ReplaySeedPreservedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -273,7 +286,8 @@ public class DecisionDigestBuilderDeterminismTests
|
||||
digest.ReplaySeed.FrozenEpoch.Should().Be(frozenEpoch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_MissingComponent_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -10,6 +10,7 @@ using Moq;
|
||||
using StellaOps.Provcache.Api;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -67,7 +68,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEvidenceChunks_ReturnsChunksWithPagination()
|
||||
{
|
||||
// Arrange
|
||||
@@ -106,7 +108,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
result.NextCursor.Should().Be("10");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEvidenceChunks_WithOffset_ReturnsPaginatedResults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -144,7 +147,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
result.HasMore.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEvidenceChunks_WithIncludeData_ReturnsBase64Blobs()
|
||||
{
|
||||
// Arrange
|
||||
@@ -178,7 +182,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
result!.Chunks[0].Data.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEvidenceChunks_NotFound_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
@@ -193,7 +198,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetProofManifest_ReturnsManifestWithChunkMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -227,7 +233,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
result.Chunks.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetProofManifest_NotFound_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
@@ -242,7 +249,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSingleChunk_ReturnsChunkWithData()
|
||||
{
|
||||
// Arrange
|
||||
@@ -264,7 +272,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
result.Data.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetSingleChunk_NotFound_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
@@ -279,7 +288,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyProof_ValidChunks_ReturnsIsValidTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -310,7 +320,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
result.ChunkResults.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyProof_MerkleRootMismatch_ReturnsIsValidFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -340,7 +351,8 @@ public sealed class EvidenceApiTests : IAsyncLifetime
|
||||
result.Error.Should().Contain("Merkle root mismatch");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyProof_NoChunks_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,6 +2,8 @@ using FluentAssertions;
|
||||
using StellaOps.Provcache;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,7 +20,8 @@ public sealed class EvidenceChunkerTests
|
||||
_chunker = new EvidenceChunker(_options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ChunkAsync_ShouldSplitEvidenceIntoMultipleChunks_WhenLargerThanChunkSize()
|
||||
{
|
||||
// Arrange
|
||||
@@ -44,7 +47,8 @@ public sealed class EvidenceChunkerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ChunkAsync_ShouldCreateSingleChunk_WhenSmallerThanChunkSize()
|
||||
{
|
||||
// Arrange
|
||||
@@ -62,7 +66,8 @@ public sealed class EvidenceChunkerTests
|
||||
result.Chunks[0].BlobSize.Should().Be(32);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ChunkAsync_ShouldHandleEmptyEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -78,7 +83,8 @@ public sealed class EvidenceChunkerTests
|
||||
result.TotalSize.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ChunkAsync_ShouldProduceUniqueHashForEachChunk()
|
||||
{
|
||||
// Arrange - create evidence with distinct bytes per chunk
|
||||
@@ -95,7 +101,8 @@ public sealed class EvidenceChunkerTests
|
||||
result.Chunks[0].ChunkHash.Should().NotBe(result.Chunks[1].ChunkHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReassembleAsync_ShouldRecoverOriginalEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -112,7 +119,8 @@ public sealed class EvidenceChunkerTests
|
||||
reassembled.Should().BeEquivalentTo(original);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReassembleAsync_ShouldThrow_WhenMerkleRootMismatch()
|
||||
{
|
||||
// Arrange
|
||||
@@ -128,7 +136,8 @@ public sealed class EvidenceChunkerTests
|
||||
.WithMessage("*Merkle root mismatch*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReassembleAsync_ShouldThrow_WhenChunkCorrupted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -151,7 +160,8 @@ public sealed class EvidenceChunkerTests
|
||||
.WithMessage("*verification failed*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyChunk_ShouldReturnTrue_WhenChunkValid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -175,7 +185,8 @@ public sealed class EvidenceChunkerTests
|
||||
_chunker.VerifyChunk(chunk).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyChunk_ShouldReturnFalse_WhenHashMismatch()
|
||||
{
|
||||
// Arrange
|
||||
@@ -195,7 +206,8 @@ public sealed class EvidenceChunkerTests
|
||||
_chunker.VerifyChunk(chunk).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeMerkleRoot_ShouldReturnSameResult_ForSameInput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -210,7 +222,8 @@ public sealed class EvidenceChunkerTests
|
||||
root1.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeMerkleRoot_ShouldHandleSingleHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -223,7 +236,8 @@ public sealed class EvidenceChunkerTests
|
||||
root.Should().Be("sha256:aabbccdd");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeMerkleRoot_ShouldHandleOddNumberOfHashes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -237,7 +251,8 @@ public sealed class EvidenceChunkerTests
|
||||
root.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ChunkStreamAsync_ShouldYieldChunksInOrder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -261,7 +276,8 @@ public sealed class EvidenceChunkerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Roundtrip_ShouldPreserveDataIntegrity()
|
||||
{
|
||||
// Arrange - use realistic chunk size
|
||||
|
||||
@@ -2,6 +2,7 @@ using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
public sealed class LazyFetchTests
|
||||
@@ -17,7 +18,8 @@ public sealed class LazyFetchTests
|
||||
NullLogger<LazyFetchOrchestrator>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FetchAndStoreAsync_WhenFetcherNotAvailable_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
@@ -34,7 +36,8 @@ public sealed class LazyFetchTests
|
||||
result.Errors.Should().Contain(e => e.Contains("not available"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FetchAndStoreAsync_WhenNoManifestFound_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +59,8 @@ public sealed class LazyFetchTests
|
||||
result.Errors.Should().Contain(e => e.Contains("No manifest found"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FetchAndStoreAsync_WhenAllChunksPresent_ReturnsSuccessWithZeroFetched()
|
||||
{
|
||||
// Arrange
|
||||
@@ -82,7 +86,8 @@ public sealed class LazyFetchTests
|
||||
result.BytesFetched.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FetchAndStoreAsync_FetchesMissingChunks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -124,7 +129,8 @@ public sealed class LazyFetchTests
|
||||
It.IsAny<CancellationToken>()), Times.AtLeastOnce);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FetchAndStoreAsync_WithVerification_RejectsCorruptedChunks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -166,7 +172,8 @@ public sealed class LazyFetchTests
|
||||
result.ChunksFetched.Should().Be(0); // Nothing stored
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FetchAndStoreAsync_WithFailOnVerificationError_AbortsOnCorruption()
|
||||
{
|
||||
// Arrange
|
||||
@@ -210,7 +217,8 @@ public sealed class LazyFetchTests
|
||||
result.ChunksFailedVerification.Should().BeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FetchAndStoreAsync_RespectsMaxChunksLimit()
|
||||
{
|
||||
// Arrange
|
||||
@@ -250,7 +258,8 @@ public sealed class LazyFetchTests
|
||||
result.ChunksFetched.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FileChunkFetcher_FetcherType_ReturnsFile()
|
||||
{
|
||||
// Arrange
|
||||
@@ -261,7 +270,8 @@ public sealed class LazyFetchTests
|
||||
fetcher.FetcherType.Should().Be("file");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FileChunkFetcher_IsAvailableAsync_ReturnsTrueWhenDirectoryExists()
|
||||
{
|
||||
// Arrange
|
||||
@@ -284,7 +294,8 @@ public sealed class LazyFetchTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FileChunkFetcher_IsAvailableAsync_ReturnsFalseWhenDirectoryMissing()
|
||||
{
|
||||
// Arrange
|
||||
@@ -298,7 +309,8 @@ public sealed class LazyFetchTests
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FileChunkFetcher_FetchChunkAsync_ReturnsNullWhenChunkNotFound()
|
||||
{
|
||||
// Arrange
|
||||
@@ -321,7 +333,8 @@ public sealed class LazyFetchTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HttpChunkFetcher_FetcherType_ReturnsHttp()
|
||||
{
|
||||
// Arrange
|
||||
@@ -332,7 +345,8 @@ public sealed class LazyFetchTests
|
||||
fetcher.FetcherType.Should().Be("http");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpChunkFetcher_IsAvailableAsync_ReturnsFalseWhenHostUnreachable()
|
||||
{
|
||||
// Arrange - use a non-routable IP to ensure connection failure
|
||||
|
||||
@@ -6,6 +6,8 @@ using StellaOps.Provenance.Attestation;
|
||||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -113,7 +115,8 @@ public sealed class MinimalProofExporterTests
|
||||
|
||||
#region Export Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_LiteDensity_ReturnsDigestAndManifestOnly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -132,7 +135,8 @@ public sealed class MinimalProofExporterTests
|
||||
bundle.Signature.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_StandardDensity_ReturnsFirstNChunks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -160,7 +164,8 @@ public sealed class MinimalProofExporterTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_StrictDensity_ReturnsAllChunks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -177,7 +182,8 @@ public sealed class MinimalProofExporterTests
|
||||
bundle.Chunks.Select(c => c.Index).Should().BeEquivalentTo([0, 1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_NotFound_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -190,7 +196,8 @@ public sealed class MinimalProofExporterTests
|
||||
_exporter.ExportAsync("sha256:notfound", options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsJsonAsync_ReturnsValidJson()
|
||||
{
|
||||
// Arrange
|
||||
@@ -207,7 +214,8 @@ public sealed class MinimalProofExporterTests
|
||||
bundle!.BundleVersion.Should().Be("v1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportToStreamAsync_WritesToStream()
|
||||
{
|
||||
// Arrange
|
||||
@@ -229,7 +237,8 @@ public sealed class MinimalProofExporterTests
|
||||
|
||||
#region Import Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ImportAsync_ValidBundle_StoresChunks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -255,7 +264,8 @@ public sealed class MinimalProofExporterTests
|
||||
result.Verification.ChunksValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ImportFromJsonAsync_ValidJson_ImportsSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
@@ -275,7 +285,8 @@ public sealed class MinimalProofExporterTests
|
||||
|
||||
#region Verify Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ValidBundle_ReturnsValid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -294,7 +305,8 @@ public sealed class MinimalProofExporterTests
|
||||
verification.FailedChunkIndices.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_CorruptedChunk_ReportsFailure()
|
||||
{
|
||||
// Arrange
|
||||
@@ -315,7 +327,8 @@ public sealed class MinimalProofExporterTests
|
||||
verification.FailedChunkIndices.Should().Contain(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_InvalidDigest_ReportsFailure()
|
||||
{
|
||||
// Arrange
|
||||
@@ -338,7 +351,8 @@ public sealed class MinimalProofExporterTests
|
||||
|
||||
#region EstimateSize Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EstimateExportSizeAsync_LiteDensity_ReturnsBaseSize()
|
||||
{
|
||||
// Arrange
|
||||
@@ -351,7 +365,8 @@ public sealed class MinimalProofExporterTests
|
||||
size.Should().Be(2048); // Base size
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EstimateExportSizeAsync_StrictDensity_ReturnsLargerSize()
|
||||
{
|
||||
// Arrange
|
||||
@@ -364,7 +379,8 @@ public sealed class MinimalProofExporterTests
|
||||
size.Should().BeGreaterThan(2048); // Base + all chunk data
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EstimateExportSizeAsync_NotFound_ReturnsZero()
|
||||
{
|
||||
// Arrange
|
||||
@@ -382,7 +398,8 @@ public sealed class MinimalProofExporterTests
|
||||
|
||||
#region Signing Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_SigningWithoutSigner_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -398,7 +415,8 @@ public sealed class MinimalProofExporterTests
|
||||
_exporter.ExportAsync(_testEntry.VeriKey, options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_WithSigner_SignsBundle()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -65,7 +66,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
|
||||
#region GET /v1/provcache/{veriKey}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByVeriKey_CacheHit_Returns200WithEntry()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +90,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
content.Source.Should().Be("valkey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByVeriKey_CacheMiss_Returns204()
|
||||
{
|
||||
// Arrange
|
||||
@@ -105,7 +108,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByVeriKey_Expired_Returns410Gone()
|
||||
{
|
||||
// Arrange
|
||||
@@ -123,7 +127,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Gone);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByVeriKey_WithBypassCache_PassesFlagToService()
|
||||
{
|
||||
// Arrange
|
||||
@@ -144,7 +149,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
|
||||
#region POST /v1/provcache
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateOrUpdate_ValidRequest_Returns201Created()
|
||||
{
|
||||
// Arrange
|
||||
@@ -170,7 +176,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
content.Success.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateOrUpdate_NullEntry_Returns400BadRequest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -187,7 +194,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
|
||||
#region POST /v1/provcache/invalidate
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Invalidate_SingleVeriKey_Returns200WithAffectedCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -214,7 +222,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
content.Type.Should().Be("verikey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Invalidate_ByPolicyHash_Returns200WithBulkResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -249,7 +258,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
content!.EntriesAffected.Should().Be(5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Invalidate_ByPattern_Returns200WithPatternResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -288,7 +298,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
|
||||
#region GET /v1/provcache/metrics
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetMetrics_Returns200WithMetrics()
|
||||
{
|
||||
// Arrange
|
||||
@@ -325,7 +336,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
|
||||
#region Contract Verification Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByVeriKey_ResponseContract_HasRequiredFields()
|
||||
{
|
||||
// Arrange
|
||||
@@ -350,7 +362,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
root.TryGetProperty("entry", out _).Should().BeTrue("Response must have 'entry' field");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateOrUpdate_ResponseContract_HasRequiredFields()
|
||||
{
|
||||
// Arrange
|
||||
@@ -374,7 +387,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
root.TryGetProperty("expiresAt", out _).Should().BeTrue("Response must have 'expiresAt' field");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InvalidateResponse_Contract_HasRequiredFields()
|
||||
{
|
||||
// Arrange
|
||||
@@ -401,7 +415,8 @@ public sealed class ProvcacheApiTests : IAsyncDisposable
|
||||
root.TryGetProperty("value", out _).Should().BeTrue("Response must have 'value' field");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MetricsResponse_Contract_HasRequiredFields()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.Provcache.Entities;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
public sealed class RevocationLedgerTests
|
||||
@@ -14,7 +15,8 @@ public sealed class RevocationLedgerTests
|
||||
_ledger = new InMemoryRevocationLedger(NullLogger<InMemoryRevocationLedger>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RecordAsync_AssignsSeqNo()
|
||||
{
|
||||
// Arrange
|
||||
@@ -29,7 +31,8 @@ public sealed class RevocationLedgerTests
|
||||
recorded.RevokedKey.Should().Be("signer-hash-1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RecordAsync_AssignsIncrementingSeqNos()
|
||||
{
|
||||
// Arrange
|
||||
@@ -48,7 +51,8 @@ public sealed class RevocationLedgerTests
|
||||
recorded3.SeqNo.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEntriesSinceAsync_ReturnsEntriesAfterSeqNo()
|
||||
{
|
||||
// Arrange
|
||||
@@ -66,7 +70,8 @@ public sealed class RevocationLedgerTests
|
||||
entries[1].SeqNo.Should().Be(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEntriesSinceAsync_RespectsLimit()
|
||||
{
|
||||
// Arrange
|
||||
@@ -82,7 +87,8 @@ public sealed class RevocationLedgerTests
|
||||
entries.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEntriesByTypeAsync_FiltersCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -99,7 +105,8 @@ public sealed class RevocationLedgerTests
|
||||
signerEntries.Should().OnlyContain(e => e.RevocationType == RevocationTypes.Signer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEntriesByTypeAsync_FiltersBySinceTime()
|
||||
{
|
||||
// Arrange
|
||||
@@ -125,7 +132,8 @@ public sealed class RevocationLedgerTests
|
||||
entries[0].RevokedKey.Should().Be("s2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetLatestSeqNoAsync_ReturnsZeroWhenEmpty()
|
||||
{
|
||||
// Act
|
||||
@@ -135,7 +143,8 @@ public sealed class RevocationLedgerTests
|
||||
seqNo.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetLatestSeqNoAsync_ReturnsLatest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -150,7 +159,8 @@ public sealed class RevocationLedgerTests
|
||||
seqNo.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetRevocationsForKeyAsync_ReturnsMatchingEntries()
|
||||
{
|
||||
// Arrange
|
||||
@@ -166,7 +176,8 @@ public sealed class RevocationLedgerTests
|
||||
entries.Should().OnlyContain(e => e.RevokedKey == "s1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetStatsAsync_ReturnsCorrectStats()
|
||||
{
|
||||
// Arrange
|
||||
@@ -188,7 +199,8 @@ public sealed class RevocationLedgerTests
|
||||
stats.EntriesByType[RevocationTypes.Policy].Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Clear_RemovesAllEntries()
|
||||
{
|
||||
// Arrange
|
||||
@@ -237,7 +249,8 @@ public sealed class RevocationReplayServiceTests
|
||||
NullLogger<RevocationReplayService>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReplayFromAsync_ReplaysAllEntries()
|
||||
{
|
||||
// Arrange
|
||||
@@ -262,7 +275,8 @@ public sealed class RevocationReplayServiceTests
|
||||
result.EntriesByType.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReplayFromAsync_StartsFromCheckpoint()
|
||||
{
|
||||
// Arrange
|
||||
@@ -282,7 +296,8 @@ public sealed class RevocationReplayServiceTests
|
||||
result.EndSeqNo.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReplayFromAsync_RespectsMaxEntries()
|
||||
{
|
||||
// Arrange
|
||||
@@ -303,7 +318,8 @@ public sealed class RevocationReplayServiceTests
|
||||
result.EntriesReplayed.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReplayFromAsync_ReturnsEmptyWhenNoEntries()
|
||||
{
|
||||
// Act
|
||||
@@ -314,7 +330,8 @@ public sealed class RevocationReplayServiceTests
|
||||
result.EntriesReplayed.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetCheckpointAsync_ReturnsZeroInitially()
|
||||
{
|
||||
// Act
|
||||
@@ -324,7 +341,8 @@ public sealed class RevocationReplayServiceTests
|
||||
checkpoint.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveCheckpointAsync_PersistsCheckpoint()
|
||||
{
|
||||
// Act
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Provcache/StellaOps.Provcache.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.Provcache.Api/StellaOps.Provcache.Api.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -4,6 +4,8 @@ using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -47,7 +49,8 @@ public class WriteBehindQueueTests
|
||||
HitCount = 0
|
||||
};
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnqueueAsync_SingleEntry_UpdatesMetrics()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +71,8 @@ public class WriteBehindQueueTests
|
||||
metrics.CurrentQueueDepth.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnqueueAsync_MultipleEntries_TracksQueueDepth()
|
||||
{
|
||||
// Arrange
|
||||
@@ -90,7 +94,8 @@ public class WriteBehindQueueTests
|
||||
metrics.CurrentQueueDepth.Should().Be(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetMetrics_InitialState_AllZeros()
|
||||
{
|
||||
// Arrange
|
||||
@@ -112,7 +117,8 @@ public class WriteBehindQueueTests
|
||||
metrics.CurrentQueueDepth.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ProcessBatch_SuccessfulPersist_UpdatesPersistMetrics()
|
||||
{
|
||||
// Arrange
|
||||
@@ -147,7 +153,8 @@ public class WriteBehindQueueTests
|
||||
metrics.TotalBatches.Should().BeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WriteBehindMetrics_Timestamp_IsRecent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -205,7 +212,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
HitCount = 0
|
||||
};
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SetAsync_ThenGetAsync_ReturnsEntry()
|
||||
{
|
||||
// Arrange
|
||||
@@ -239,7 +247,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
result.Source.Should().Be("valkey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetAsync_CacheMissWithDbHit_BackfillsCache()
|
||||
{
|
||||
// Arrange
|
||||
@@ -274,7 +283,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
store.Verify(s => s.SetAsync(It.Is<ProvcacheEntry>(e => e.VeriKey == veriKey), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetAsync_FullMiss_ReturnsMissResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -303,7 +313,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
result.Entry.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetOrComputeAsync_CacheHit_DoesNotCallFactory()
|
||||
{
|
||||
// Arrange
|
||||
@@ -335,7 +346,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
result.VeriKey.Should().Be(veriKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetOrComputeAsync_CacheMiss_CallsFactoryAndStores()
|
||||
{
|
||||
// Arrange
|
||||
@@ -374,7 +386,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
store.Verify(s => s.SetAsync(It.Is<ProvcacheEntry>(e => e.VeriKey == veriKey), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InvalidateAsync_RemovesFromBothStoreLayers()
|
||||
{
|
||||
// Arrange
|
||||
@@ -403,7 +416,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
repository.Verify(r => r.DeleteAsync(veriKey, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetAsync_BypassCache_ReturnsbypassedResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -426,7 +440,8 @@ public class ProvcacheServiceStorageIntegrationTests
|
||||
store.Verify(s => s.GetAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetMetricsAsync_ReturnsCurrentMetrics()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Provcache.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -14,7 +15,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
TimeWindowBucket = TimeSpan.FromHours(1)
|
||||
};
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_SameInputs_ProducesSameVeriKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -49,7 +51,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentInputOrder_VexHashes_ProducesSameVeriKey()
|
||||
{
|
||||
// Arrange - VEX hashes in different orders
|
||||
@@ -64,7 +67,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().Be(veriKey2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentInputOrder_CertificateHashes_ProducesSameVeriKey()
|
||||
{
|
||||
// Arrange - Certificate hashes in different orders
|
||||
@@ -79,7 +83,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().Be(veriKey2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentSourceHash_ProducesDifferentVeriKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -90,7 +95,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().NotBe(veriKey2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentSbomHash_ProducesDifferentVeriKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -101,7 +107,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().NotBe(veriKey2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentTimeWindow_ProducesDifferentVeriKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -112,7 +119,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().NotBe(veriKey2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_MultipleTimes_ReturnsConsistentResult()
|
||||
{
|
||||
// Arrange & Act - Create multiple builder instances with same inputs
|
||||
@@ -125,7 +133,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
results.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_AcrossMultipleBuilders_ProducesSameResult()
|
||||
{
|
||||
// Act - Create 10 different builder instances
|
||||
@@ -138,7 +147,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
results.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithHashPrefixNormalization_ProducesSameVeriKey()
|
||||
{
|
||||
// Arrange - Same hash with different case prefixes
|
||||
@@ -164,7 +174,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().Be(veriKey2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WithTimeWindow_Timestamp_BucketsDeterministically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -182,7 +193,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
builder1.Build().Should().NotBe(builder3.Build());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildWithComponents_ReturnsSameVeriKeyAsIndividualComponents()
|
||||
{
|
||||
// Arrange & Act - Create two identical builders
|
||||
@@ -195,7 +207,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
components.SbomHash.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_EmptyVexSet_ProducesConsistentHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -215,7 +228,8 @@ public class VeriKeyBuilderDeterminismTests
|
||||
veriKey1.Should().Be(veriKey2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_MissingComponent_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -4,11 +4,13 @@ using StellaOps.Replay.Core;
|
||||
using StellaOps.Cryptography.Bcl;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Replay.Core.Tests;
|
||||
|
||||
public class ReachabilityReplayWriterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildManifestV2_SortsGraphsAndTraces_Deterministically()
|
||||
{
|
||||
var scan = new ReplayScanMetadata
|
||||
|
||||
@@ -22,5 +22,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Replay.Core/StellaOps.Replay.Core.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -7,11 +7,13 @@ using StellaOps.Replay.Models;
|
||||
using StellaOps.Testing.Manifests.Models;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Replay.Tests;
|
||||
|
||||
public class ReplayEngineTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Replay_SameManifest_ProducesIdenticalVerdict()
|
||||
{
|
||||
var manifest = CreateManifest();
|
||||
@@ -23,7 +25,8 @@ public class ReplayEngineTests
|
||||
result1.VerdictDigest.Should().Be(result2.VerdictDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Replay_DifferentManifest_ProducesDifferentVerdict()
|
||||
{
|
||||
var manifest1 = CreateManifest();
|
||||
@@ -39,7 +42,8 @@ public class ReplayEngineTests
|
||||
result1.VerdictDigest.Should().NotBe(result2.VerdictDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CheckDeterminism_IdenticalResults_ReturnsTrue()
|
||||
{
|
||||
var engine = CreateEngine();
|
||||
@@ -51,7 +55,8 @@ public class ReplayEngineTests
|
||||
check.IsDeterministic.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CheckDeterminism_DifferentResults_ReturnsDifferences()
|
||||
{
|
||||
var engine = CreateEngine();
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
<ProjectReference Include="..\..\StellaOps.Replay\StellaOps.Replay.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Testing.Manifests\StellaOps.Testing.Manifests.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Evidence\StellaOps.Evidence.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,7 @@ using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Common.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +13,8 @@ public sealed class FrameConverterTests
|
||||
{
|
||||
#region ToFrame (RequestFrame) Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToFrame_RequestFrame_ReturnsFrameWithRequestType()
|
||||
{
|
||||
// Arrange
|
||||
@@ -25,7 +27,8 @@ public sealed class FrameConverterTests
|
||||
frame.Type.Should().Be(FrameType.Request);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToFrame_RequestFrame_SetsCorrelationIdFromRequest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -38,7 +41,8 @@ public sealed class FrameConverterTests
|
||||
frame.CorrelationId.Should().Be("test-correlation-123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToFrame_RequestFrame_UsesRequestIdWhenCorrelationIdIsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +61,8 @@ public sealed class FrameConverterTests
|
||||
frame.CorrelationId.Should().Be("request-id-456");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToFrame_RequestFrame_SerializesPayload()
|
||||
{
|
||||
// Arrange
|
||||
@@ -74,7 +79,8 @@ public sealed class FrameConverterTests
|
||||
|
||||
#region ToRequestFrame Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_ValidRequestFrame_ReturnsRequestFrame()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +94,8 @@ public sealed class FrameConverterTests
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_WrongFrameType_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -106,7 +113,8 @@ public sealed class FrameConverterTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_InvalidJson_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -124,7 +132,8 @@ public sealed class FrameConverterTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_RoundTrip_PreservesRequestId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -138,7 +147,8 @@ public sealed class FrameConverterTests
|
||||
result!.RequestId.Should().Be("unique-request-id");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_RoundTrip_PreservesMethod()
|
||||
{
|
||||
// Arrange
|
||||
@@ -152,7 +162,8 @@ public sealed class FrameConverterTests
|
||||
result!.Method.Should().Be("DELETE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_RoundTrip_PreservesPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -166,7 +177,8 @@ public sealed class FrameConverterTests
|
||||
result!.Path.Should().Be("/api/users/123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_RoundTrip_PreservesHeaders()
|
||||
{
|
||||
// Arrange
|
||||
@@ -193,7 +205,8 @@ public sealed class FrameConverterTests
|
||||
result.Headers["X-Custom-Header"].Should().Be("custom-value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_RoundTrip_PreservesPayload()
|
||||
{
|
||||
// Arrange
|
||||
@@ -214,7 +227,8 @@ public sealed class FrameConverterTests
|
||||
result!.Payload.ToArray().Should().BeEquivalentTo(payloadBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_RoundTrip_PreservesTimeoutSeconds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -234,7 +248,8 @@ public sealed class FrameConverterTests
|
||||
result!.TimeoutSeconds.Should().Be(60);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_RoundTrip_PreservesSupportsStreaming()
|
||||
{
|
||||
// Arrange
|
||||
@@ -258,7 +273,8 @@ public sealed class FrameConverterTests
|
||||
|
||||
#region ToFrame (ResponseFrame) Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToFrame_ResponseFrame_ReturnsFrameWithResponseType()
|
||||
{
|
||||
// Arrange
|
||||
@@ -271,7 +287,8 @@ public sealed class FrameConverterTests
|
||||
frame.Type.Should().Be(FrameType.Response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToFrame_ResponseFrame_SetsCorrelationIdToRequestId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -288,7 +305,8 @@ public sealed class FrameConverterTests
|
||||
|
||||
#region ToResponseFrame Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_ValidResponseFrame_ReturnsResponseFrame()
|
||||
{
|
||||
// Arrange
|
||||
@@ -302,7 +320,8 @@ public sealed class FrameConverterTests
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_WrongFrameType_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -320,7 +339,8 @@ public sealed class FrameConverterTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_InvalidJson_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -338,7 +358,8 @@ public sealed class FrameConverterTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_RoundTrip_PreservesRequestId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -352,7 +373,8 @@ public sealed class FrameConverterTests
|
||||
result!.RequestId.Should().Be("original-req-id");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_RoundTrip_PreservesStatusCode()
|
||||
{
|
||||
// Arrange
|
||||
@@ -366,7 +388,8 @@ public sealed class FrameConverterTests
|
||||
result!.StatusCode.Should().Be(404);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_RoundTrip_PreservesHeaders()
|
||||
{
|
||||
// Arrange
|
||||
@@ -391,7 +414,8 @@ public sealed class FrameConverterTests
|
||||
result.Headers["Cache-Control"].Should().Be("no-cache");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_RoundTrip_PreservesPayload()
|
||||
{
|
||||
// Arrange
|
||||
@@ -411,7 +435,8 @@ public sealed class FrameConverterTests
|
||||
result!.Payload.ToArray().Should().BeEquivalentTo(payloadBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_RoundTrip_PreservesHasMoreChunks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -434,7 +459,8 @@ public sealed class FrameConverterTests
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_EmptyPayload_ReturnsEmptyPayload()
|
||||
{
|
||||
// Arrange
|
||||
@@ -454,7 +480,8 @@ public sealed class FrameConverterTests
|
||||
result!.Payload.IsEmpty.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_NullHeaders_ReturnsEmptyHeaders()
|
||||
{
|
||||
// Arrange
|
||||
@@ -474,7 +501,8 @@ public sealed class FrameConverterTests
|
||||
result.Headers.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_EmptyPayload_ReturnsEmptyPayload()
|
||||
{
|
||||
// Arrange
|
||||
@@ -493,7 +521,8 @@ public sealed class FrameConverterTests
|
||||
result!.Payload.IsEmpty.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToFrame_LargePayload_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -3,6 +3,7 @@ using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Common.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -13,7 +14,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
{
|
||||
#region Request Frame Complete Round-Trip Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_CompleteRoundTrip_AllFieldsPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -51,7 +53,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored.Payload.ToArray().Should().BeEquivalentTo(original.Payload);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
@@ -72,7 +75,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Method.Should().Be(method);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("/")]
|
||||
[InlineData("/api")]
|
||||
[InlineData("/api/users")]
|
||||
@@ -94,7 +98,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Path.Should().Be(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_EmptyPayload_RoundTripsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -114,7 +119,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Payload.Length.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_LargePayload_RoundTripsCorrectly()
|
||||
{
|
||||
// Arrange - 1MB payload
|
||||
@@ -137,7 +143,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Payload.ToArray().Should().BeEquivalentTo(largePayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_BinaryPayload_RoundTripsCorrectly()
|
||||
{
|
||||
// Arrange - Binary data with all byte values 0-255
|
||||
@@ -159,7 +166,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Payload.ToArray().Should().BeEquivalentTo(binaryPayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_NoHeaders_RoundTripsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -179,7 +187,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Headers.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_ManyHeaders_RoundTripsCorrectly()
|
||||
{
|
||||
// Arrange - 100 headers
|
||||
@@ -202,7 +211,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Headers.Should().BeEquivalentTo(headers);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(30)]
|
||||
[InlineData(60)]
|
||||
@@ -231,7 +241,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
|
||||
#region Response Frame Complete Round-Trip Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResponseFrame_CompleteRoundTrip_AllFieldsPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -262,7 +273,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored.Payload.ToArray().Should().BeEquivalentTo(original.Payload);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(200)]
|
||||
[InlineData(201)]
|
||||
[InlineData(204)]
|
||||
@@ -288,7 +300,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.StatusCode.Should().Be(statusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void ResponseFrame_StreamingFlag_RoundTripsCorrectly(bool hasMoreChunks)
|
||||
@@ -313,7 +326,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
|
||||
#region Frame Type Discrimination Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_HasCorrectFrameType()
|
||||
{
|
||||
// Arrange
|
||||
@@ -326,7 +340,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
frame.Type.Should().Be(FrameType.Request);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResponseFrame_HasCorrectFrameType()
|
||||
{
|
||||
// Arrange
|
||||
@@ -339,7 +354,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
frame.Type.Should().Be(FrameType.Response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToRequestFrame_ReturnsNull_ForResponseFrame()
|
||||
{
|
||||
// Arrange
|
||||
@@ -353,7 +369,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToResponseFrame_ReturnsNull_ForRequestFrame()
|
||||
{
|
||||
// Arrange
|
||||
@@ -371,7 +388,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
|
||||
#region Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_MultipleRoundTrips_ProduceIdenticalResults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -413,7 +431,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResponseFrame_MultipleRoundTrips_ProduceIdenticalResults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -453,7 +472,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
|
||||
#region Correlation ID Handling Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_CorrelationIdNull_UsesRequestIdInFrame()
|
||||
{
|
||||
// Arrange
|
||||
@@ -472,7 +492,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
frame.CorrelationId.Should().Be("req-123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_CorrelationIdSet_UsesCorrelationIdInFrame()
|
||||
{
|
||||
// Arrange
|
||||
@@ -491,7 +512,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
frame.CorrelationId.Should().Be("corr-456");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResponseFrame_UsesRequestIdAsCorrelationId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -512,7 +534,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
|
||||
#region Edge Case Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_SpecialCharactersInHeaders_RoundTripCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -538,7 +561,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.Headers.Should().BeEquivalentTo(original.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_UnicodePayload_RoundTripsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -560,7 +584,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restoredPayload.Should().Be(unicodeJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RequestFrame_EmptyRequestId_RoundTripsCorrectly()
|
||||
{
|
||||
// Note: Empty RequestId is technically invalid but should still round-trip
|
||||
@@ -579,7 +604,8 @@ public sealed class MessageFramingRoundTripTests
|
||||
restored!.RequestId.Should().Be("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResponseFrame_ZeroStatusCode_RoundTripsCorrectly()
|
||||
{
|
||||
// Note: Zero status code is technically invalid but should still round-trip
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Common.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,7 +8,8 @@ public sealed class PathMatcherTests
|
||||
{
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_SetsTemplate()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -17,7 +19,8 @@ public sealed class PathMatcherTests
|
||||
matcher.Template.Should().Be("/api/users/{id}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_DefaultsCaseInsensitive()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -27,7 +30,8 @@ public sealed class PathMatcherTests
|
||||
matcher.IsMatch("/api/users").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_CaseSensitive_DoesNotMatchDifferentCase()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -42,7 +46,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region IsMatch Tests - Exact Paths
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_ExactPath_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -52,7 +57,8 @@ public sealed class PathMatcherTests
|
||||
matcher.IsMatch("/api/health").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_ExactPath_TrailingSlash_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -62,7 +68,8 @@ public sealed class PathMatcherTests
|
||||
matcher.IsMatch("/api/health/").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_ExactPath_NoLeadingSlash_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -72,7 +79,8 @@ public sealed class PathMatcherTests
|
||||
matcher.IsMatch("api/health").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_DifferentPath_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -82,7 +90,8 @@ public sealed class PathMatcherTests
|
||||
matcher.IsMatch("/api/status").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_PartialPath_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -92,7 +101,8 @@ public sealed class PathMatcherTests
|
||||
matcher.IsMatch("/api/users").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_LongerPath_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -106,7 +116,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region IsMatch Tests - Case Sensitivity
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_CaseInsensitive_MatchesMixedCase()
|
||||
{
|
||||
// Arrange
|
||||
@@ -118,7 +129,8 @@ public sealed class PathMatcherTests
|
||||
matcher.IsMatch("/aPi/uSeRs").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_CaseSensitive_OnlyMatchesExactCase()
|
||||
{
|
||||
// Arrange
|
||||
@@ -134,7 +146,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region TryMatch Tests - Single Parameter
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_SingleParameter_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -147,7 +160,8 @@ public sealed class PathMatcherTests
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_SingleParameter_ExtractsParameter()
|
||||
{
|
||||
// Arrange
|
||||
@@ -161,7 +175,8 @@ public sealed class PathMatcherTests
|
||||
parameters["id"].Should().Be("123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_SingleParameter_ExtractsGuidParameter()
|
||||
{
|
||||
// Arrange
|
||||
@@ -175,7 +190,8 @@ public sealed class PathMatcherTests
|
||||
parameters["userId"].Should().Be(guid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_SingleParameter_ExtractsStringParameter()
|
||||
{
|
||||
// Arrange
|
||||
@@ -192,7 +208,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region TryMatch Tests - Multiple Parameters
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_MultipleParameters_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -205,7 +222,8 @@ public sealed class PathMatcherTests
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_MultipleParameters_ExtractsAllParameters()
|
||||
{
|
||||
// Arrange
|
||||
@@ -221,7 +239,8 @@ public sealed class PathMatcherTests
|
||||
parameters["postId"].Should().Be("post-2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ThreeParameters_ExtractsAllParameters()
|
||||
{
|
||||
// Arrange
|
||||
@@ -241,7 +260,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region TryMatch Tests - Non-Matching
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_NonMatchingPath_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -255,7 +275,8 @@ public sealed class PathMatcherTests
|
||||
parameters.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_MissingParameter_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -268,7 +289,8 @@ public sealed class PathMatcherTests
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ExtraSegment_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -285,7 +307,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region TryMatch Tests - Path Normalization
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_TrailingSlash_Matches()
|
||||
{
|
||||
// Arrange
|
||||
@@ -299,7 +322,8 @@ public sealed class PathMatcherTests
|
||||
parameters["id"].Should().Be("123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_NoLeadingSlash_Matches()
|
||||
{
|
||||
// Arrange
|
||||
@@ -317,7 +341,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region TryMatch Tests - Parameter Type Constraints
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ParameterWithTypeConstraint_ExtractsParameterName()
|
||||
{
|
||||
// Arrange
|
||||
@@ -332,7 +357,8 @@ public sealed class PathMatcherTests
|
||||
parameters["id"].Should().Be("123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ParameterWithGuidConstraint_ExtractsParameterName()
|
||||
{
|
||||
// Arrange
|
||||
@@ -350,7 +376,8 @@ public sealed class PathMatcherTests
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_RootPath_Matches()
|
||||
{
|
||||
// Arrange
|
||||
@@ -364,7 +391,8 @@ public sealed class PathMatcherTests
|
||||
parameters.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_SingleSegmentWithParameter_Matches()
|
||||
{
|
||||
// Arrange
|
||||
@@ -378,7 +406,8 @@ public sealed class PathMatcherTests
|
||||
parameters["id"].Should().Be("test-value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_EmptyPath_HandlesGracefully()
|
||||
{
|
||||
// Arrange
|
||||
@@ -391,7 +420,8 @@ public sealed class PathMatcherTests
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ParameterWithHyphen_Extracts()
|
||||
{
|
||||
// Arrange
|
||||
@@ -405,7 +435,8 @@ public sealed class PathMatcherTests
|
||||
parameters["user-id"].Should().Be("123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ParameterWithUnderscore_Extracts()
|
||||
{
|
||||
// Arrange
|
||||
@@ -418,7 +449,8 @@ public sealed class PathMatcherTests
|
||||
parameters.Should().ContainKey("user_id");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_SpecialCharactersInPath_Matches()
|
||||
{
|
||||
// Arrange
|
||||
@@ -431,7 +463,8 @@ public sealed class PathMatcherTests
|
||||
parameters["query"].Should().Be("hello-world_test.123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsMatch_ComplexRealWorldPath_Matches()
|
||||
{
|
||||
// Arrange
|
||||
@@ -444,7 +477,8 @@ public sealed class PathMatcherTests
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryMatch_ComplexRealWorldPath_ExtractsAllParameters()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,6 +2,7 @@ using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Common.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -11,7 +12,8 @@ public sealed class RoutingDeterminismTests
|
||||
{
|
||||
#region Core Determinism Property Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SameContextAndConnections_AlwaysSelectsSameRoute()
|
||||
{
|
||||
// Arrange
|
||||
@@ -32,7 +34,8 @@ public sealed class RoutingDeterminismTests
|
||||
results.Should().AllBeEquivalentTo(results[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DifferentConnectionOrder_ProducesSameResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +60,8 @@ public sealed class RoutingDeterminismTests
|
||||
result1.ConnectionId.Should().Be(result2.ConnectionId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SamePathAndMethod_WithSameHeaders_ProducesSameRouteKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -97,7 +101,8 @@ public sealed class RoutingDeterminismTests
|
||||
|
||||
#region Region Affinity Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SameRegion_AlwaysPreferredWhenAvailable()
|
||||
{
|
||||
// Arrange
|
||||
@@ -117,7 +122,8 @@ public sealed class RoutingDeterminismTests
|
||||
results.Should().AllSatisfy(r => r.Instance.Region.Should().Be("us-east"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NoLocalRegion_FallbackIsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -142,7 +148,8 @@ public sealed class RoutingDeterminismTests
|
||||
|
||||
#region Version Selection Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SameRequestedVersion_AlwaysSelectsMatchingConnection()
|
||||
{
|
||||
// Arrange
|
||||
@@ -163,7 +170,8 @@ public sealed class RoutingDeterminismTests
|
||||
results.Should().AllSatisfy(r => r.Instance.Version.Should().Be("2.0.0"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NoVersionRequested_LatestStableIsSelectedDeterministically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -188,7 +196,8 @@ public sealed class RoutingDeterminismTests
|
||||
|
||||
#region Health Status Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HealthyConnectionsPreferred_Deterministically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -209,7 +218,8 @@ public sealed class RoutingDeterminismTests
|
||||
results.Should().AllSatisfy(r => r.ConnectionId.Should().Be("conn-healthy"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DegradedConnectionSelected_WhenNoHealthyAvailable()
|
||||
{
|
||||
// Arrange
|
||||
@@ -231,7 +241,8 @@ public sealed class RoutingDeterminismTests
|
||||
results[0].Status.Should().Be(InstanceHealthStatus.Degraded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DrainingConnectionsExcluded()
|
||||
{
|
||||
// Arrange
|
||||
@@ -253,7 +264,8 @@ public sealed class RoutingDeterminismTests
|
||||
|
||||
#region Multi-Criteria Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegionThenVersionThenHealth_OrderingIsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -288,7 +300,8 @@ public sealed class RoutingDeterminismTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TieBreaker_UsesConnectionIdForConsistency()
|
||||
{
|
||||
// Arrange - Two identical connections except ID
|
||||
@@ -312,7 +325,8 @@ public sealed class RoutingDeterminismTests
|
||||
|
||||
#region Endpoint Matching Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathParameterMatching_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -335,7 +349,8 @@ public sealed class RoutingDeterminismTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MultipleEndpoints_SamePath_SelectsFirstMatchDeterministically()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Common.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -11,7 +12,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
{
|
||||
#region Path Template Matching Rules
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathMatcher_ExactPath_MatchesOnly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -25,7 +27,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
matcher.IsMatch("/api").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathMatcher_SingleParameter_CapturesValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -40,7 +43,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
parameters["id"].Should().Be("12345");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathMatcher_MultipleParameters_CapturesAllValues()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +61,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
parameters["memberId"].Should().Be("member-3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathMatcher_SegmentMismatch_DoesNotMatch()
|
||||
{
|
||||
// Arrange
|
||||
@@ -69,7 +74,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
matcher.IsMatch("/api/users/123").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("/api/users/123", true)]
|
||||
[InlineData("/api/users/abc-def-ghi", true)]
|
||||
[InlineData("/api/users/user@example.com", false)] // Contains @ which may be problematic
|
||||
@@ -91,7 +97,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
|
||||
#region Endpoint Selection Rules
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointSelection_MatchesByMethodAndPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -110,7 +117,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
selector.FindEndpoint("PUT", "/api/users").Should().BeNull(); // No PUT endpoint
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointSelection_MoreSpecificPathWins()
|
||||
{
|
||||
// Arrange - Specific path should win over parameterized path
|
||||
@@ -129,7 +137,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
idEndpoint!.ServiceName.Should().Be("user-service");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointSelection_DifferentMethodsSamePath_SelectsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -150,7 +159,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
|
||||
#region Version Matching Rules
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VersionMatching_ExactMatch_Required()
|
||||
{
|
||||
// Arrange
|
||||
@@ -169,7 +179,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result[0].Instance.Version.Should().Be("2.0.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VersionMatching_NoVersionRequested_AllVersionsEligible()
|
||||
{
|
||||
// Arrange
|
||||
@@ -186,7 +197,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VersionMatching_NoMatchingVersion_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
@@ -207,7 +219,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
|
||||
#region Health Status Rules
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HealthFilter_OnlyHealthy_WhenAvailable()
|
||||
{
|
||||
// Arrange
|
||||
@@ -226,7 +239,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result[0].ConnectionId.Should().Be("conn-healthy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HealthFilter_DegradedFallback_WhenNoHealthy()
|
||||
{
|
||||
// Arrange
|
||||
@@ -245,7 +259,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result.All(c => c.Status == InstanceHealthStatus.Degraded).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HealthFilter_NoDegradedAllowed_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
@@ -262,7 +277,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HealthFilter_DrainingAlwaysExcluded()
|
||||
{
|
||||
// Arrange
|
||||
@@ -284,7 +300,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
|
||||
#region Region Affinity Rules
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegionFilter_LocalRegionFirst()
|
||||
{
|
||||
// Arrange
|
||||
@@ -303,7 +320,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result.Connections[0].Instance.Region.Should().Be("eu-west");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegionFilter_NeighborTierSecond()
|
||||
{
|
||||
// Arrange
|
||||
@@ -322,7 +340,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result.Connections[0].Instance.Region.Should().Be("eu-central");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegionFilter_GlobalTierLast()
|
||||
{
|
||||
// Arrange
|
||||
@@ -344,7 +363,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
|
||||
#region Latency-Based Rules
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LatencySort_LowestPingFirst()
|
||||
{
|
||||
// Arrange
|
||||
@@ -364,7 +384,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result[2].ConnectionId.Should().Be("conn-high");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LatencySort_TiedPing_UsesHeartbeatRecency()
|
||||
{
|
||||
// Arrange
|
||||
@@ -389,7 +410,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
|
||||
#region Rule Combination Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RuleChain_AppliesInOrder()
|
||||
{
|
||||
// Arrange - Multiple healthy connections, different regions, different pings
|
||||
@@ -413,7 +435,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
result.ConnectionId.Should().Be("local-healthy-slow");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RuleChain_FallsBackWhenNoIdealCandidate()
|
||||
{
|
||||
// Arrange - No local healthy connections
|
||||
@@ -440,7 +463,8 @@ public sealed class RoutingRulesEvaluationTests
|
||||
|
||||
#region Determinism Verification
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RuleEvaluation_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Router.Testing\StellaOps.Router.Testing.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -5,7 +6,8 @@ namespace StellaOps.Router.Config.Tests;
|
||||
/// </summary>
|
||||
public sealed class ConfigChangedEventArgsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_SetsPreviousConfig()
|
||||
{
|
||||
// Arrange
|
||||
@@ -19,7 +21,8 @@ public sealed class ConfigChangedEventArgsTests
|
||||
args.Previous.Should().BeSameAs(previous);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_SetsCurrentConfig()
|
||||
{
|
||||
// Arrange
|
||||
@@ -33,7 +36,8 @@ public sealed class ConfigChangedEventArgsTests
|
||||
args.Current.Should().BeSameAs(current);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_SetsChangedAtToCurrentTime()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +54,8 @@ public sealed class ConfigChangedEventArgsTests
|
||||
args.ChangedAt.Should().BeOnOrBefore(afterCreate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_DifferentConfigs_BothAccessible()
|
||||
{
|
||||
// Arrange
|
||||
@@ -71,7 +76,8 @@ public sealed class ConfigChangedEventArgsTests
|
||||
args.Current.Routing.LocalRegion.Should().Be("us-east-1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConfigChangedEventArgs_InheritsFromEventArgs()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,7 +8,8 @@ public sealed class ConfigValidationResultTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Errors_DefaultsToEmptyList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -18,7 +20,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.Errors.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Warnings_DefaultsToEmptyList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -33,7 +36,8 @@ public sealed class ConfigValidationResultTests
|
||||
|
||||
#region IsValid Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsValid_NoErrors_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -43,7 +47,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsValid_WithErrors_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -54,7 +59,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsValid_WithOnlyWarnings_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -65,7 +71,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsValid_WithErrorsAndWarnings_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -77,7 +84,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsValid_MultipleErrors_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -95,7 +103,8 @@ public sealed class ConfigValidationResultTests
|
||||
|
||||
#region Static Success Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Success_ReturnsValidResult()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -107,7 +116,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.Warnings.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Success_ReturnsNewInstance()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -122,7 +132,8 @@ public sealed class ConfigValidationResultTests
|
||||
|
||||
#region Errors Collection Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Errors_CanBeModified()
|
||||
{
|
||||
// Arrange
|
||||
@@ -138,7 +149,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.Errors.Should().Contain("Error 2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Errors_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -156,7 +168,8 @@ public sealed class ConfigValidationResultTests
|
||||
|
||||
#region Warnings Collection Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Warnings_CanBeModified()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +185,8 @@ public sealed class ConfigValidationResultTests
|
||||
result.Warnings.Should().Contain("Warning 2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Warnings_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,7 +8,8 @@ public sealed class RouterConfigOptionsTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_ConfigPath_DefaultsToNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -17,7 +19,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.ConfigPath.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_EnvironmentVariablePrefix_DefaultsToStellaOpsRouter()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -27,7 +30,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.EnvironmentVariablePrefix.Should().Be("STELLAOPS_ROUTER_");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_EnableHotReload_DefaultsToTrue()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -37,7 +41,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.EnableHotReload.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_DebounceInterval_DefaultsTo500Milliseconds()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -47,7 +52,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.DebounceInterval.Should().Be(TimeSpan.FromMilliseconds(500));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_ThrowOnValidationError_DefaultsToFalse()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -57,7 +63,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.ThrowOnValidationError.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_ConfigurationSection_DefaultsToRouter()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -71,7 +78,8 @@ public sealed class RouterConfigOptionsTests
|
||||
|
||||
#region Property Assignment Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConfigPath_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -84,7 +92,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.ConfigPath.Should().Be("/etc/stellaops/router.yaml");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EnvironmentVariablePrefix_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -97,7 +106,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.EnvironmentVariablePrefix.Should().Be("CUSTOM_PREFIX_");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EnableHotReload_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -110,7 +120,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.EnableHotReload.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DebounceInterval_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -123,7 +134,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.DebounceInterval.Should().Be(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ThrowOnValidationError_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -136,7 +148,8 @@ public sealed class RouterConfigOptionsTests
|
||||
options.ThrowOnValidationError.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConfigurationSection_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +33,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_InitializesCurrentConfig()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -42,7 +44,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
provider.Current.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_ExposesOptions()
|
||||
{
|
||||
// Arrange
|
||||
@@ -60,7 +63,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
provider.Options.ConfigPath.Should().Be("/test/path.yaml");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_WithHotReloadDisabled_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -77,7 +81,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region Validate Tests - PayloadLimits
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ValidConfig_ReturnsIsValid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -90,7 +95,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ZeroMaxRequestBytesPerCall_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -105,7 +111,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("MaxRequestBytesPerCall"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_NegativeMaxRequestBytesPerCall_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -120,7 +127,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("MaxRequestBytesPerCall"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ZeroMaxRequestBytesPerConnection_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -135,7 +143,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("MaxRequestBytesPerConnection"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ZeroMaxAggregateInflightBytes_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -150,7 +159,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("MaxAggregateInflightBytes"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_MaxCallBytesLargerThanConnectionBytes_ReturnsWarning()
|
||||
{
|
||||
// Arrange
|
||||
@@ -174,7 +184,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region Validate Tests - RoutingOptions
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ZeroDefaultTimeout_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -189,7 +200,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("DefaultTimeout"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_NegativeDefaultTimeout_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -208,7 +220,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region Validate Tests - Services
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_EmptyServiceName_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -223,7 +236,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("Service name cannot be empty"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhitespaceServiceName_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -238,7 +252,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("Service name cannot be empty"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_DuplicateServiceNames_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -254,7 +269,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("Duplicate service name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_DuplicateServiceNamesCaseInsensitive_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -270,7 +286,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("Duplicate service name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_EndpointEmptyMethod_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -289,7 +306,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("endpoint method cannot be empty"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_EndpointEmptyPath_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -308,7 +326,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("endpoint path cannot be empty"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_EndpointNonPositiveTimeout_ReturnsWarning()
|
||||
{
|
||||
// Arrange
|
||||
@@ -331,7 +350,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region Validate Tests - StaticInstances
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_StaticInstanceEmptyServiceName_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -352,7 +372,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("Static instance service name cannot be empty"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_StaticInstanceEmptyHost_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -373,7 +394,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("host cannot be empty"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(65536)]
|
||||
@@ -398,7 +420,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.Errors.Should().Contain(e => e.Contains("port must be between 1 and 65535"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(80)]
|
||||
[InlineData(443)]
|
||||
@@ -423,7 +446,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(-100)]
|
||||
@@ -452,7 +476,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region ReloadAsync Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReloadAsync_ValidConfig_UpdatesCurrentConfig()
|
||||
{
|
||||
// Arrange
|
||||
@@ -465,7 +490,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
provider.Current.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReloadAsync_InvalidConfig_ThrowsConfigurationException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -483,7 +509,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
provider.Current.PayloadLimits.MaxRequestBytesPerCall.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReloadAsync_Cancelled_ThrowsOperationCanceledException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -499,7 +526,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region ConfigurationChanged Event Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReloadAsync_RaisesConfigurationChangedEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -521,7 +549,8 @@ public sealed class RouterConfigProviderTests : IDisposable
|
||||
|
||||
#region Dispose Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -9,7 +10,8 @@ public sealed class RouterConfigTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_PayloadLimits_DefaultsToNewInstance()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -19,7 +21,8 @@ public sealed class RouterConfigTests
|
||||
config.PayloadLimits.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Routing_DefaultsToNewInstance()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -29,7 +32,8 @@ public sealed class RouterConfigTests
|
||||
config.Routing.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Services_DefaultsToEmptyList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -40,7 +44,8 @@ public sealed class RouterConfigTests
|
||||
config.Services.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_StaticInstances_DefaultsToEmptyList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -55,7 +60,8 @@ public sealed class RouterConfigTests
|
||||
|
||||
#region PayloadLimits Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PayloadLimits_HasDefaultValues()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -67,7 +73,8 @@ public sealed class RouterConfigTests
|
||||
config.PayloadLimits.MaxAggregateInflightBytes.Should().Be(1024 * 1024 * 1024); // 1 GB
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PayloadLimits_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -91,7 +98,8 @@ public sealed class RouterConfigTests
|
||||
|
||||
#region Routing Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Routing_HasDefaultValues()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -104,7 +112,8 @@ public sealed class RouterConfigTests
|
||||
config.Routing.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Routing_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -132,7 +141,8 @@ public sealed class RouterConfigTests
|
||||
|
||||
#region Services Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_CanAddServices()
|
||||
{
|
||||
// Arrange
|
||||
@@ -148,7 +158,8 @@ public sealed class RouterConfigTests
|
||||
config.Services[1].ServiceName.Should().Be("service-b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -170,7 +181,8 @@ public sealed class RouterConfigTests
|
||||
|
||||
#region StaticInstances Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StaticInstances_CanAddInstances()
|
||||
{
|
||||
// Arrange
|
||||
@@ -190,7 +202,8 @@ public sealed class RouterConfigTests
|
||||
config.StaticInstances[0].ServiceName.Should().Be("legacy-service");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StaticInstances_CanBeInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -223,7 +236,8 @@ public sealed class RouterConfigTests
|
||||
|
||||
#region Complete Configuration Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CompleteConfiguration_Works()
|
||||
{
|
||||
// Arrange & Act
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,7 +8,8 @@ public sealed class RoutingOptionsTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_LocalRegion_DefaultsToDefault()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -17,7 +19,8 @@ public sealed class RoutingOptionsTests
|
||||
options.LocalRegion.Should().Be("default");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_NeighborRegions_DefaultsToEmptyList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -28,7 +31,8 @@ public sealed class RoutingOptionsTests
|
||||
options.NeighborRegions.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_TieBreaker_DefaultsToRoundRobin()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -38,7 +42,8 @@ public sealed class RoutingOptionsTests
|
||||
options.TieBreaker.Should().Be(TieBreakerStrategy.RoundRobin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_PreferLocalRegion_DefaultsToTrue()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -48,7 +53,8 @@ public sealed class RoutingOptionsTests
|
||||
options.PreferLocalRegion.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_DefaultTimeout_DefaultsTo30Seconds()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -62,7 +68,8 @@ public sealed class RoutingOptionsTests
|
||||
|
||||
#region Property Assignment Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LocalRegion_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -75,7 +82,8 @@ public sealed class RoutingOptionsTests
|
||||
options.LocalRegion.Should().Be("us-east-1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NeighborRegions_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -90,7 +98,8 @@ public sealed class RoutingOptionsTests
|
||||
options.NeighborRegions.Should().Contain("eu-west-1");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(TieBreakerStrategy.RoundRobin)]
|
||||
[InlineData(TieBreakerStrategy.Random)]
|
||||
[InlineData(TieBreakerStrategy.LeastLoaded)]
|
||||
@@ -107,7 +116,8 @@ public sealed class RoutingOptionsTests
|
||||
options.TieBreaker.Should().Be(strategy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PreferLocalRegion_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -120,7 +130,8 @@ public sealed class RoutingOptionsTests
|
||||
options.PreferLocalRegion.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DefaultTimeout_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -137,7 +148,8 @@ public sealed class RoutingOptionsTests
|
||||
|
||||
#region TieBreakerStrategy Enum Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TieBreakerStrategy_HasFourValues()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -147,28 +159,32 @@ public sealed class RoutingOptionsTests
|
||||
values.Should().HaveCount(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TieBreakerStrategy_RoundRobin_HasValueZero()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
((int)TieBreakerStrategy.RoundRobin).Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TieBreakerStrategy_Random_HasValueOne()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
((int)TieBreakerStrategy.Random).Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TieBreakerStrategy_LeastLoaded_HasValueTwo()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
((int)TieBreakerStrategy.LeastLoaded).Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TieBreakerStrategy_ConsistentHash_HasValueThree()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -10,7 +11,8 @@ public sealed class ServiceConfigTests
|
||||
{
|
||||
#region ServiceConfig Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceConfig_DefaultVersion_DefaultsToNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -20,7 +22,8 @@ public sealed class ServiceConfigTests
|
||||
config.DefaultVersion.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceConfig_DefaultTransport_DefaultsToTcp()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -30,7 +33,8 @@ public sealed class ServiceConfigTests
|
||||
config.DefaultTransport.Should().Be(TransportType.Tcp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceConfig_Endpoints_DefaultsToEmptyList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -45,7 +49,8 @@ public sealed class ServiceConfigTests
|
||||
|
||||
#region ServiceConfig Property Assignment Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceConfig_ServiceName_CanBeSet()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -55,7 +60,8 @@ public sealed class ServiceConfigTests
|
||||
config.ServiceName.Should().Be("my-service");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceConfig_DefaultVersion_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +74,8 @@ public sealed class ServiceConfigTests
|
||||
config.DefaultVersion.Should().Be("1.0.0");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(TransportType.Tcp)]
|
||||
[InlineData(TransportType.Certificate)]
|
||||
[InlineData(TransportType.Udp)]
|
||||
@@ -86,7 +93,8 @@ public sealed class ServiceConfigTests
|
||||
config.DefaultTransport.Should().Be(transport);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceConfig_Endpoints_CanAddEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -104,7 +112,8 @@ public sealed class ServiceConfigTests
|
||||
|
||||
#region EndpointConfig Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_DefaultTimeout_DefaultsToNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -114,7 +123,8 @@ public sealed class ServiceConfigTests
|
||||
endpoint.DefaultTimeout.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_SupportsStreaming_DefaultsToFalse()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -124,7 +134,8 @@ public sealed class ServiceConfigTests
|
||||
endpoint.SupportsStreaming.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_RequiringClaims_DefaultsToEmptyList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -139,7 +150,8 @@ public sealed class ServiceConfigTests
|
||||
|
||||
#region EndpointConfig Property Assignment Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_Method_CanBeSet()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -149,7 +161,8 @@ public sealed class ServiceConfigTests
|
||||
endpoint.Method.Should().Be("DELETE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_Path_CanBeSet()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -159,7 +172,8 @@ public sealed class ServiceConfigTests
|
||||
endpoint.Path.Should().Be("/api/users/{id}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_DefaultTimeout_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +186,8 @@ public sealed class ServiceConfigTests
|
||||
endpoint.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(60));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_SupportsStreaming_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -185,7 +200,8 @@ public sealed class ServiceConfigTests
|
||||
endpoint.SupportsStreaming.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointConfig_RequiringClaims_CanAddClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -203,7 +219,8 @@ public sealed class ServiceConfigTests
|
||||
|
||||
#region Complex Configuration Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceConfig_CompleteConfiguration_Works()
|
||||
{
|
||||
// Arrange & Act
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Config.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -9,7 +10,8 @@ public sealed class StaticInstanceConfigTests
|
||||
{
|
||||
#region Default Values Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Region_DefaultsToDefault()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -25,7 +27,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Region.Should().Be("default");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Transport_DefaultsToTcp()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -41,7 +44,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Transport.Should().Be(TransportType.Tcp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Weight_DefaultsTo100()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -57,7 +61,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Weight.Should().Be(100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Metadata_DefaultsToEmptyDictionary()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -78,7 +83,8 @@ public sealed class StaticInstanceConfigTests
|
||||
|
||||
#region Required Properties Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ServiceName_IsRequired()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -94,7 +100,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.ServiceName.Should().Be("required-service");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Version_IsRequired()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -110,7 +117,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Version.Should().Be("2.3.4");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Host_IsRequired()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -126,7 +134,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Host.Should().Be("192.168.1.100");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Port_IsRequired()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -146,7 +155,8 @@ public sealed class StaticInstanceConfigTests
|
||||
|
||||
#region Property Assignment Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Region_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -165,7 +175,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Region.Should().Be("us-west-2");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(TransportType.Tcp)]
|
||||
[InlineData(TransportType.Certificate)]
|
||||
[InlineData(TransportType.Udp)]
|
||||
@@ -189,7 +200,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Transport.Should().Be(transport);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(50)]
|
||||
[InlineData(100)]
|
||||
@@ -213,7 +225,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Weight.Should().Be(weight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Metadata_CanAddEntries()
|
||||
{
|
||||
// Arrange
|
||||
@@ -239,7 +252,8 @@ public sealed class StaticInstanceConfigTests
|
||||
|
||||
#region Complex Configuration Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CompleteConfiguration_Works()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -271,7 +285,8 @@ public sealed class StaticInstanceConfigTests
|
||||
config.Metadata.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MultipleInstances_CanHaveDifferentWeights()
|
||||
{
|
||||
// Arrange & Act
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Config\StellaOps.Router.Config.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Router.Testing\StellaOps.Router.Testing.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,7 +19,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
|
||||
#region Initialization Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_IsInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -28,7 +30,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
connectionManager.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_HasConnections()
|
||||
{
|
||||
// Arrange
|
||||
@@ -41,7 +44,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
connections.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_ConnectionHasCorrectServiceInfo()
|
||||
{
|
||||
// Arrange
|
||||
@@ -58,7 +62,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
connection.Instance.InstanceId.Should().Be("test-instance-001");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_ConnectionHasEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -75,7 +80,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
|
||||
#region Status Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_DefaultStatus_IsHealthy()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +94,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
status.Should().Be(InstanceHealthStatus.Healthy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_CanChangeStatus()
|
||||
{
|
||||
// Arrange
|
||||
@@ -106,7 +113,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
newStatus.Should().Be(InstanceHealthStatus.Degraded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(InstanceHealthStatus.Healthy)]
|
||||
[InlineData(InstanceHealthStatus.Degraded)]
|
||||
[InlineData(InstanceHealthStatus.Draining)]
|
||||
@@ -132,7 +140,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
|
||||
#region Metrics Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_InFlightRequestCount_InitiallyZero()
|
||||
{
|
||||
// Arrange
|
||||
@@ -145,7 +154,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
count.Should().BeGreaterOrEqualTo(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_ErrorRate_InitiallyZero()
|
||||
{
|
||||
// Arrange
|
||||
@@ -159,7 +169,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
errorRate.Should().BeLessThanOrEqualTo(1.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_CanSetInFlightRequestCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -177,7 +188,8 @@ public sealed class ConnectionManagerIntegrationTests
|
||||
newCount.Should().Be(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_CanSetErrorRate()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -24,7 +25,8 @@ public sealed class EndToEndRoutingTests
|
||||
|
||||
#region Basic Request/Response Flow
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Route_EchoEndpoint_IsRegistered()
|
||||
{
|
||||
// Arrange & Act - Verify endpoint is registered for routing
|
||||
@@ -35,7 +37,8 @@ public sealed class EndToEndRoutingTests
|
||||
endpoints.Should().Contain(e => e.Path == "/echo" && e.Method == "POST");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Route_GetUserEndpoint_MatchesPathPattern()
|
||||
{
|
||||
// Act
|
||||
@@ -50,7 +53,8 @@ public sealed class EndToEndRoutingTests
|
||||
getUserEndpoint!.Path.Should().Be("/users/{userId}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Route_CreateUserEndpoint_PreservesCorrelationId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -82,7 +86,8 @@ public sealed class EndToEndRoutingTests
|
||||
|
||||
#region Endpoint Registration Verification
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointRegistry_ContainsAllTestEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -109,7 +114,8 @@ public sealed class EndToEndRoutingTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointRegistry_EachEndpointHasUniqueMethodPath()
|
||||
{
|
||||
// Act
|
||||
@@ -124,7 +130,8 @@ public sealed class EndToEndRoutingTests
|
||||
|
||||
#region Connection Manager State
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_HasActiveConnections()
|
||||
{
|
||||
// Act
|
||||
@@ -134,7 +141,8 @@ public sealed class EndToEndRoutingTests
|
||||
connections.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionManager_ConnectionsHaveInstanceInfo()
|
||||
{
|
||||
// Act
|
||||
@@ -153,7 +161,8 @@ public sealed class EndToEndRoutingTests
|
||||
|
||||
#region Frame Protocol Integration
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Frame_RequestSerializationRoundTrip_PreservesAllFields()
|
||||
{
|
||||
// Arrange
|
||||
@@ -185,7 +194,8 @@ public sealed class EndToEndRoutingTests
|
||||
restored.Payload.ToArray().Should().BeEquivalentTo(original.Payload.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Frame_ResponseSerializationRoundTrip_PreservesAllFields()
|
||||
{
|
||||
// Arrange
|
||||
@@ -217,7 +227,8 @@ public sealed class EndToEndRoutingTests
|
||||
|
||||
#region Path Matching Integration
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("GET", "/users/123", true)]
|
||||
[InlineData("GET", "/users/abc-def", true)]
|
||||
[InlineData("GET", "/users/", false)]
|
||||
@@ -237,7 +248,8 @@ public sealed class EndToEndRoutingTests
|
||||
isMatch.Should().Be(shouldMatch);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("/echo", "/echo", true)]
|
||||
[InlineData("/echo", "/Echo", true)] // PathMatcher is case-insensitive
|
||||
[InlineData("/users", "/users", true)]
|
||||
@@ -259,7 +271,8 @@ public sealed class EndToEndRoutingTests
|
||||
|
||||
#region Routing Determinism
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Routing_SameRequest_AlwaysSameEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
@@ -283,7 +296,8 @@ public sealed class EndToEndRoutingTests
|
||||
results.Should().OnlyContain(r => r == "POST:/echo");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Routing_MultipleEndpoints_DeterministicOrdering()
|
||||
{
|
||||
// Act - Get endpoints multiple times
|
||||
@@ -300,7 +314,8 @@ public sealed class EndToEndRoutingTests
|
||||
|
||||
#region Error Routing
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EndpointRegistry_ContainsFailEndpoint()
|
||||
{
|
||||
// Act
|
||||
@@ -310,7 +325,8 @@ public sealed class EndToEndRoutingTests
|
||||
endpoints.Should().Contain(e => e.Path == "/fail" && e.Method == "POST");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Routing_UnknownPath_NoMatchingEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Microservice;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,7 +19,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
|
||||
#region Endpoint Discovery Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Registry_ContainsAllTestEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -31,7 +33,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
endpoints.Should().HaveCount(17);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("POST", "/echo")]
|
||||
[InlineData("GET", "/users/123")]
|
||||
[InlineData("POST", "/users")]
|
||||
@@ -54,7 +57,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
match!.Endpoint.Method.Should().Be(method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Registry_ReturnsNull_ForUnknownEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +72,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
match.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Registry_MatchesPathParameters()
|
||||
{
|
||||
// Arrange
|
||||
@@ -82,7 +87,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
match!.Endpoint.Path.Should().Be("/users/{userId}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Registry_ExtractsPathParameters()
|
||||
{
|
||||
// Arrange
|
||||
@@ -101,7 +107,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
|
||||
#region Endpoint Metadata Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectTimeout()
|
||||
{
|
||||
// Arrange
|
||||
@@ -116,7 +123,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
slowMatch!.Endpoint.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(60));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectStreamingFlag()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +139,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
echoMatch!.Endpoint.SupportsStreaming.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectClaims()
|
||||
{
|
||||
// Arrange
|
||||
@@ -148,7 +157,8 @@ public sealed class EndpointRegistryIntegrationTests
|
||||
echoMatch!.Endpoint.RequiringClaims.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectHandlerType()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -6,6 +6,8 @@ using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Transport.InMemory;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,7 +38,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
|
||||
#region FIFO Ordering Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_SingleProducer_SingleConsumer_FIFO()
|
||||
{
|
||||
// Arrange
|
||||
@@ -63,7 +66,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
receivedOrder.Should().BeEquivalentTo(sentOrder, options => options.WithStrictOrdering());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_SingleProducer_DelayedConsumer_FIFO()
|
||||
{
|
||||
// Arrange
|
||||
@@ -92,7 +96,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
receivedOrder.Should().BeEquivalentTo(sentOrder, options => options.WithStrictOrdering());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_ConcurrentProducerConsumer_FIFO()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +136,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
|
||||
#region Bidirectional Ordering
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_BothDirections_IndependentFIFO()
|
||||
{
|
||||
// Arrange
|
||||
@@ -182,7 +188,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
|
||||
#region Ordering Under Backpressure
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_WithBackpressure_FIFO()
|
||||
{
|
||||
// Arrange - Small buffer to force backpressure
|
||||
@@ -223,7 +230,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
|
||||
#region Frame Type Ordering
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_MixedFrameTypes_FIFO()
|
||||
{
|
||||
// Arrange
|
||||
@@ -262,7 +270,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
|
||||
#region Correlation ID Ordering
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_CorrelationIds_Preserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -297,7 +306,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
|
||||
#region Large Message Ordering
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_VariablePayloadSizes_FIFO()
|
||||
{
|
||||
// Arrange
|
||||
@@ -334,7 +344,8 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
|
||||
|
||||
#region Ordering Determinism
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_MultipleRuns_Deterministic()
|
||||
{
|
||||
// Run the same sequence multiple times and verify deterministic ordering
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -26,7 +27,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region FromQuery - Query Parameter Binding
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_StringParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -41,7 +43,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Query.Should().Be("test-search-term");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_IntParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -58,7 +61,8 @@ public sealed class ParameterBindingTests
|
||||
result.PageSize.Should().Be(25);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_BoolParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -73,7 +77,8 @@ public sealed class ParameterBindingTests
|
||||
result!.IncludeDeleted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_MultipleParameters_BindCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -94,7 +99,8 @@ public sealed class ParameterBindingTests
|
||||
result.IncludeDeleted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_UrlEncodedValues_BindCorrectly()
|
||||
{
|
||||
// Arrange - Query with special characters
|
||||
@@ -112,7 +118,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Query.Should().Be(query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_OptionalParameters_UseDefaults()
|
||||
{
|
||||
// Arrange & Act - No query parameters provided
|
||||
@@ -129,7 +136,8 @@ public sealed class ParameterBindingTests
|
||||
result.SortOrder.Should().Be("asc"); // Default
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_OverrideDefaults_BindCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -150,7 +158,8 @@ public sealed class ParameterBindingTests
|
||||
result.SortOrder.Should().Be("desc");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromQuery_WithAnonymousObject_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act - Using anonymous object for multiple query params
|
||||
@@ -171,7 +180,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region FromRoute - Path Parameter Binding
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_SinglePathParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -185,7 +195,8 @@ public sealed class ParameterBindingTests
|
||||
result!.UserId.Should().Be("user-123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_MultiplePathParameters_BindCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -201,7 +212,8 @@ public sealed class ParameterBindingTests
|
||||
result.Name.Should().Be("Item-widget-456-in-electronics");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_NumericPathParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -215,7 +227,8 @@ public sealed class ParameterBindingTests
|
||||
result!.UserId.Should().Be("12345");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_GuidPathParameter_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -232,7 +245,8 @@ public sealed class ParameterBindingTests
|
||||
result!.UserId.Should().Be(guid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromRoute_SpecialCharactersInPath_BindsCorrectly()
|
||||
{
|
||||
// Arrange - URL-encoded special characters
|
||||
@@ -255,7 +269,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region FromHeader - Header Binding
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromHeader_AuthorizationHeader_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -270,7 +285,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Authorization.Should().Be("Bearer test-token-12345");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromHeader_CustomHeaders_BindCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -289,7 +305,8 @@ public sealed class ParameterBindingTests
|
||||
result!.AcceptLanguage.Should().Be("en-US");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromHeader_MultipleHeaders_AllAccessible()
|
||||
{
|
||||
// Arrange
|
||||
@@ -319,7 +336,8 @@ public sealed class ParameterBindingTests
|
||||
result.AllHeaders.Should().ContainKey("Accept-Language");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromHeader_BearerToken_ParsesCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -338,7 +356,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region FromBody - JSON Body Binding
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_SimpleJson_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -353,7 +372,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Echo.Should().Contain("Hello, World!");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_ComplexObject_BindsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -372,7 +392,8 @@ public sealed class ParameterBindingTests
|
||||
result.UserId.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_AnonymousObject_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -387,7 +408,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Echo.Should().Contain("Anonymous type test");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_NestedObject_BindsCorrectly()
|
||||
{
|
||||
// Arrange - For raw echo we can test nested JSON structure
|
||||
@@ -414,7 +436,8 @@ public sealed class ParameterBindingTests
|
||||
body.Should().Contain("deeply nested");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromBody_CamelCaseNaming_BindsCorrectly()
|
||||
{
|
||||
// Arrange - Ensure camelCase property naming works
|
||||
@@ -435,7 +458,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region FromForm - Form Data Binding
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_SimpleFormData_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -452,7 +476,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Password.Should().Be("secret123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_BooleanField_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -469,7 +494,8 @@ public sealed class ParameterBindingTests
|
||||
result!.RememberMe.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_WithAnonymousObject_BindsCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -486,7 +512,8 @@ public sealed class ParameterBindingTests
|
||||
result!.RememberMe.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_UrlEncodedSpecialChars_BindsCorrectly()
|
||||
{
|
||||
// Arrange - Special characters that need URL encoding
|
||||
@@ -505,7 +532,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Password.Should().Be(password);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FromForm_ContentType_IsCorrect()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -525,7 +553,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region Combined Binding - Multiple Sources
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CombinedBinding_PathAndBody_BindCorrectly()
|
||||
{
|
||||
// Arrange - PUT /resources/{resourceId} with JSON body
|
||||
@@ -545,7 +574,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Description.Should().Be("New description");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CombinedBinding_PathQueryAndBody_BindCorrectly()
|
||||
{
|
||||
// Arrange - PUT /resources/{resourceId}?format=json&verbose=true with body
|
||||
@@ -568,7 +598,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Name.Should().Be("Full Update");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CombinedBinding_HeadersAndBody_BindCorrectly()
|
||||
{
|
||||
// Arrange - POST with headers and JSON body
|
||||
@@ -589,7 +620,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region HTTP Methods
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpGet_ReturnsData()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -603,7 +635,8 @@ public sealed class ParameterBindingTests
|
||||
result!.UserId.Should().Be("get-test-user");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPost_CreatesResource()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -618,7 +651,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Success.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPut_UpdatesResource()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -634,7 +668,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Name.Should().Be("Updated Name");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPatch_PartialUpdate()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -653,7 +688,8 @@ public sealed class ParameterBindingTests
|
||||
result!.UpdatedFields.Should().Contain("price");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpPatch_PartialUpdate_OnlySpecifiedFields()
|
||||
{
|
||||
// Arrange & Act - Only update name, not price
|
||||
@@ -669,7 +705,8 @@ public sealed class ParameterBindingTests
|
||||
result!.UpdatedFields.Should().NotContain("price");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HttpDelete_RemovesResource()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -688,7 +725,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region Raw Body Handling
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RawBody_PlainText_HandledCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -705,7 +743,8 @@ public sealed class ParameterBindingTests
|
||||
body.Should().Be(text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RawBody_Xml_HandledCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -722,7 +761,8 @@ public sealed class ParameterBindingTests
|
||||
body.Should().Be(xml);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RawBody_Binary_HandledCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -740,7 +780,8 @@ public sealed class ParameterBindingTests
|
||||
response.Payload.Length.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RawBody_ResponseHeaders_IncludeContentLength()
|
||||
{
|
||||
// Arrange
|
||||
@@ -761,7 +802,8 @@ public sealed class ParameterBindingTests
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EmptyBody_HandledCorrectly()
|
||||
{
|
||||
// Arrange & Act - GET with no body should work for endpoints with optional params
|
||||
@@ -777,7 +819,8 @@ public sealed class ParameterBindingTests
|
||||
result.Limit.Should().Be(20);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EmptyQueryString_UsesDefaults()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -793,7 +836,8 @@ public sealed class ParameterBindingTests
|
||||
result.PageSize.Should().Be(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConcurrentRequests_HandleCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -816,7 +860,8 @@ public sealed class ParameterBindingTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LargePayload_HandledCorrectly()
|
||||
{
|
||||
// Arrange - Create a moderately large message
|
||||
@@ -834,7 +879,8 @@ public sealed class ParameterBindingTests
|
||||
result!.Echo.Should().Contain(largeMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task UnicodeContent_HandledCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Microservice;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,7 +19,8 @@ public sealed class PathMatchingIntegrationTests
|
||||
|
||||
#region Exact Path Matching Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("POST", "/echo")]
|
||||
[InlineData("POST", "/users")]
|
||||
[InlineData("POST", "/slow")]
|
||||
@@ -43,7 +45,8 @@ public sealed class PathMatchingIntegrationTests
|
||||
|
||||
#region Parameterized Path Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("/users/123", "/users/{userId}")]
|
||||
[InlineData("/users/abc-def", "/users/{userId}")]
|
||||
[InlineData("/users/user_001", "/users/{userId}")]
|
||||
@@ -60,7 +63,8 @@ public sealed class PathMatchingIntegrationTests
|
||||
match!.Endpoint.Path.Should().Be(expectedPattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathMatching_PostUsersPath_MatchesCreateEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
@@ -78,7 +82,8 @@ public sealed class PathMatchingIntegrationTests
|
||||
|
||||
#region Non-Matching Path Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("GET", "/nonexistent")]
|
||||
[InlineData("POST", "/unknown/path")]
|
||||
[InlineData("PUT", "/echo")] // Wrong method
|
||||
@@ -100,7 +105,8 @@ public sealed class PathMatchingIntegrationTests
|
||||
|
||||
#region Method Matching Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathMatching_SamePathDifferentMethods_MatchCorrectEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -4,6 +4,7 @@ using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,7 +24,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region Echo Endpoint Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_EchoEndpoint_ReturnsExpectedResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -43,7 +45,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
echoResponse.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_EchoEndpoint_ReturnsValidRequestId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +59,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
response.RequestId.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("Simple message")]
|
||||
[InlineData("Numbers and underscores 123_456_789")]
|
||||
[InlineData("Long message with multiple words and spaces")]
|
||||
@@ -78,7 +82,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region User Endpoints Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_GetUser_EndpointResponds()
|
||||
{
|
||||
// Arrange - Path parameters are extracted by the microservice
|
||||
@@ -93,7 +98,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
response.StatusCode.Should().BeOneOf(200, 400); // 400 if path param binding issue, 200 if working
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_CreateUser_ReturnsNewUserId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -115,7 +121,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region Error Handling Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_FailEndpoint_ReturnsInternalError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -128,7 +135,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
response.StatusCode.Should().Be(500);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_NonexistentEndpoint_Returns404()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -138,7 +146,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
response.StatusCode.Should().Be(404);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_WrongHttpMethod_Returns404()
|
||||
{
|
||||
// Arrange - /echo is POST only
|
||||
@@ -155,7 +164,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region Slow/Timeout Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_SlowEndpoint_CompletesWithinTimeout()
|
||||
{
|
||||
// Arrange - 100ms delay should complete within 30s timeout
|
||||
@@ -175,7 +185,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region Concurrent Requests Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_MultipleRequests_AllSucceed()
|
||||
{
|
||||
// Arrange
|
||||
@@ -192,7 +203,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
responses.Should().OnlyContain(r => r.StatusCode == 200);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_ConcurrentDifferentEndpoints_AllSucceed()
|
||||
{
|
||||
// Arrange & Act - only use endpoints that work with request body binding
|
||||
@@ -216,7 +228,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region Connection State Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Connection_HasRegisteredEndpoints()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -231,7 +244,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
connection.Endpoints.Should().ContainKey(("POST", "/users"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Connection_HasCorrectInstanceInfo()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -249,7 +263,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region Frame Protocol Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispatch_RequestFrameConversion_PreservesData()
|
||||
{
|
||||
// Arrange
|
||||
@@ -284,7 +299,8 @@ public sealed class RequestDispatchIntegrationTests
|
||||
|
||||
#region Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispatch_SameRequest_ProducesDeterministicResponse()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -4,6 +4,8 @@ using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
using StellaOps.Router.Transport.InMemory;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -21,7 +23,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
|
||||
#region Core Services Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_MicroserviceOptionsAreRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -33,7 +36,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
options.Version.Should().Be("1.0.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_EndpointRegistryIsRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -43,7 +47,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
registry.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_ConnectionManagerIsRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -53,7 +58,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
connectionManager.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_RequestDispatcherIsRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -63,7 +69,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
dispatcher.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_EndpointDiscoveryServiceIsRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -77,7 +84,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
|
||||
#region Transport Services Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_TransportClientIsRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -88,7 +96,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
client.Should().BeOfType<InMemoryTransportClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_TransportServerIsRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -99,7 +108,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
server.Should().BeOfType<InMemoryTransportServer>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_InMemoryConnectionRegistryIsRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -113,7 +123,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
|
||||
#region Endpoint Handler Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_EndpointHandlersAreRegistered()
|
||||
{
|
||||
// Act
|
||||
@@ -128,7 +139,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
createUserEndpoint.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_EndpointHandlersAreScopedInstances()
|
||||
{
|
||||
// Act
|
||||
@@ -146,7 +158,8 @@ public sealed class ServiceRegistrationIntegrationTests
|
||||
|
||||
#region Singleton Services Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Services_SingletonServicesAreSameInstance()
|
||||
{
|
||||
// Act
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.InMemory\StellaOps.Router.Transport.InMemory.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Microservice\StellaOps.Microservice.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Router.Testing\StellaOps.Router.Testing.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,7 @@ using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
using StellaOps.Router.Transport.InMemory;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -20,7 +21,8 @@ public sealed class TransportIntegrationTests
|
||||
|
||||
#region InMemory Transport Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Transport_ClientIsRegistered()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -31,7 +33,8 @@ public sealed class TransportIntegrationTests
|
||||
client.Should().BeOfType<InMemoryTransportClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Transport_ConnectionRegistryIsShared()
|
||||
{
|
||||
// Arrange
|
||||
@@ -45,7 +48,8 @@ public sealed class TransportIntegrationTests
|
||||
|
||||
#region Connection Lifecycle Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Transport_ConnectionIsEstablished()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,6 +2,8 @@ using System.Threading.Channels;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Transport.InMemory.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +14,8 @@ public sealed class BackpressureTests
|
||||
{
|
||||
#region Bounded Channel Backpressure
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_BoundedChannel_BlocksProducer()
|
||||
{
|
||||
// Arrange
|
||||
@@ -32,7 +35,8 @@ public sealed class BackpressureTests
|
||||
canWrite.Should().BeFalse("Channel should be at capacity");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_DrainOne_AllowsOneMore()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +60,8 @@ public sealed class BackpressureTests
|
||||
canWriteAfterDrain.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_DrainAll_AllowsFullRefill()
|
||||
{
|
||||
// Arrange
|
||||
@@ -94,7 +99,8 @@ public sealed class BackpressureTests
|
||||
|
||||
#region Slow Consumer Scenarios
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_SlowConsumer_ProducerWaits()
|
||||
{
|
||||
// Arrange
|
||||
@@ -134,7 +140,8 @@ public sealed class BackpressureTests
|
||||
consumed.Should().Be(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_SlowConsumer_NoMessageDropped()
|
||||
{
|
||||
// Arrange
|
||||
@@ -177,7 +184,8 @@ public sealed class BackpressureTests
|
||||
|
||||
#region Async Write With Backpressure
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_AsyncWrite_WaitsForSpace()
|
||||
{
|
||||
// Arrange
|
||||
@@ -203,7 +211,8 @@ public sealed class BackpressureTests
|
||||
writeTask.IsCompleted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_WaitToWriteAsync_ReturnsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -231,7 +240,8 @@ public sealed class BackpressureTests
|
||||
|
||||
#region Unbounded Channel Behavior
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_UnboundedChannel_NeverBlocks()
|
||||
{
|
||||
// Arrange - Unbounded channel (default)
|
||||
@@ -254,7 +264,8 @@ public sealed class BackpressureTests
|
||||
readCount.Should().Be(messageCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_UnboundedChannel_HighThroughput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -291,7 +302,8 @@ public sealed class BackpressureTests
|
||||
|
||||
#region Bidirectional Backpressure
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_BothDirections_Independent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -326,7 +338,8 @@ public sealed class BackpressureTests
|
||||
|
||||
#region Channel Completion With Pending Items
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_CompleteWithPendingItems_AllDrained()
|
||||
{
|
||||
// Arrange
|
||||
@@ -354,7 +367,8 @@ public sealed class BackpressureTests
|
||||
drained.Should().HaveCount(itemCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_CompleteWithWaitingWriter_Fails()
|
||||
{
|
||||
// Arrange
|
||||
@@ -380,7 +394,8 @@ public sealed class BackpressureTests
|
||||
|
||||
#region Cancellation During Backpressure
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_CancelledDuringWait_Throws()
|
||||
{
|
||||
// Arrange
|
||||
@@ -402,7 +417,8 @@ public sealed class BackpressureTests
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(() => writeTask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Backpressure_AlreadyCancelled_ThrowsImmediately()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,6 +2,8 @@ using System.Threading.Channels;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Transport.InMemory.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +34,8 @@ public sealed class InMemoryChannelTests
|
||||
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_SetsConnectionId()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -42,7 +45,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.ConnectionId.Should().Be("conn-123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_CreatesUnboundedChannels_ByDefault()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -53,7 +57,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.ToGateway.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_CreatesBoundedChannels_WhenBufferSizeSpecified()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -64,7 +69,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.ToGateway.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_CreatesLifetimeToken()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -75,7 +81,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.LifetimeToken.IsCancellationRequested.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_Instance_IsInitiallyNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -85,7 +92,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.Instance.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_State_IsInitiallyNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -99,7 +107,8 @@ public sealed class InMemoryChannelTests
|
||||
|
||||
#region Property Assignment Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Instance_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -114,7 +123,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.Instance.ServiceName.Should().Be("test-service");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void State_CanBeSet()
|
||||
{
|
||||
// Arrange
|
||||
@@ -132,7 +142,8 @@ public sealed class InMemoryChannelTests
|
||||
|
||||
#region Channel Communication Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ToMicroservice_CanWriteAndRead()
|
||||
{
|
||||
// Arrange
|
||||
@@ -152,7 +163,8 @@ public sealed class InMemoryChannelTests
|
||||
received.Should().BeSameAs(frame);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ToGateway_CanWriteAndRead()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +184,8 @@ public sealed class InMemoryChannelTests
|
||||
received.Should().BeSameAs(frame);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Channel_MultipleFrames_DeliveredInOrder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -200,7 +213,8 @@ public sealed class InMemoryChannelTests
|
||||
|
||||
#region Bounded Channel Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BoundedChannel_AcceptsUpToBufferSize()
|
||||
{
|
||||
// Arrange
|
||||
@@ -221,7 +235,8 @@ public sealed class InMemoryChannelTests
|
||||
|
||||
#region Dispose Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CancelsLifetimeToken()
|
||||
{
|
||||
// Arrange
|
||||
@@ -234,7 +249,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.LifetimeToken.IsCancellationRequested.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CompletesChannels()
|
||||
{
|
||||
// Arrange
|
||||
@@ -248,7 +264,8 @@ public sealed class InMemoryChannelTests
|
||||
channel.ToGateway.Reader.Completion.IsCompleted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -266,7 +283,8 @@ public sealed class InMemoryChannelTests
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispose_ReaderDetectsCompletion()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Transport.InMemory.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -43,7 +44,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region CreateChannel Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateChannel_ReturnsNewChannel()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -54,7 +56,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
channel.ConnectionId.Should().Be("conn-123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateChannel_IncreasesCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -67,7 +70,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
_registry.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateChannel_WithBufferSize_CreatesCorrectChannel()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -77,7 +81,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
channel.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateChannel_DuplicateId_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -91,7 +96,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
.WithMessage("*conn-123*already exists*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateChannel_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -108,7 +114,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region GetChannel Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetChannel_ExistingConnection_ReturnsChannel()
|
||||
{
|
||||
// Arrange
|
||||
@@ -121,7 +128,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
retrieved.Should().BeSameAs(created);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetChannel_NonexistentConnection_ReturnsNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -135,7 +143,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region GetRequiredChannel Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetRequiredChannel_ExistingConnection_ReturnsChannel()
|
||||
{
|
||||
// Arrange
|
||||
@@ -148,7 +157,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
retrieved.Should().BeSameAs(created);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetRequiredChannel_NonexistentConnection_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -163,7 +173,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region RemoveChannel Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RemoveChannel_ExistingConnection_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -177,7 +188,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
_registry.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RemoveChannel_NonexistentConnection_ReturnsFalse()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -187,7 +199,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RemoveChannel_DisposesChannel()
|
||||
{
|
||||
// Arrange
|
||||
@@ -201,7 +214,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
token.IsCancellationRequested.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RemoveChannel_CannotGetAfterRemove()
|
||||
{
|
||||
// Arrange
|
||||
@@ -219,7 +233,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region ConnectionIds Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionIds_EmptyRegistry_ReturnsEmpty()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -229,7 +244,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
ids.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionIds_WithConnections_ReturnsAllIds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -251,14 +267,16 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region Count Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Count_EmptyRegistry_IsZero()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
_registry.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Count_ReflectsActiveConnections()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -274,7 +292,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region GetAllConnections Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetAllConnections_EmptyRegistry_ReturnsEmpty()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -284,7 +303,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
connections.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetAllConnections_ChannelsWithoutState_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
@@ -298,7 +318,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
connections.Should().BeEmpty(); // No State set on channels
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetAllConnections_ChannelsWithState_ReturnsStates()
|
||||
{
|
||||
// Arrange
|
||||
@@ -319,7 +340,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region GetConnectionsFor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetConnectionsFor_NoMatchingConnections_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
@@ -332,7 +354,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
connections.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetConnectionsFor_MatchingServiceAndEndpoint_ReturnsConnections()
|
||||
{
|
||||
// Arrange
|
||||
@@ -354,7 +377,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
connections.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetConnectionsFor_MismatchedVersion_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
@@ -380,7 +404,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region Dispose Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_DisposesAllChannels()
|
||||
{
|
||||
// Arrange
|
||||
@@ -397,7 +422,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
token2.IsCancellationRequested.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_ClearsRegistry()
|
||||
{
|
||||
// Arrange
|
||||
@@ -411,7 +437,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
// We need a separate test for post-dispose behavior
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -433,7 +460,8 @@ public sealed class InMemoryConnectionRegistryTests : IDisposable
|
||||
|
||||
#region Concurrency Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConcurrentOperations_ThreadSafe()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user