Refactor code structure and optimize performance across multiple modules

This commit is contained in:
StellaOps Bot
2025-12-26 20:03:22 +02:00
parent c786faae84
commit f10d83c444
1385 changed files with 69732 additions and 10280 deletions

View File

@@ -29,7 +29,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task LoadFromDirectoryAsync_LoadsPemFiles()
{
// Arrange
@@ -46,7 +47,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 +62,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 +77,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
Assert.Contains("required", result.Error);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task LoadFromDirectoryAsync_LoadsFromManifest()
{
// Arrange
@@ -108,7 +112,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 +144,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 +159,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
Assert.Contains("empty", result.Error);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadFromBundle_FailsWithInvalidJson()
{
// Arrange
@@ -167,7 +174,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
Assert.False(result.Success);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetPublicKey_ReturnsKey()
{
// Arrange
@@ -185,7 +193,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
Assert.NotNull(result.KeyBytes);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetPublicKey_ReturnsNotFound()
{
// Arrange
@@ -200,7 +209,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 +246,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
Assert.Contains("expired", result.Warning);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateVerificationKey_ReturnsEcdsaKey()
{
// Arrange
@@ -273,7 +284,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
key.Dispose();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateVerificationKey_ReturnsNullForMissingKey()
{
// Arrange
@@ -287,7 +299,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
Assert.Null(key);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAvailableKeyIds_ReturnsAllKeys()
{
// Arrange
@@ -305,7 +318,8 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
Assert.Contains("key2", keyIds);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Count_ReturnsCorrectValue()
{
// Arrange
@@ -321,6 +335,7 @@ public class AirGapTrustStoreIntegrationTests : IDisposable
private static string GenerateEcdsaPublicKeyPem()
{
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using StellaOps.TestKit;
return ecdsa.ExportSubjectPublicKeyInfoPem();
}
}

View File

@@ -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

View File

@@ -41,7 +41,8 @@ public class AuditReplayE2ETests : IDisposable
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task E2E_ExportTransferReplayOffline_MatchingVerdict()
{
// ===== PHASE 1: EXPORT =====
@@ -146,7 +147,8 @@ public class AuditReplayE2ETests : IDisposable
Assert.Equal(decision, replayResult.OriginalDecision);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task E2E_ReplayDetectsTamperedSbom()
{
// Setup
@@ -222,7 +224,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 +274,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 +327,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
@@ -506,6 +511,7 @@ public class AuditReplayE2ETests : IDisposable
private static async Task<string> ComputeFileHashAsync(string filePath)
{
await using var stream = File.OpenRead(filePath);
using StellaOps.TestKit;
var hash = await SHA256.HashDataAsync(stream);
return Convert.ToHexString(hash).ToLowerInvariant();
}

View File

@@ -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[]

View File

@@ -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");

View File

@@ -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();

View File

@@ -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

View File

@@ -10,7 +10,8 @@ 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 +54,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 +92,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 +116,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,10 +154,12 @@ 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();
using StellaOps.TestKit;
var authenticator = new TestFidoAuthenticator(fixture);
var options = new Fido2Options
{

View File

@@ -12,7 +12,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 +50,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,10 +68,12 @@ 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();
using StellaOps.TestKit;
var keyId = "kms-export";
await client.RotateAsync(keyId);

View File

@@ -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

View File

@@ -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();

View File

@@ -9,7 +9,8 @@ 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();
@@ -19,6 +20,7 @@ public sealed class BouncyCastleEd25519CryptoProviderTests
services.AddBouncyCastleEd25519Provider();
using var provider = services.BuildServiceProvider();
using StellaOps.TestKit;
var registry = provider.GetRequiredService<ICryptoProviderRegistry>();
var bcProvider = provider.GetServices<ICryptoProvider>()
.OfType<BouncyCastleEd25519CryptoProvider>()

View File

@@ -11,7 +11,8 @@ namespace StellaOps.Cryptography.Tests;
public class CryptoProGostSignerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExportPublicJsonWebKey_ContainsCertificateChain()
{
if (!OperatingSystem.IsWindows())
@@ -28,6 +29,7 @@ public class CryptoProGostSignerTests
var request = new CertificateRequest("CN=stellaops.test", ecdsa, HashAlgorithmName.SHA256);
using var cert = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1));
using StellaOps.TestKit;
var entry = new CryptoProGostKeyEntry(
"test-key",
SignatureAlgorithms.GostR3410_2012_256,

View File

@@ -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();

View File

@@ -14,7 +14,8 @@ 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 +24,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 +34,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 +44,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 +54,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 +65,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,12 +75,14 @@ public sealed class DefaultCryptoHashTests
Assert.Equal(expected, actual);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ComputeHashHexAsync_Sha256_MatchesBclLowerHex()
{
var hash = CryptoHashFactory.CreateDefault();
var expected = Convert.ToHexStringLower(SHA256.HashData(Sample));
await using var stream = new MemoryStream(Sample);
using StellaOps.TestKit;
var actual = await hash.ComputeHashHexAsync(stream, HashAlgorithms.Sha256);
Assert.Equal(expected, actual);
}

View File

@@ -13,7 +13,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,12 +23,14 @@ public sealed class DefaultCryptoHmacTests
Assert.Equal(expected, actual);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ComputeHmacHexForPurposeAsync_WebhookInterop_MatchesBclLowerHex()
{
var hmac = DefaultCryptoHmac.CreateForTests();
var expected = Convert.ToHexStringLower(HMACSHA256.HashData(Key, Sample));
await using var stream = new MemoryStream(Sample);
using StellaOps.TestKit;
var actual = await hmac.ComputeHmacHexForPurposeAsync(Key, stream, HmacPurpose.WebhookInterop);
Assert.Equal(expected, actual);
}

View File

@@ -11,7 +11,8 @@ namespace StellaOps.Cryptography.Tests;
public class DefaultCryptoProviderSigningTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpsertSigningKey_AllowsSignAndVerifyEs256()
{
var provider = new DefaultCryptoProvider();
@@ -52,11 +53,13 @@ public class DefaultCryptoProviderSigningTests
Assert.False(tamperedResult);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RemoveSigningKey_PreventsRetrieval()
{
var provider = new DefaultCryptoProvider();
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using StellaOps.TestKit;
var parameters = ecdsa.ExportParameters(true);
var signingKey = new CryptoSigningKey(new CryptoKeyReference("key-to-remove"), SignatureAlgorithms.Es256, in parameters, DateTimeOffset.UtcNow);

View File

@@ -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];

View File

@@ -9,11 +9,13 @@ namespace StellaOps.Cryptography.Tests;
public class LibsodiumCryptoProviderTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task LibsodiumProvider_SignsAndVerifiesEs256()
{
var provider = new LibsodiumCryptoProvider();
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using StellaOps.TestKit;
var parameters = ecdsa.ExportParameters(includePrivateParameters: true);
var signingKey = new CryptoSigningKey(

View File

@@ -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")]

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -11,7 +11,8 @@ 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))
@@ -20,6 +21,7 @@ public class Pkcs11GostProviderTests
}
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using StellaOps.TestKit;
var req = new CertificateRequest("CN=pkcs11.test", ecdsa, HashAlgorithmName.SHA256);
var cert = req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1));

View File

@@ -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();

View File

@@ -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();

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -30,7 +30,8 @@ public sealed class PostgresFixtureTests : IAsyncLifetime
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Initialize_CreatesSchema()
{
// Arrange
@@ -45,7 +46,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 +76,8 @@ public sealed class PostgresFixtureTests : IAsyncLifetime
count.Should().Be(0L);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Dispose_DropsSchema()
{
// Arrange
@@ -94,6 +97,7 @@ public sealed class PostgresFixtureTests : IAsyncLifetime
await using var cmd = new Npgsql.NpgsqlCommand(
"SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name = @name)",
conn);
using StellaOps.TestKit;
cmd.Parameters.AddWithValue("name", schemaName);
var exists = await cmd.ExecuteScalarAsync();
exists.Should().Be(false);

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -271,6 +271,7 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
app.MapPatch("/items/{itemId}", async ([FromRoute] string itemId, HttpContext context) =>
{
using var reader = new StreamReader(context.Request.Body);
using StellaOps.TestKit;
var bodyText = await reader.ReadToEndAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var request = JsonSerializer.Deserialize<PatchItemRequestDto>(bodyText, options);
@@ -295,7 +296,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
#region FromQuery Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FromQuery_StringParameter_BindsCorrectly()
{
// Arrange
@@ -310,7 +312,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 +329,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 +345,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 +364,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 +383,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 +402,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 +422,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
#region FromRoute Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FromRoute_SinglePathParameter_BindsCorrectly()
{
// Arrange
@@ -429,7 +438,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 +455,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 +471,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 +492,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
#region FromHeader Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FromHeader_AuthorizationHeader_BindsCorrectly()
{
// Arrange
@@ -498,7 +511,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 +534,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 +560,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 +577,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 +596,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 +615,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 +633,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 +655,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
#region FromForm Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FromForm_SimpleFormData_BindsCorrectly()
{
// Arrange
@@ -653,7 +674,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 +691,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 +712,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 +731,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 +755,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
#region HTTP Method Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HttpGet_ReturnsData()
{
// Arrange
@@ -745,7 +771,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 +788,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 +806,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 +825,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 +844,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 +865,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
#region Edge Cases
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SimpleEndpoint_NoParameters()
{
// Arrange
@@ -849,7 +881,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 +895,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 +909,8 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
Assert.Equal(404, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConcurrentRequests_AllSucceed()
{
// Arrange

View File

@@ -114,6 +114,7 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
app.MapPut("/api/items/{id}", async (string id, HttpContext context) =>
{
using var reader = new StreamReader(context.Request.Body);
using StellaOps.TestKit;
var body = await reader.ReadToEndAsync();
var data = JsonSerializer.Deserialize<JsonElement>(body);
var name = data.GetProperty("name").GetString();
@@ -124,7 +125,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 +137,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 +152,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 +169,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
#region Endpoint Discovery Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EndpointDiscovery_FindsAllEndpoints()
{
// Arrange
@@ -186,7 +191,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 +216,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EndpointDiscovery_IncludesServiceMetadata()
{
// Arrange
@@ -229,7 +236,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 +252,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 +269,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 +287,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 +306,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 +323,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 +337,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 +355,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 +380,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 +407,8 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
#region Response Capture Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Dispatch_ResponseContainsAllExpectedFields()
{
// Arrange
@@ -407,7 +424,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 +487,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 +507,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
Assert.Contains("ServiceName", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddStellaRouterBridge_MissingVersion_ThrowsInvalidOperation()
{
// Arrange
@@ -507,7 +527,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
Assert.Contains("Version", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddStellaRouterBridge_MissingRegion_ThrowsInvalidOperation()
{
// Arrange
@@ -526,7 +547,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
Assert.Contains("Region", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddStellaRouterBridge_ZeroTimeout_ThrowsInvalidOperation()
{
// Arrange
@@ -547,7 +569,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
Assert.Contains("DefaultTimeout", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddStellaRouterBridge_ExcessiveTimeout_ThrowsInvalidOperation()
{
// Arrange
@@ -568,7 +591,8 @@ public sealed class StellaRouterBridgeOptionsValidationTests
Assert.Contains("DefaultTimeout", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddStellaRouterBridge_ValidOptions_Succeeds()
{
// Arrange

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -7,7 +7,8 @@ public sealed class HeaderCollectionTests
{
#region Constructor Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Default_CreatesEmptyCollection()
{
// Arrange & Act
@@ -17,7 +18,8 @@ public sealed class HeaderCollectionTests
headers.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_WithKeyValuePairs_AddsAllHeaders()
{
// Arrange
@@ -35,7 +37,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 +59,8 @@ public sealed class HeaderCollectionTests
#region Empty Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Empty_IsSharedInstance()
{
// Arrange & Act
@@ -67,7 +71,8 @@ public sealed class HeaderCollectionTests
empty1.Should().BeSameAs(empty2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Empty_HasNoHeaders()
{
// Arrange & Act
@@ -81,7 +86,8 @@ public sealed class HeaderCollectionTests
#region Indexer Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Indexer_ExistingKey_ReturnsFirstValue()
{
// Arrange
@@ -95,7 +101,8 @@ public sealed class HeaderCollectionTests
value.Should().Be("application/json");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Indexer_MultipleValues_ReturnsFirstValue()
{
// Arrange
@@ -110,7 +117,8 @@ public sealed class HeaderCollectionTests
value.Should().Be("application/json");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Indexer_NonexistentKey_ReturnsNull()
{
// Arrange
@@ -123,7 +131,8 @@ public sealed class HeaderCollectionTests
value.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Indexer_IsCaseInsensitive()
{
// Arrange
@@ -140,7 +149,8 @@ public sealed class HeaderCollectionTests
#region Add Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Add_NewKey_AddsHeader()
{
// Arrange
@@ -153,7 +163,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 +178,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 +197,8 @@ public sealed class HeaderCollectionTests
#region Set Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Set_NewKey_AddsHeader()
{
// Arrange
@@ -198,7 +211,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 +231,8 @@ public sealed class HeaderCollectionTests
#region GetValues Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetValues_ExistingKey_ReturnsAllValues()
{
// Arrange
@@ -233,7 +248,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 +262,8 @@ public sealed class HeaderCollectionTests
values.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetValues_IsCaseInsensitive()
{
// Arrange
@@ -262,7 +279,8 @@ public sealed class HeaderCollectionTests
#region TryGetValue Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryGetValue_ExistingKey_ReturnsTrueAndValue()
{
// Arrange
@@ -277,7 +295,8 @@ public sealed class HeaderCollectionTests
value.Should().Be("application/json");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryGetValue_NonexistentKey_ReturnsFalse()
{
// Arrange
@@ -291,7 +310,8 @@ public sealed class HeaderCollectionTests
value.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryGetValue_IsCaseInsensitive()
{
// Arrange
@@ -310,7 +330,8 @@ public sealed class HeaderCollectionTests
#region ContainsKey Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ContainsKey_ExistingKey_ReturnsTrue()
{
// Arrange
@@ -321,7 +342,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 +353,8 @@ public sealed class HeaderCollectionTests
headers.ContainsKey("X-Missing").Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ContainsKey_IsCaseInsensitive()
{
// Arrange
@@ -347,7 +370,8 @@ public sealed class HeaderCollectionTests
#region Enumeration Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetEnumerator_EnumeratesAllHeaderValues()
{
// Arrange
@@ -366,7 +390,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

View File

@@ -22,7 +22,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
#region Track Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Track_NewRequest_ReturnsNonCancelledToken()
{
// Arrange
@@ -35,7 +36,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
token.IsCancellationRequested.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Track_NewRequest_IncreasesCount()
{
// Arrange
@@ -48,7 +50,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 +63,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 +79,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 +97,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
#region Cancel Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Cancel_TrackedRequest_CancelsToken()
{
// Arrange
@@ -107,7 +113,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
token.IsCancellationRequested.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Cancel_UntrackedRequest_ReturnsFalse()
{
// Arrange
@@ -120,7 +127,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
result.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Cancel_WithNullReason_Works()
{
// Arrange
@@ -134,7 +142,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Cancel_CompletedRequest_ReturnsFalse()
{
// Arrange
@@ -153,7 +162,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
#region Complete Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Complete_TrackedRequest_RemovesFromTracking()
{
// Arrange
@@ -167,7 +177,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 +191,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
action.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Complete_MultipleCompletions_DoesNotThrow()
{
// Arrange
@@ -202,7 +214,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
#region CancelAll Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CancelAll_CancelsAllTrackedRequests()
{
// Arrange
@@ -219,7 +232,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
token3.IsCancellationRequested.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CancelAll_ClearsTrackedRequests()
{
// Arrange
@@ -233,7 +247,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 +262,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
#region Dispose Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CancelsAllRequests()
{
// Arrange
@@ -260,7 +276,8 @@ public sealed class InflightRequestTrackerTests : IDisposable
token.IsCancellationRequested.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CanBeCalledMultipleTimes()
{
// Arrange & Act
@@ -279,17 +296,20 @@ public sealed class InflightRequestTrackerTests : IDisposable
#region Count Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Count_InitiallyZero()
{
// Arrange - use a fresh tracker
using var tracker = new InflightRequestTracker(NullLogger<InflightRequestTracker>.Instance);
using StellaOps.TestKit;
// Assert
tracker.Count.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Count_ReflectsActiveRequests()
{
// Arrange

View File

@@ -7,7 +7,8 @@ public sealed class RawRequestContextTests
{
#region Default Values Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Method_DefaultsToEmptyString()
{
// Arrange & Act
@@ -17,7 +18,8 @@ public sealed class RawRequestContextTests
context.Method.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Path_DefaultsToEmptyString()
{
// Arrange & Act
@@ -27,7 +29,8 @@ public sealed class RawRequestContextTests
context.Path.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_PathParameters_DefaultsToEmptyDictionary()
{
// Arrange & Act
@@ -38,7 +41,8 @@ public sealed class RawRequestContextTests
context.PathParameters.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Headers_DefaultsToEmptyCollection()
{
// Arrange & Act
@@ -48,7 +52,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 +63,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 +74,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 +89,8 @@ public sealed class RawRequestContextTests
#region Property Initialization Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Method_CanBeInitialized()
{
// Arrange & Act
@@ -92,7 +100,8 @@ public sealed class RawRequestContextTests
context.Method.Should().Be("POST");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Path_CanBeInitialized()
{
// Arrange & Act
@@ -102,7 +111,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 +131,8 @@ public sealed class RawRequestContextTests
context.PathParameters["action"].Should().Be("update");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Headers_CanBeInitialized()
{
// Arrange
@@ -137,7 +148,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 +162,8 @@ public sealed class RawRequestContextTests
context.Body.Should().BeSameAs(body);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CancellationToken_CanBeInitialized()
{
// Arrange
@@ -163,7 +176,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 +191,8 @@ public sealed class RawRequestContextTests
#region Complete Context Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CompleteContext_AllPropertiesSet_Works()
{
// Arrange
@@ -210,11 +225,13 @@ public sealed class RawRequestContextTests
context.CorrelationId.Should().Be("corr-789");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Context_WithCancelledToken_HasCancellationRequested()
{
// Arrange
using var cts = new CancellationTokenSource();
using StellaOps.TestKit;
cts.Cancel();
// Act
@@ -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

View File

@@ -9,7 +9,8 @@ public sealed class RawResponseTests
{
#region Default Values Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_StatusCode_DefaultsTo200()
{
// Arrange & Act
@@ -19,7 +20,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 +31,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 +46,8 @@ public sealed class RawResponseTests
#region Ok Factory Method Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Ok_WithStream_CreatesOkResponse()
{
// Arrange
@@ -57,7 +61,8 @@ public sealed class RawResponseTests
response.Body.Should().BeSameAs(stream);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Ok_WithByteArray_CreatesOkResponse()
{
// Arrange
@@ -72,7 +77,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 +93,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 +109,8 @@ public sealed class RawResponseTests
#region NoContent Factory Method Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NoContent_Creates204Response()
{
// Arrange & Act
@@ -112,7 +120,8 @@ public sealed class RawResponseTests
response.StatusCode.Should().Be(204);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NoContent_HasDefaultHeaders()
{
// Arrange & Act
@@ -122,7 +131,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 +146,8 @@ public sealed class RawResponseTests
#region BadRequest Factory Method Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BadRequest_Creates400Response()
{
// Arrange & Act
@@ -146,7 +157,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 +169,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 +181,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 +196,8 @@ public sealed class RawResponseTests
#region NotFound Factory Method Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NotFound_Creates404Response()
{
// Arrange & Act
@@ -192,7 +207,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 +219,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 +235,8 @@ public sealed class RawResponseTests
#region InternalError Factory Method Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InternalError_Creates500Response()
{
// Arrange & Act
@@ -228,7 +246,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 +258,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 +274,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 +292,8 @@ public sealed class RawResponseTests
response.StatusCode.Should().Be(statusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Error_SetsCorrectContentType()
{
// Arrange & Act
@@ -281,7 +303,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 +318,8 @@ public sealed class RawResponseTests
reader.ReadToEnd().Should().Be(message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Error_WithUnicodeMessage_EncodesCorrectly()
{
// Arrange
@@ -306,6 +330,7 @@ public sealed class RawResponseTests
// Assert
using var reader = new StreamReader(response.Body, Encoding.UTF8);
using StellaOps.TestKit;
reader.ReadToEnd().Should().Be(message);
}
@@ -313,7 +338,8 @@ public sealed class RawResponseTests
#region Property Initialization Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StatusCode_CanBeInitialized()
{
// Arrange & Act
@@ -323,7 +349,8 @@ public sealed class RawResponseTests
response.StatusCode.Should().Be(201);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Headers_CanBeInitialized()
{
// Arrange
@@ -337,7 +364,8 @@ public sealed class RawResponseTests
response.Headers["X-Custom"].Should().Be("value");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Body_CanBeInitialized()
{
// Arrange

View File

@@ -51,7 +51,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region Constructor Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_InitializesCorrectly()
{
// Act
@@ -68,7 +69,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region CurrentStatus Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CurrentStatus_CanBeSet()
{
// Arrange
@@ -81,7 +83,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 +105,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region InFlightRequestCount Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InFlightRequestCount_CanBeSet()
{
// Arrange
@@ -119,7 +123,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region ErrorRate Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ErrorRate_CanBeSet()
{
// Arrange
@@ -136,7 +141,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region StartAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_DiscoversEndpoints()
{
// Arrange
@@ -157,7 +163,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 +187,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 +218,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 +237,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region StopAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StopAsync_ClearsConnections()
{
// Arrange
@@ -252,7 +262,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region Heartbeat Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Heartbeat_SendsViaTransport()
{
// Arrange
@@ -275,7 +286,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
Times.AtLeastOnce);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Heartbeat_IncludesCurrentMetrics()
{
// Arrange
@@ -286,6 +298,7 @@ public sealed class RouterConnectionManagerTests : IDisposable
TransportType = TransportType.InMemory
});
using var manager = CreateManager();
using StellaOps.TestKit;
manager.CurrentStatus = InstanceHealthStatus.Degraded;
manager.InFlightRequestCount = 10;
manager.ErrorRate = 0.05;
@@ -311,7 +324,8 @@ public sealed class RouterConnectionManagerTests : IDisposable
#region Dispose Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CanBeCalledMultipleTimes()
{
// Arrange

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -18,7 +18,8 @@ public sealed class EvidenceChunkerTests
_chunker = new EvidenceChunker(_options);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ChunkAsync_ShouldSplitEvidenceIntoMultipleChunks_WhenLargerThanChunkSize()
{
// Arrange
@@ -44,7 +45,8 @@ public sealed class EvidenceChunkerTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ChunkAsync_ShouldCreateSingleChunk_WhenSmallerThanChunkSize()
{
// Arrange
@@ -62,7 +64,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 +81,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 +99,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 +117,8 @@ public sealed class EvidenceChunkerTests
reassembled.Should().BeEquivalentTo(original);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReassembleAsync_ShouldThrow_WhenMerkleRootMismatch()
{
// Arrange
@@ -128,7 +134,8 @@ public sealed class EvidenceChunkerTests
.WithMessage("*Merkle root mismatch*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReassembleAsync_ShouldThrow_WhenChunkCorrupted()
{
// Arrange
@@ -151,7 +158,8 @@ public sealed class EvidenceChunkerTests
.WithMessage("*verification failed*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerifyChunk_ShouldReturnTrue_WhenChunkValid()
{
// Arrange
@@ -175,7 +183,8 @@ public sealed class EvidenceChunkerTests
_chunker.VerifyChunk(chunk).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerifyChunk_ShouldReturnFalse_WhenHashMismatch()
{
// Arrange
@@ -195,7 +204,8 @@ public sealed class EvidenceChunkerTests
_chunker.VerifyChunk(chunk).Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeMerkleRoot_ShouldReturnSameResult_ForSameInput()
{
// Arrange
@@ -210,7 +220,8 @@ public sealed class EvidenceChunkerTests
root1.Should().StartWith("sha256:");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeMerkleRoot_ShouldHandleSingleHash()
{
// Arrange
@@ -223,7 +234,8 @@ public sealed class EvidenceChunkerTests
root.Should().Be("sha256:aabbccdd");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeMerkleRoot_ShouldHandleOddNumberOfHashes()
{
// Arrange
@@ -237,13 +249,15 @@ public sealed class EvidenceChunkerTests
root.Should().StartWith("sha256:");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ChunkStreamAsync_ShouldYieldChunksInOrder()
{
// Arrange
var evidence = new byte[200];
Random.Shared.NextBytes(evidence);
using var stream = new MemoryStream(evidence);
using StellaOps.TestKit;
const string contentType = "application/octet-stream";
// Act
@@ -261,7 +275,8 @@ public sealed class EvidenceChunkerTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Roundtrip_ShouldPreserveDataIntegrity()
{
// Arrange - use realistic chunk size

View File

@@ -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

View File

@@ -113,7 +113,8 @@ public sealed class MinimalProofExporterTests
#region Export Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExportAsync_LiteDensity_ReturnsDigestAndManifestOnly()
{
// Arrange
@@ -132,7 +133,8 @@ public sealed class MinimalProofExporterTests
bundle.Signature.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExportAsync_StandardDensity_ReturnsFirstNChunks()
{
// Arrange
@@ -160,7 +162,8 @@ public sealed class MinimalProofExporterTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExportAsync_StrictDensity_ReturnsAllChunks()
{
// Arrange
@@ -177,7 +180,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 +194,8 @@ public sealed class MinimalProofExporterTests
_exporter.ExportAsync("sha256:notfound", options));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExportAsJsonAsync_ReturnsValidJson()
{
// Arrange
@@ -207,7 +212,8 @@ public sealed class MinimalProofExporterTests
bundle!.BundleVersion.Should().Be("v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExportToStreamAsync_WritesToStream()
{
// Arrange
@@ -215,6 +221,7 @@ public sealed class MinimalProofExporterTests
var options = new MinimalProofExportOptions { Density = ProofDensity.Lite };
using var stream = new MemoryStream();
using StellaOps.TestKit;
// Act
await _exporter.ExportToStreamAsync(_testEntry.VeriKey, options, stream);
@@ -229,7 +236,8 @@ public sealed class MinimalProofExporterTests
#region Import Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ImportAsync_ValidBundle_StoresChunks()
{
// Arrange
@@ -255,7 +263,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 +284,8 @@ public sealed class MinimalProofExporterTests
#region Verify Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyAsync_ValidBundle_ReturnsValid()
{
// Arrange
@@ -294,7 +304,8 @@ public sealed class MinimalProofExporterTests
verification.FailedChunkIndices.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyAsync_CorruptedChunk_ReportsFailure()
{
// Arrange
@@ -315,7 +326,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 +350,8 @@ public sealed class MinimalProofExporterTests
#region EstimateSize Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EstimateExportSizeAsync_LiteDensity_ReturnsBaseSize()
{
// Arrange
@@ -351,7 +364,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 +378,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 +397,8 @@ public sealed class MinimalProofExporterTests
#region Signing Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExportAsync_SigningWithoutSigner_ThrowsException()
{
// Arrange
@@ -398,7 +414,8 @@ public sealed class MinimalProofExporterTests
_exporter.ExportAsync(_testEntry.VeriKey, options));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExportAsync_WithSigner_SignsBundle()
{
// Arrange

View File

@@ -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

View File

@@ -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

View File

@@ -47,7 +47,8 @@ public class WriteBehindQueueTests
HitCount = 0
};
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EnqueueAsync_SingleEntry_UpdatesMetrics()
{
// Arrange
@@ -68,7 +69,8 @@ public class WriteBehindQueueTests
metrics.CurrentQueueDepth.Should().Be(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EnqueueAsync_MultipleEntries_TracksQueueDepth()
{
// Arrange
@@ -90,7 +92,8 @@ public class WriteBehindQueueTests
metrics.CurrentQueueDepth.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetMetrics_InitialState_AllZeros()
{
// Arrange
@@ -112,7 +115,8 @@ public class WriteBehindQueueTests
metrics.CurrentQueueDepth.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ProcessBatch_SuccessfulPersist_UpdatesPersistMetrics()
{
// Arrange
@@ -133,6 +137,7 @@ public class WriteBehindQueueTests
// Act - Start the queue and let it process
using var cts = new CancellationTokenSource();
using StellaOps.TestKit;
var task = queue.StartAsync(cts.Token);
// Wait for processing
@@ -147,7 +152,8 @@ public class WriteBehindQueueTests
metrics.TotalBatches.Should().BeGreaterThanOrEqualTo(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void WriteBehindMetrics_Timestamp_IsRecent()
{
// Arrange
@@ -205,7 +211,8 @@ public class ProvcacheServiceStorageIntegrationTests
HitCount = 0
};
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_ThenGetAsync_ReturnsEntry()
{
// Arrange
@@ -239,7 +246,8 @@ public class ProvcacheServiceStorageIntegrationTests
result.Source.Should().Be("valkey");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_CacheMissWithDbHit_BackfillsCache()
{
// Arrange
@@ -274,7 +282,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 +312,8 @@ public class ProvcacheServiceStorageIntegrationTests
result.Entry.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetOrComputeAsync_CacheHit_DoesNotCallFactory()
{
// Arrange
@@ -335,7 +345,8 @@ public class ProvcacheServiceStorageIntegrationTests
result.VeriKey.Should().Be(veriKey);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetOrComputeAsync_CacheMiss_CallsFactoryAndStores()
{
// Arrange
@@ -374,7 +385,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 +415,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 +439,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

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -7,7 +7,8 @@ public sealed class PathMatcherTests
{
#region Constructor Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsTemplate()
{
// Arrange & Act
@@ -17,7 +18,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 +29,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 +45,8 @@ public sealed class PathMatcherTests
#region IsMatch Tests - Exact Paths
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsMatch_ExactPath_ReturnsTrue()
{
// Arrange
@@ -52,7 +56,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 +67,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 +78,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 +89,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 +100,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 +115,8 @@ public sealed class PathMatcherTests
#region IsMatch Tests - Case Sensitivity
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsMatch_CaseInsensitive_MatchesMixedCase()
{
// Arrange
@@ -118,7 +128,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 +145,8 @@ public sealed class PathMatcherTests
#region TryMatch Tests - Single Parameter
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_SingleParameter_ReturnsTrue()
{
// Arrange
@@ -147,7 +159,8 @@ public sealed class PathMatcherTests
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_SingleParameter_ExtractsParameter()
{
// Arrange
@@ -161,7 +174,8 @@ public sealed class PathMatcherTests
parameters["id"].Should().Be("123");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_SingleParameter_ExtractsGuidParameter()
{
// Arrange
@@ -175,7 +189,8 @@ public sealed class PathMatcherTests
parameters["userId"].Should().Be(guid);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_SingleParameter_ExtractsStringParameter()
{
// Arrange
@@ -192,7 +207,8 @@ public sealed class PathMatcherTests
#region TryMatch Tests - Multiple Parameters
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_MultipleParameters_ReturnsTrue()
{
// Arrange
@@ -205,7 +221,8 @@ public sealed class PathMatcherTests
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_MultipleParameters_ExtractsAllParameters()
{
// Arrange
@@ -221,7 +238,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 +259,8 @@ public sealed class PathMatcherTests
#region TryMatch Tests - Non-Matching
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_NonMatchingPath_ReturnsFalse()
{
// Arrange
@@ -255,7 +274,8 @@ public sealed class PathMatcherTests
parameters.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_MissingParameter_ReturnsFalse()
{
// Arrange
@@ -268,7 +288,8 @@ public sealed class PathMatcherTests
result.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_ExtraSegment_ReturnsFalse()
{
// Arrange
@@ -285,7 +306,8 @@ public sealed class PathMatcherTests
#region TryMatch Tests - Path Normalization
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_TrailingSlash_Matches()
{
// Arrange
@@ -299,7 +321,8 @@ public sealed class PathMatcherTests
parameters["id"].Should().Be("123");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_NoLeadingSlash_Matches()
{
// Arrange
@@ -317,7 +340,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 +356,8 @@ public sealed class PathMatcherTests
parameters["id"].Should().Be("123");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_ParameterWithGuidConstraint_ExtractsParameterName()
{
// Arrange
@@ -350,7 +375,8 @@ public sealed class PathMatcherTests
#region Edge Cases
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_RootPath_Matches()
{
// Arrange
@@ -364,7 +390,8 @@ public sealed class PathMatcherTests
parameters.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_SingleSegmentWithParameter_Matches()
{
// Arrange
@@ -378,7 +405,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 +419,8 @@ public sealed class PathMatcherTests
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_ParameterWithHyphen_Extracts()
{
// Arrange
@@ -405,7 +434,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 +448,8 @@ public sealed class PathMatcherTests
parameters.Should().ContainKey("user_id");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_SpecialCharactersInPath_Matches()
{
// Arrange
@@ -431,7 +462,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 +476,8 @@ public sealed class PathMatcherTests
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryMatch_ComplexRealWorldPath_ExtractsAllParameters()
{
// Arrange

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,8 @@ namespace StellaOps.Router.Config.Tests;
/// </summary>
public sealed class ConfigChangedEventArgsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsPreviousConfig()
{
// Arrange
@@ -19,7 +20,8 @@ public sealed class ConfigChangedEventArgsTests
args.Previous.Should().BeSameAs(previous);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsCurrentConfig()
{
// Arrange
@@ -33,7 +35,8 @@ public sealed class ConfigChangedEventArgsTests
args.Current.Should().BeSameAs(current);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsChangedAtToCurrentTime()
{
// Arrange
@@ -50,7 +53,8 @@ public sealed class ConfigChangedEventArgsTests
args.ChangedAt.Should().BeOnOrBefore(afterCreate);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_DifferentConfigs_BothAccessible()
{
// Arrange
@@ -71,7 +75,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

View File

@@ -7,7 +7,8 @@ public sealed class ConfigValidationResultTests
{
#region Default Values Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Errors_DefaultsToEmptyList()
{
// Arrange & Act
@@ -18,7 +19,8 @@ public sealed class ConfigValidationResultTests
result.Errors.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Warnings_DefaultsToEmptyList()
{
// Arrange & Act
@@ -33,7 +35,8 @@ public sealed class ConfigValidationResultTests
#region IsValid Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsValid_NoErrors_ReturnsTrue()
{
// Arrange
@@ -43,7 +46,8 @@ public sealed class ConfigValidationResultTests
result.IsValid.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsValid_WithErrors_ReturnsFalse()
{
// Arrange
@@ -54,7 +58,8 @@ public sealed class ConfigValidationResultTests
result.IsValid.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsValid_WithOnlyWarnings_ReturnsTrue()
{
// Arrange
@@ -65,7 +70,8 @@ public sealed class ConfigValidationResultTests
result.IsValid.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsValid_WithErrorsAndWarnings_ReturnsFalse()
{
// Arrange
@@ -77,7 +83,8 @@ public sealed class ConfigValidationResultTests
result.IsValid.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsValid_MultipleErrors_ReturnsFalse()
{
// Arrange
@@ -95,7 +102,8 @@ public sealed class ConfigValidationResultTests
#region Static Success Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Success_ReturnsValidResult()
{
// Arrange & Act
@@ -107,7 +115,8 @@ public sealed class ConfigValidationResultTests
result.Warnings.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Success_ReturnsNewInstance()
{
// Arrange & Act
@@ -122,7 +131,8 @@ public sealed class ConfigValidationResultTests
#region Errors Collection Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Errors_CanBeModified()
{
// Arrange
@@ -138,7 +148,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 +167,8 @@ public sealed class ConfigValidationResultTests
#region Warnings Collection Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Warnings_CanBeModified()
{
// Arrange
@@ -172,7 +184,8 @@ public sealed class ConfigValidationResultTests
result.Warnings.Should().Contain("Warning 2");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Warnings_CanBeInitialized()
{
// Arrange & Act

View File

@@ -7,7 +7,8 @@ public sealed class RouterConfigOptionsTests
{
#region Default Values Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_ConfigPath_DefaultsToNull()
{
// Arrange & Act
@@ -17,7 +18,8 @@ public sealed class RouterConfigOptionsTests
options.ConfigPath.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_EnvironmentVariablePrefix_DefaultsToStellaOpsRouter()
{
// Arrange & Act
@@ -27,7 +29,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 +40,8 @@ public sealed class RouterConfigOptionsTests
options.EnableHotReload.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_DebounceInterval_DefaultsTo500Milliseconds()
{
// Arrange & Act
@@ -47,7 +51,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 +62,8 @@ public sealed class RouterConfigOptionsTests
options.ThrowOnValidationError.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_ConfigurationSection_DefaultsToRouter()
{
// Arrange & Act
@@ -71,7 +77,8 @@ public sealed class RouterConfigOptionsTests
#region Property Assignment Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ConfigPath_CanBeSet()
{
// Arrange
@@ -84,7 +91,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 +105,8 @@ public sealed class RouterConfigOptionsTests
options.EnvironmentVariablePrefix.Should().Be("CUSTOM_PREFIX_");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnableHotReload_CanBeSet()
{
// Arrange
@@ -110,7 +119,8 @@ public sealed class RouterConfigOptionsTests
options.EnableHotReload.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DebounceInterval_CanBeSet()
{
// Arrange
@@ -123,7 +133,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 +147,8 @@ public sealed class RouterConfigOptionsTests
options.ThrowOnValidationError.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ConfigurationSection_CanBeSet()
{
// Arrange

View File

@@ -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

View File

@@ -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

View File

@@ -7,7 +7,8 @@ public sealed class RoutingOptionsTests
{
#region Default Values Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_LocalRegion_DefaultsToDefault()
{
// Arrange & Act
@@ -17,7 +18,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 +30,8 @@ public sealed class RoutingOptionsTests
options.NeighborRegions.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_TieBreaker_DefaultsToRoundRobin()
{
// Arrange & Act
@@ -38,7 +41,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 +52,8 @@ public sealed class RoutingOptionsTests
options.PreferLocalRegion.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_DefaultTimeout_DefaultsTo30Seconds()
{
// Arrange & Act
@@ -62,7 +67,8 @@ public sealed class RoutingOptionsTests
#region Property Assignment Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LocalRegion_CanBeSet()
{
// Arrange
@@ -75,7 +81,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 +97,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 +115,8 @@ public sealed class RoutingOptionsTests
options.TieBreaker.Should().Be(strategy);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PreferLocalRegion_CanBeSet()
{
// Arrange
@@ -120,7 +129,8 @@ public sealed class RoutingOptionsTests
options.PreferLocalRegion.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultTimeout_CanBeSet()
{
// Arrange
@@ -137,7 +147,8 @@ public sealed class RoutingOptionsTests
#region TieBreakerStrategy Enum Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TieBreakerStrategy_HasFourValues()
{
// Arrange & Act
@@ -147,28 +158,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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -36,7 +36,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 +64,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 +94,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 +134,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 +186,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 +228,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 +268,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 +304,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 +342,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
@@ -343,6 +352,7 @@ public sealed class MessageOrderingTests : IAsyncLifetime, IDisposable
for (int run = 0; run < 3; run++)
{
using var channel = new InMemoryChannel($"determinism-{run}", bufferSize: 100);
using StellaOps.TestKit;
var received = new List<int>();
// Same sequence each run

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -21,7 +21,8 @@ public sealed class ServiceRegistrationIntegrationTests
#region Core Services Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_MicroserviceOptionsAreRegistered()
{
// Act
@@ -33,7 +34,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 +45,8 @@ public sealed class ServiceRegistrationIntegrationTests
registry.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_ConnectionManagerIsRegistered()
{
// Act
@@ -53,7 +56,8 @@ public sealed class ServiceRegistrationIntegrationTests
connectionManager.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_RequestDispatcherIsRegistered()
{
// Act
@@ -63,7 +67,8 @@ public sealed class ServiceRegistrationIntegrationTests
dispatcher.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_EndpointDiscoveryServiceIsRegistered()
{
// Act
@@ -77,7 +82,8 @@ public sealed class ServiceRegistrationIntegrationTests
#region Transport Services Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_TransportClientIsRegistered()
{
// Act
@@ -88,7 +94,8 @@ public sealed class ServiceRegistrationIntegrationTests
client.Should().BeOfType<InMemoryTransportClient>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_TransportServerIsRegistered()
{
// Act
@@ -99,7 +106,8 @@ public sealed class ServiceRegistrationIntegrationTests
server.Should().BeOfType<InMemoryTransportServer>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_InMemoryConnectionRegistryIsRegistered()
{
// Act
@@ -113,7 +121,8 @@ public sealed class ServiceRegistrationIntegrationTests
#region Endpoint Handler Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_EndpointHandlersAreRegistered()
{
// Act
@@ -128,13 +137,15 @@ public sealed class ServiceRegistrationIntegrationTests
createUserEndpoint.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_EndpointHandlersAreScopedInstances()
{
// Act
using var scope1 = _fixture.Services.CreateScope();
using var scope2 = _fixture.Services.CreateScope();
using StellaOps.TestKit;
var echo1 = scope1.ServiceProvider.GetService<EchoEndpoint>();
var echo2 = scope2.ServiceProvider.GetService<EchoEndpoint>();
@@ -146,7 +157,8 @@ public sealed class ServiceRegistrationIntegrationTests
#region Singleton Services Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Services_SingletonServicesAreSameInstance()
{
// Act

View File

@@ -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

View File

@@ -12,7 +12,8 @@ public sealed class BackpressureTests
{
#region Bounded Channel Backpressure
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_BoundedChannel_BlocksProducer()
{
// Arrange
@@ -32,7 +33,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 +58,8 @@ public sealed class BackpressureTests
canWriteAfterDrain.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_DrainAll_AllowsFullRefill()
{
// Arrange
@@ -94,7 +97,8 @@ public sealed class BackpressureTests
#region Slow Consumer Scenarios
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_SlowConsumer_ProducerWaits()
{
// Arrange
@@ -134,7 +138,8 @@ public sealed class BackpressureTests
consumed.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_SlowConsumer_NoMessageDropped()
{
// Arrange
@@ -177,7 +182,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 +209,8 @@ public sealed class BackpressureTests
writeTask.IsCompleted.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_WaitToWriteAsync_ReturnsCorrectly()
{
// Arrange
@@ -231,7 +238,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 +262,8 @@ public sealed class BackpressureTests
readCount.Should().Be(messageCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_UnboundedChannel_HighThroughput()
{
// Arrange
@@ -291,7 +300,8 @@ public sealed class BackpressureTests
#region Bidirectional Backpressure
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_BothDirections_Independent()
{
// Arrange
@@ -326,7 +336,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 +365,8 @@ public sealed class BackpressureTests
drained.Should().HaveCount(itemCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_CompleteWithWaitingWriter_Fails()
{
// Arrange
@@ -380,7 +392,8 @@ public sealed class BackpressureTests
#region Cancellation During Backpressure
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_CancelledDuringWait_Throws()
{
// Arrange
@@ -402,12 +415,14 @@ public sealed class BackpressureTests
await Assert.ThrowsAsync<OperationCanceledException>(() => writeTask);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_AlreadyCancelled_ThrowsImmediately()
{
// Arrange
using var channel = new InMemoryChannel("bp-precancelled");
using var cts = new CancellationTokenSource();
using StellaOps.TestKit;
await cts.CancelAsync();
// Act & Assert

View File

@@ -32,7 +32,8 @@ public sealed class InMemoryChannelTests
#region Constructor Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsConnectionId()
{
// Arrange & Act
@@ -42,7 +43,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 +55,8 @@ public sealed class InMemoryChannelTests
channel.ToGateway.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_CreatesBoundedChannels_WhenBufferSizeSpecified()
{
// Arrange & Act
@@ -64,7 +67,8 @@ public sealed class InMemoryChannelTests
channel.ToGateway.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_CreatesLifetimeToken()
{
// Arrange & Act
@@ -75,7 +79,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 +90,8 @@ public sealed class InMemoryChannelTests
channel.Instance.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_State_IsInitiallyNull()
{
// Arrange & Act
@@ -99,7 +105,8 @@ public sealed class InMemoryChannelTests
#region Property Assignment Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Instance_CanBeSet()
{
// Arrange
@@ -114,7 +121,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 +140,8 @@ public sealed class InMemoryChannelTests
#region Channel Communication Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ToMicroservice_CanWriteAndRead()
{
// Arrange
@@ -152,7 +161,8 @@ public sealed class InMemoryChannelTests
received.Should().BeSameAs(frame);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ToGateway_CanWriteAndRead()
{
// Arrange
@@ -172,7 +182,8 @@ public sealed class InMemoryChannelTests
received.Should().BeSameAs(frame);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Channel_MultipleFrames_DeliveredInOrder()
{
// Arrange
@@ -200,7 +211,8 @@ public sealed class InMemoryChannelTests
#region Bounded Channel Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BoundedChannel_AcceptsUpToBufferSize()
{
// Arrange
@@ -221,7 +233,8 @@ public sealed class InMemoryChannelTests
#region Dispose Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CancelsLifetimeToken()
{
// Arrange
@@ -234,7 +247,8 @@ public sealed class InMemoryChannelTests
channel.LifetimeToken.IsCancellationRequested.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CompletesChannels()
{
// Arrange
@@ -248,7 +262,8 @@ public sealed class InMemoryChannelTests
channel.ToGateway.Reader.Completion.IsCompleted.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CanBeCalledMultipleTimes()
{
// Arrange
@@ -266,12 +281,14 @@ public sealed class InMemoryChannelTests
action.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Dispose_ReaderDetectsCompletion()
{
// Arrange
using var channel = new InMemoryChannel("conn-123");
using StellaOps.TestKit;
// Start reader task
var readerTask = Task.Run(async () =>
{

View File

@@ -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

View File

@@ -13,7 +13,8 @@ public sealed class InMemoryTransportComplianceTests
{
#region Roundtrip Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Roundtrip_RequestResponse_PreservesAllData()
{
// Arrange
@@ -78,7 +79,8 @@ public sealed class InMemoryTransportComplianceTests
receivedResponse.HasMoreChunks.Should().Be(response.HasMoreChunks);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Roundtrip_BinaryPayload_PreservesAllBytes()
{
// Arrange
@@ -106,7 +108,8 @@ public sealed class InMemoryTransportComplianceTests
restored!.Payload.ToArray().Should().BeEquivalentTo(binaryPayload);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Roundtrip_LargePayload_TransfersSuccessfully()
{
// Arrange
@@ -135,7 +138,8 @@ public sealed class InMemoryTransportComplianceTests
restored!.Payload.ToArray().Should().BeEquivalentTo(largePayload);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(100)]
@@ -175,7 +179,8 @@ public sealed class InMemoryTransportComplianceTests
#region Ordering Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ordering_MultipleMessages_FifoPreserved()
{
// Arrange
@@ -214,7 +219,8 @@ public sealed class InMemoryTransportComplianceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ordering_InterleavedRequestResponse_MaintainsCorrelation()
{
// Arrange
@@ -267,7 +273,8 @@ public sealed class InMemoryTransportComplianceTests
#region Backpressure Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_BoundedChannel_BlocksWhenFull()
{
// Arrange
@@ -297,7 +304,8 @@ public sealed class InMemoryTransportComplianceTests
canWrite.Should().BeFalse("Channel should be at capacity");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_DrainAndResume_Works()
{
// Arrange
@@ -338,7 +346,8 @@ public sealed class InMemoryTransportComplianceTests
canWriteAfterDrain.Should().BeTrue("Should be able to write after draining");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_SlowConsumer_ProducerWaits()
{
// Arrange
@@ -382,7 +391,8 @@ public sealed class InMemoryTransportComplianceTests
readCount.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Backpressure_UnboundedChannel_NeverBlocks()
{
// Arrange
@@ -414,7 +424,8 @@ public sealed class InMemoryTransportComplianceTests
#region Connection Lifecycle Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Lifecycle_ChannelDispose_StopsReaders()
{
// Arrange
@@ -448,7 +459,8 @@ public sealed class InMemoryTransportComplianceTests
readerCancelled.Should().BeTrue("Reader should be cancelled on dispose");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Lifecycle_LifetimeToken_CancelledOnDispose()
{
// Arrange
@@ -464,7 +476,8 @@ public sealed class InMemoryTransportComplianceTests
token.IsCancellationRequested.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Lifecycle_PendingWritesDrained_OnGracefulClose()
{
// Arrange
@@ -498,7 +511,8 @@ public sealed class InMemoryTransportComplianceTests
#region Concurrent Access Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Concurrent_MultipleProducers_AllMessagesDelivered()
{
// Arrange
@@ -538,7 +552,8 @@ public sealed class InMemoryTransportComplianceTests
received.Distinct().Should().HaveCount(expectedTotal, "No duplicates");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Concurrent_MultipleConsumers_NoMessageLost()
{
// Arrange
@@ -587,7 +602,8 @@ public sealed class InMemoryTransportComplianceTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameInputs_SameOutputs()
{
// Run same test multiple times - should always produce same results
@@ -596,6 +612,7 @@ public sealed class InMemoryTransportComplianceTests
// Arrange
using var channel = new InMemoryChannel($"conn-det-{run}");
using StellaOps.TestKit;
var request = new RequestFrame
{
RequestId = "deterministic-req",

View File

@@ -7,7 +7,8 @@ public sealed class InMemoryTransportOptionsTests
{
#region Default Values Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_DefaultTimeout_Is30Seconds()
{
// Arrange & Act
@@ -17,7 +18,8 @@ public sealed class InMemoryTransportOptionsTests
options.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(30));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SimulatedLatency_IsZero()
{
// Arrange & Act
@@ -27,7 +29,8 @@ public sealed class InMemoryTransportOptionsTests
options.SimulatedLatency.Should().Be(TimeSpan.Zero);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_ChannelBufferSize_IsZero()
{
// Arrange & Act
@@ -37,7 +40,8 @@ public sealed class InMemoryTransportOptionsTests
options.ChannelBufferSize.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_HeartbeatInterval_Is10Seconds()
{
// Arrange & Act
@@ -47,7 +51,8 @@ public sealed class InMemoryTransportOptionsTests
options.HeartbeatInterval.Should().Be(TimeSpan.FromSeconds(10));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_HeartbeatTimeout_Is30Seconds()
{
// Arrange & Act
@@ -61,7 +66,8 @@ public sealed class InMemoryTransportOptionsTests
#region Property Assignment Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultTimeout_CanBeSet()
{
// Arrange
@@ -74,7 +80,8 @@ public sealed class InMemoryTransportOptionsTests
options.DefaultTimeout.Should().Be(TimeSpan.FromMinutes(5));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SimulatedLatency_CanBeSet()
{
// Arrange
@@ -87,7 +94,8 @@ public sealed class InMemoryTransportOptionsTests
options.SimulatedLatency.Should().Be(TimeSpan.FromMilliseconds(100));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0)]
[InlineData(100)]
[InlineData(1000)]
@@ -104,7 +112,8 @@ public sealed class InMemoryTransportOptionsTests
options.ChannelBufferSize.Should().Be(bufferSize);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HeartbeatInterval_CanBeSet()
{
// Arrange
@@ -117,7 +126,8 @@ public sealed class InMemoryTransportOptionsTests
options.HeartbeatInterval.Should().Be(TimeSpan.FromSeconds(5));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HeartbeatTimeout_CanBeSet()
{
// Arrange
@@ -134,7 +144,8 @@ public sealed class InMemoryTransportOptionsTests
#region Typical Configuration Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TypicalConfiguration_DevelopmentEnvironment()
{
// Arrange & Act
@@ -153,7 +164,8 @@ public sealed class InMemoryTransportOptionsTests
options.ChannelBufferSize.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TypicalConfiguration_TestingWithSimulatedLatency()
{
// Arrange & Act

View File

@@ -2,6 +2,7 @@ using RabbitMQ.Client;
using StellaOps.Router.Common.Enums;
using StellaOps.Router.Common.Models;
using StellaOps.TestKit;
namespace StellaOps.Router.Transport.RabbitMq.Tests;
/// <summary>
@@ -11,7 +12,8 @@ public sealed class RabbitMqFrameProtocolTests
{
#region ParseFrame Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithValidProperties_ReturnsFrame()
{
// Arrange
@@ -31,7 +33,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Payload.ToArray().Should().BeEquivalentTo(body);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithResponseType_ReturnsResponseFrame()
{
// Arrange
@@ -45,7 +48,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Type.Should().Be(FrameType.Response);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithHelloType_ReturnsHelloFrame()
{
// Arrange
@@ -59,7 +63,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Type.Should().Be(FrameType.Hello);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithHeartbeatType_ReturnsHeartbeatFrame()
{
// Arrange
@@ -73,7 +78,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Type.Should().Be(FrameType.Heartbeat);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithCancelType_ReturnsCancelFrame()
{
// Arrange
@@ -87,7 +93,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Type.Should().Be(FrameType.Cancel);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithNullType_DefaultsToRequest()
{
// Arrange
@@ -101,7 +108,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Type.Should().Be(FrameType.Request);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithEmptyType_DefaultsToRequest()
{
// Arrange
@@ -115,7 +123,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Type.Should().Be(FrameType.Request);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_WithInvalidType_DefaultsToRequest()
{
// Arrange
@@ -129,7 +138,8 @@ public sealed class RabbitMqFrameProtocolTests
frame.Type.Should().Be(FrameType.Request);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_CaseInsensitive_ParsesType()
{
// Arrange
@@ -147,7 +157,8 @@ public sealed class RabbitMqFrameProtocolTests
#region CreateProperties Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_WithFrame_SetsTypeProperty()
{
// Arrange
@@ -165,7 +176,8 @@ public sealed class RabbitMqFrameProtocolTests
properties.Type.Should().Be("Response");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_WithCorrelationId_SetsCorrelationId()
{
// Arrange
@@ -183,7 +195,8 @@ public sealed class RabbitMqFrameProtocolTests
properties.CorrelationId.Should().Be("my-correlation-id");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_WithReplyTo_SetsReplyTo()
{
// Arrange
@@ -201,7 +214,8 @@ public sealed class RabbitMqFrameProtocolTests
properties.ReplyTo.Should().Be("my-reply-queue");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_WithNullReplyTo_DoesNotSetReplyTo()
{
// Arrange
@@ -219,7 +233,8 @@ public sealed class RabbitMqFrameProtocolTests
properties.ReplyTo.Should().BeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_WithTimeout_SetsExpiration()
{
// Arrange
@@ -238,7 +253,8 @@ public sealed class RabbitMqFrameProtocolTests
properties.Expiration.Should().Be("30000");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_WithoutTimeout_DoesNotSetExpiration()
{
// Arrange
@@ -256,7 +272,8 @@ public sealed class RabbitMqFrameProtocolTests
properties.Expiration.Should().BeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_SetsTimestamp()
{
// Arrange
@@ -276,7 +293,8 @@ public sealed class RabbitMqFrameProtocolTests
properties.Timestamp.UnixTime.Should().BeInRange(beforeTimestamp, afterTimestamp);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateProperties_SetsTransientDeliveryMode()
{
// Arrange
@@ -298,7 +316,8 @@ public sealed class RabbitMqFrameProtocolTests
#region ExtractConnectionId Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractConnectionId_WithReplyTo_ExtractsFromQueueName()
{
// Arrange
@@ -316,7 +335,8 @@ public sealed class RabbitMqFrameProtocolTests
connectionId.Should().Be("rmq-instance-123");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractConnectionId_WithSimpleReplyTo_PrefixesWithRmq()
{
// Arrange
@@ -334,7 +354,8 @@ public sealed class RabbitMqFrameProtocolTests
connectionId.Should().Be("rmq-simple-queue");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractConnectionId_WithoutReplyTo_UsesCorrelationId()
{
// Arrange
@@ -353,7 +374,8 @@ public sealed class RabbitMqFrameProtocolTests
connectionId.Should().Contain("abcd1234567890ef");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractConnectionId_WithShortCorrelationId_UsesEntireId()
{
// Arrange
@@ -371,7 +393,8 @@ public sealed class RabbitMqFrameProtocolTests
connectionId.Should().Be("rmq-short");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractConnectionId_WithNoIdentifiers_GeneratesGuid()
{
// Arrange

View File

@@ -41,7 +41,8 @@ public sealed class RabbitMqTransportClientTests
#region Dispose Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_WhenNotConnected_DoesNotThrow()
{
// Arrange
@@ -51,7 +52,8 @@ public sealed class RabbitMqTransportClientTests
await client.DisposeAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_MultipleCallsDoNotThrow()
{
// Arrange
@@ -68,7 +70,8 @@ public sealed class RabbitMqTransportClientTests
#region SendStreamingAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendStreamingAsync_ThrowsNotSupportedException()
{
// Arrange
@@ -111,7 +114,8 @@ public sealed class RabbitMqTransportClientTests
#region CancelAllInflight Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CancelAllInflight_WhenNoInflightRequests_DoesNotThrow()
{
// Arrange
@@ -125,7 +129,8 @@ public sealed class RabbitMqTransportClientTests
#region Options Validation Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_WithValidOptions_DoesNotThrow()
{
// Arrange
@@ -139,7 +144,8 @@ public sealed class RabbitMqTransportClientTests
act.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_WithNullOptions_UsesDefaults()
{
// Arrange
@@ -158,7 +164,8 @@ public sealed class RabbitMqTransportClientTests
#region Event Handler Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnRequestReceived_CanBeRegistered()
{
// Arrange
@@ -181,7 +188,8 @@ public sealed class RabbitMqTransportClientTests
requestReceived.Should().BeFalse(); // Not invoked until message received
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnCancelReceived_CanBeRegistered()
{
// Arrange
@@ -203,7 +211,8 @@ public sealed class RabbitMqTransportClientTests
#region ObjectDisposedException Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendRequestAsync_WhenDisposed_ThrowsObjectDisposedException()
{
// Arrange
@@ -241,7 +250,8 @@ public sealed class RabbitMqTransportClientTests
await act.Should().ThrowAsync<ObjectDisposedException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendCancelAsync_WhenDisposed_ThrowsObjectDisposedException()
{
// Arrange
@@ -272,7 +282,8 @@ public sealed class RabbitMqTransportClientTests
await act.Should().ThrowAsync<ObjectDisposedException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectAsync_WhenDisposed_ThrowsObjectDisposedException()
{
// Arrange
@@ -305,7 +316,8 @@ public sealed class RabbitMqTransportClientTests
/// </summary>
public sealed class RabbitMqTransportClientConfigurationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_WithSsl_ConfiguresCorrectly()
{
// Arrange
@@ -319,6 +331,7 @@ public sealed class RabbitMqTransportClientConfigurationTests
Password = "secret"
};
using StellaOps.TestKit;
// Act
var client = new RabbitMqTransportClient(
Options.Create(options),
@@ -328,7 +341,8 @@ public sealed class RabbitMqTransportClientConfigurationTests
client.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_WithAutoRecovery_ConfiguresCorrectly()
{
// Arrange
@@ -348,7 +362,8 @@ public sealed class RabbitMqTransportClientConfigurationTests
client.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_WithCustomPrefetch_ConfiguresCorrectly()
{
// Arrange
@@ -367,7 +382,8 @@ public sealed class RabbitMqTransportClientConfigurationTests
client.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_ExchangeNames_AreCorrect()
{
// Arrange
@@ -381,7 +397,8 @@ public sealed class RabbitMqTransportClientConfigurationTests
options.ResponseExchange.Should().Be("myapp.responses");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_DefaultExchangeNames_AreCorrect()
{
// Arrange

View File

@@ -5,7 +5,8 @@ namespace StellaOps.Router.Transport.RabbitMq.Tests;
/// </summary>
public sealed class RabbitMqTransportOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_HostName_IsLocalhost()
{
// Arrange & Act
@@ -15,7 +16,8 @@ public sealed class RabbitMqTransportOptionsTests
options.HostName.Should().Be("localhost");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_Port_Is5672()
{
// Arrange & Act
@@ -25,7 +27,8 @@ public sealed class RabbitMqTransportOptionsTests
options.Port.Should().Be(5672);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_VirtualHost_IsRoot()
{
// Arrange & Act
@@ -35,7 +38,8 @@ public sealed class RabbitMqTransportOptionsTests
options.VirtualHost.Should().Be("/");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_UserName_IsGuest()
{
// Arrange & Act
@@ -45,7 +49,8 @@ public sealed class RabbitMqTransportOptionsTests
options.UserName.Should().Be("guest");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_Password_IsGuest()
{
// Arrange & Act
@@ -55,7 +60,8 @@ public sealed class RabbitMqTransportOptionsTests
options.Password.Should().Be("guest");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_UseSsl_IsFalse()
{
// Arrange & Act
@@ -65,7 +71,8 @@ public sealed class RabbitMqTransportOptionsTests
options.UseSsl.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_SslCertPath_IsNull()
{
// Arrange & Act
@@ -75,7 +82,8 @@ public sealed class RabbitMqTransportOptionsTests
options.SslCertPath.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_DurableQueues_IsFalse()
{
// Arrange & Act
@@ -85,7 +93,8 @@ public sealed class RabbitMqTransportOptionsTests
options.DurableQueues.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_AutoDeleteQueues_IsTrue()
{
// Arrange & Act
@@ -95,7 +104,8 @@ public sealed class RabbitMqTransportOptionsTests
options.AutoDeleteQueues.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_PrefetchCount_Is10()
{
// Arrange & Act
@@ -105,7 +115,8 @@ public sealed class RabbitMqTransportOptionsTests
options.PrefetchCount.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_ExchangePrefix_IsStellaRouter()
{
// Arrange & Act
@@ -115,7 +126,8 @@ public sealed class RabbitMqTransportOptionsTests
options.ExchangePrefix.Should().Be("stella.router");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_QueuePrefix_IsStella()
{
// Arrange & Act
@@ -125,7 +137,8 @@ public sealed class RabbitMqTransportOptionsTests
options.QueuePrefix.Should().Be("stella");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RequestExchange_UsesExchangePrefix()
{
// Arrange
@@ -138,7 +151,8 @@ public sealed class RabbitMqTransportOptionsTests
options.RequestExchange.Should().Be("custom.prefix.requests");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ResponseExchange_UsesExchangePrefix()
{
// Arrange
@@ -151,7 +165,8 @@ public sealed class RabbitMqTransportOptionsTests
options.ResponseExchange.Should().Be("custom.prefix.responses");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_NodeId_IsNull()
{
// Arrange & Act
@@ -161,7 +176,8 @@ public sealed class RabbitMqTransportOptionsTests
options.NodeId.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_InstanceId_IsNull()
{
// Arrange & Act
@@ -171,7 +187,8 @@ public sealed class RabbitMqTransportOptionsTests
options.InstanceId.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_AutomaticRecoveryEnabled_IsTrue()
{
// Arrange & Act
@@ -181,7 +198,8 @@ public sealed class RabbitMqTransportOptionsTests
options.AutomaticRecoveryEnabled.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_NetworkRecoveryInterval_Is5Seconds()
{
// Arrange & Act
@@ -191,7 +209,8 @@ public sealed class RabbitMqTransportOptionsTests
options.NetworkRecoveryInterval.Should().Be(TimeSpan.FromSeconds(5));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_DefaultTimeout_Is30Seconds()
{
// Arrange & Act
@@ -201,7 +220,8 @@ public sealed class RabbitMqTransportOptionsTests
options.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(30));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_CanBeCustomized()
{
// Arrange & Act

View File

@@ -40,7 +40,8 @@ public sealed class RabbitMqTransportServerTests
#region Constructor Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_WithValidOptions_DoesNotThrow()
{
// Arrange
@@ -53,7 +54,8 @@ public sealed class RabbitMqTransportServerTests
act.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_WithNullNodeId_GeneratesNodeId()
{
// Arrange
@@ -71,7 +73,8 @@ public sealed class RabbitMqTransportServerTests
#region Dispose Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_WhenNotStarted_DoesNotThrow()
{
// Arrange
@@ -81,7 +84,8 @@ public sealed class RabbitMqTransportServerTests
await server.DisposeAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_MultipleCallsDoNotThrow()
{
// Arrange
@@ -98,7 +102,8 @@ public sealed class RabbitMqTransportServerTests
#region Connection Management Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetConnectionState_WithUnknownConnectionId_ReturnsNull()
{
// Arrange
@@ -111,7 +116,8 @@ public sealed class RabbitMqTransportServerTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetConnections_WhenEmpty_ReturnsEmptyEnumerable()
{
// Arrange
@@ -124,7 +130,8 @@ public sealed class RabbitMqTransportServerTests
result.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectionCount_WhenEmpty_ReturnsZero()
{
// Arrange
@@ -137,7 +144,8 @@ public sealed class RabbitMqTransportServerTests
result.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RemoveConnection_WithUnknownConnectionId_DoesNotThrow()
{
// Arrange
@@ -154,7 +162,8 @@ public sealed class RabbitMqTransportServerTests
#region Event Handler Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnConnection_CanBeRegistered()
{
// Arrange
@@ -171,7 +180,8 @@ public sealed class RabbitMqTransportServerTests
connectionReceived.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnDisconnection_CanBeRegistered()
{
// Arrange
@@ -188,7 +198,8 @@ public sealed class RabbitMqTransportServerTests
disconnectionReceived.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnFrame_CanBeRegistered()
{
// Arrange
@@ -209,7 +220,8 @@ public sealed class RabbitMqTransportServerTests
#region ObjectDisposedException Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_WhenDisposed_ThrowsObjectDisposedException()
{
// Arrange
@@ -223,7 +235,8 @@ public sealed class RabbitMqTransportServerTests
await act.Should().ThrowAsync<ObjectDisposedException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendFrameAsync_WhenDisposed_ThrowsObjectDisposedException()
{
// Arrange
@@ -248,7 +261,8 @@ public sealed class RabbitMqTransportServerTests
#region SendFrameAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendFrameAsync_WithUnknownConnection_ThrowsInvalidOperationException()
{
// Arrange
@@ -273,7 +287,8 @@ public sealed class RabbitMqTransportServerTests
#region StopAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StopAsync_WhenNotStarted_DoesNotThrow()
{
// Arrange
@@ -294,7 +309,8 @@ public sealed class RabbitMqTransportServerTests
/// </summary>
public sealed class RabbitMqTransportServerConfigurationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_WithSsl_ConfiguresCorrectly()
{
// Arrange
@@ -309,6 +325,7 @@ public sealed class RabbitMqTransportServerConfigurationTests
NodeId = "secure-gw"
};
using StellaOps.TestKit;
// Act
var server = new RabbitMqTransportServer(
Options.Create(options),
@@ -318,7 +335,8 @@ public sealed class RabbitMqTransportServerConfigurationTests
server.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_WithDurableQueues_ConfiguresCorrectly()
{
// Arrange
@@ -339,7 +357,8 @@ public sealed class RabbitMqTransportServerConfigurationTests
server.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_WithAutoRecovery_ConfiguresCorrectly()
{
// Arrange
@@ -360,7 +379,8 @@ public sealed class RabbitMqTransportServerConfigurationTests
server.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_WithCustomVirtualHost_ConfiguresCorrectly()
{
// Arrange

View File

@@ -40,21 +40,24 @@ public sealed class ConnectionFailureTests : IDisposable
#region Connection Failure Scenarios
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_MaxReconnectAttempts_DefaultIsTen()
{
var options = new TcpTransportOptions();
options.MaxReconnectAttempts.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_MaxReconnectBackoff_DefaultIsOneMinute()
{
var options = new TcpTransportOptions();
options.MaxReconnectBackoff.Should().Be(TimeSpan.FromMinutes(1));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_ReconnectSettings_CanBeCustomized()
{
var options = new TcpTransportOptions
@@ -71,7 +74,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Exponential Backoff Calculation
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(1, 200)] // 2^1 * 100 = 200ms
[InlineData(2, 400)] // 2^2 * 100 = 400ms
[InlineData(3, 800)] // 2^3 * 100 = 800ms
@@ -84,7 +88,8 @@ public sealed class ConnectionFailureTests : IDisposable
calculated.Should().Be(expectedMs);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Backoff_CappedAtMaximum_WhenExceedsLimit()
{
var maxBackoff = TimeSpan.FromMinutes(1);
@@ -96,7 +101,8 @@ public sealed class ConnectionFailureTests : IDisposable
capped.Should().Be(maxBackoff.TotalMilliseconds);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Backoff_Sequence_IsMonotonicallyIncreasing()
{
var maxBackoff = TimeSpan.FromMinutes(1);
@@ -118,7 +124,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Connection Refused Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Connect_ServerNotListening_ThrowsException()
{
// Arrange - Stop the listener so connection will be refused
@@ -148,7 +155,8 @@ public sealed class ConnectionFailureTests : IDisposable
await client.DisposeAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Connect_InvalidHost_ThrowsException()
{
var options = new TcpTransportOptions
@@ -179,7 +187,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Connection Drop Detection
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ServerDropsConnection_ReadReturnsNull()
{
// This test verifies the frame protocol handles connection drops
@@ -205,7 +214,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Reconnection State Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReconnectAttempts_ResetOnSuccessfulConnection()
{
// This is a behavioral expectation from the implementation:
@@ -224,7 +234,8 @@ public sealed class ConnectionFailureTests : IDisposable
options.MaxReconnectAttempts.Should().Be(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconnectionLoop_RespectsMaxAttempts()
{
// Arrange
@@ -244,7 +255,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Frame Protocol Connection Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FrameProtocol_ReadFromClosedStream_ReturnsNull()
{
// Arrange
@@ -257,7 +269,8 @@ public sealed class ConnectionFailureTests : IDisposable
frame.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FrameProtocol_PartialRead_HandlesGracefully()
{
// Arrange - Create a stream with incomplete frame header
@@ -276,7 +289,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Timeout Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Connect_Timeout_RespectsTimeoutSetting()
{
var options = new TcpTransportOptions
@@ -319,7 +333,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Disposal During Reconnection
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Dispose_DuringPendingConnect_CancelsGracefully()
{
var options = new TcpTransportOptions
@@ -360,7 +375,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Socket Error Classification
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SocketException_ConnectionRefused_IsRecoverable()
{
var ex = new SocketException((int)SocketError.ConnectionRefused);
@@ -369,7 +385,8 @@ public sealed class ConnectionFailureTests : IDisposable
ex.SocketErrorCode.Should().Be(SocketError.ConnectionRefused);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SocketException_ConnectionReset_IsRecoverable()
{
var ex = new SocketException((int)SocketError.ConnectionReset);
@@ -378,7 +395,8 @@ public sealed class ConnectionFailureTests : IDisposable
ex.SocketErrorCode.Should().Be(SocketError.ConnectionReset);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SocketException_NetworkUnreachable_IsRecoverable()
{
var ex = new SocketException((int)SocketError.NetworkUnreachable);
@@ -387,7 +405,8 @@ public sealed class ConnectionFailureTests : IDisposable
ex.SocketErrorCode.Should().Be(SocketError.NetworkUnreachable);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SocketException_TimedOut_IsRecoverable()
{
var ex = new SocketException((int)SocketError.TimedOut);
@@ -400,7 +419,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Multiple Reconnection Cycles
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BackoffSequence_MultipleFullCycles_Deterministic()
{
// Verify that backoff calculation is deterministic across cycles
@@ -429,7 +449,8 @@ public sealed class ConnectionFailureTests : IDisposable
#region Connection State Tracking
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Client_InitialState_NotConnected()
{
var options = new TcpTransportOptions
@@ -457,21 +478,24 @@ public sealed class TlsConnectionFailureTests
{
#region TLS-Specific Options
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsOptions_MaxReconnectAttempts_DefaultIsTen()
{
var options = new TlsTransportOptions();
options.MaxReconnectAttempts.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsOptions_MaxReconnectBackoff_DefaultIsOneMinute()
{
var options = new TlsTransportOptions();
options.MaxReconnectBackoff.Should().Be(TimeSpan.FromMinutes(1));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsOptions_ReconnectAndSsl_CanBeCombined()
{
var options = new TlsTransportOptions
@@ -492,7 +516,8 @@ public sealed class TlsConnectionFailureTests
#region TLS Connection Failures
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TlsConnect_InvalidCertificate_ShouldFail()
{
// TLS connections with invalid certificates should fail
@@ -511,7 +536,8 @@ public sealed class TlsConnectionFailureTests
options.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsBackoff_SameFormulaAsTcp()
{
// TLS uses the same exponential backoff formula
@@ -531,7 +557,8 @@ public sealed class TlsConnectionFailureTests
/// </summary>
public sealed class InMemoryConnectionFailureTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InMemoryChannel_NoReconnection_NotApplicable()
{
// InMemory transport doesn't have network connections
@@ -553,10 +580,12 @@ public sealed class InMemoryConnectionFailureTests
canWrite.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task InMemoryChannel_CompletedWithError_PropagatesError()
{
using var channel = new InMemoryChannel("error-complete");
using StellaOps.TestKit;
var expectedException = new InvalidOperationException("Simulated failure");
// Complete with error

View File

@@ -13,7 +13,8 @@ public sealed class FrameFuzzTests
{
#region Truncated Frame Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_EmptyStream_ReturnsNull()
{
// Arrange
@@ -26,7 +27,8 @@ public sealed class FrameFuzzTests
result.Should().BeNull();
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
@@ -41,7 +43,8 @@ public sealed class FrameFuzzTests
.WithMessage("*Incomplete length prefix*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_LengthPrefixOnly_ThrowsException()
{
// Arrange - Valid length prefix but no payload
@@ -57,7 +60,8 @@ public sealed class FrameFuzzTests
.WithMessage("*Incomplete payload*");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(50, 10)]
[InlineData(100, 25)]
[InlineData(1000, 100)]
@@ -81,7 +85,8 @@ public sealed class FrameFuzzTests
#region Invalid Length Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_NegativeLength_ThrowsException()
{
// Arrange - Negative length (high bit set in signed int)
@@ -96,7 +101,8 @@ public sealed class FrameFuzzTests
await action.Should().ThrowAsync<InvalidOperationException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_ZeroLength_ThrowsException()
{
// Arrange
@@ -112,7 +118,8 @@ public sealed class FrameFuzzTests
.WithMessage("*Invalid payload length*");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(1)]
[InlineData(5)]
[InlineData(16)]
@@ -132,7 +139,8 @@ public sealed class FrameFuzzTests
.WithMessage("*Invalid payload length*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_OversizedLength_ThrowsException()
{
// Arrange - Frame larger than max allowed
@@ -156,7 +164,8 @@ public sealed class FrameFuzzTests
#region Invalid Frame Type Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(255)]
[InlineData(100)]
[InlineData(50)]
@@ -187,7 +196,8 @@ public sealed class FrameFuzzTests
#region Corrupted Correlation ID Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_AllZeroCorrelationId_ReadSuccessfully()
{
// Arrange
@@ -210,7 +220,8 @@ public sealed class FrameFuzzTests
result!.CorrelationId.Should().Be("00000000000000000000000000000000");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_NonGuidCorrelationBytes_ReadAsHex()
{
// Arrange - Non-standard bytes that aren't a valid GUID
@@ -238,7 +249,8 @@ public sealed class FrameFuzzTests
#region Random Data Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_RandomBytes_HandledGracefully()
{
// Arrange
@@ -260,7 +272,8 @@ public sealed class FrameFuzzTests
}
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(10)]
[InlineData(50)]
[InlineData(100)]
@@ -294,7 +307,8 @@ public sealed class FrameFuzzTests
#region Boundary Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_ExactMinimumValidFrame_ParsesSuccessfully()
{
// Arrange - Minimum valid frame: type (1) + correlation (16) + 0 payload = 17 bytes
@@ -317,7 +331,8 @@ public sealed class FrameFuzzTests
result.Payload.Length.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_MaxIntLength_RejectedByMaxFrameSize()
{
// Arrange - Length = Int32.MaxValue
@@ -333,7 +348,8 @@ public sealed class FrameFuzzTests
.WithMessage("*exceeds maximum*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_ExactMaxFrameSize_Accepted()
{
// Arrange
@@ -358,7 +374,8 @@ public sealed class FrameFuzzTests
result.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_OneBytOverMaxFrameSize_Rejected()
{
// Arrange
@@ -386,7 +403,8 @@ public sealed class FrameFuzzTests
#region Multiple Frames Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_GarbageBetweenFrames_CorruptsSubsequent()
{
// Arrange - Valid frame, then garbage, then valid frame
@@ -424,7 +442,8 @@ public sealed class FrameFuzzTests
await action.Should().ThrowAsync<InvalidOperationException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_MultipleValidFrames_AllParsed()
{
// Arrange
@@ -463,7 +482,8 @@ public sealed class FrameFuzzTests
#region Payload Content Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_AllByteValues_InPayload_Preserved()
{
// Arrange - All possible byte values (0-255)
@@ -487,11 +507,13 @@ public sealed class FrameFuzzTests
result!.Payload.ToArray().Should().BeEquivalentTo(allBytes);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Fuzz_NullBytes_InPayload_Preserved()
{
// Arrange - Payload with null bytes
using var stream = new MemoryStream();
using StellaOps.TestKit;
var payloadWithNulls = new byte[] { 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00 };
var frame = new Frame

View File

@@ -13,7 +13,8 @@ public sealed class TcpTransportComplianceTests
{
#region Protocol Roundtrip Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ProtocolRoundtrip_RequestFrame_AllFieldsPreserved()
{
// Arrange
@@ -57,7 +58,8 @@ public sealed class TcpTransportComplianceTests
restored.SupportsStreaming.Should().Be(request.SupportsStreaming);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ProtocolRoundtrip_ResponseFrame_AllFieldsPreserved()
{
// Arrange
@@ -93,7 +95,8 @@ public sealed class TcpTransportComplianceTests
restored.HasMoreChunks.Should().Be(response.HasMoreChunks);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ProtocolRoundtrip_BinaryPayload_PreservesAllBytes()
{
// Arrange
@@ -118,7 +121,8 @@ public sealed class TcpTransportComplianceTests
readFrame!.Payload.ToArray().Should().BeEquivalentTo(binaryPayload);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(100)]
@@ -157,7 +161,8 @@ public sealed class TcpTransportComplianceTests
#region Frame Type Discrimination Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(FrameType.Request)]
[InlineData(FrameType.Response)]
[InlineData(FrameType.Hello)]
@@ -188,7 +193,8 @@ public sealed class TcpTransportComplianceTests
#region Message Ordering Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ordering_MultipleFrames_FifoPreserved()
{
// Arrange
@@ -226,7 +232,8 @@ public sealed class TcpTransportComplianceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ordering_MixedFrameTypes_OrderPreserved()
{
// Arrange
@@ -266,7 +273,8 @@ public sealed class TcpTransportComplianceTests
#region Framing Integrity Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FramingIntegrity_CorrelationIdPreserved()
{
// Arrange
@@ -300,7 +308,8 @@ public sealed class TcpTransportComplianceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FramingIntegrity_LargeFrame_TransfersCompletely()
{
// Arrange - 1MB frame
@@ -328,7 +337,8 @@ public sealed class TcpTransportComplianceTests
#region Connection Behavior Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectionBehavior_PendingRequestTracker_TracksCorrectly()
{
// Arrange
@@ -354,7 +364,8 @@ public sealed class TcpTransportComplianceTests
tracker.Count.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectionBehavior_RequestTimeout_CancelsCleanly()
{
// Arrange
@@ -370,7 +381,8 @@ public sealed class TcpTransportComplianceTests
tracker.Count.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ConnectionBehavior_CancelAll_ClearsAllPending()
{
// Arrange
@@ -389,7 +401,8 @@ public sealed class TcpTransportComplianceTests
tasks.Should().AllSatisfy(t => t.IsCanceled.Should().BeTrue());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ConnectionBehavior_FailRequest_PropagatesToAwaiter()
{
// Arrange
@@ -410,7 +423,8 @@ public sealed class TcpTransportComplianceTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameInput_SameOutput()
{
// Run same test multiple times - should always produce same results
@@ -445,7 +459,8 @@ public sealed class TcpTransportComplianceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_ByteSequence_Consistent()
{
// Arrange - Write same frame twice
@@ -471,7 +486,8 @@ public sealed class TcpTransportComplianceTests
#region Error Handling Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ErrorHandling_OversizedFrame_Rejected()
{
// Arrange
@@ -495,7 +511,8 @@ public sealed class TcpTransportComplianceTests
.WithMessage("*exceeds maximum*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ErrorHandling_EmptyStream_ReturnsNull()
{
// Arrange
@@ -508,12 +525,14 @@ public sealed class TcpTransportComplianceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ErrorHandling_CancellationDuringWrite_Throws()
{
// Arrange
using var stream = new MemoryStream();
using var cts = new CancellationTokenSource();
using StellaOps.TestKit;
await cts.CancelAsync();
var frame = new Frame

View File

@@ -13,7 +13,8 @@ namespace StellaOps.Router.Transport.Tcp.Tests;
public class TcpTransportOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_HaveCorrectValues()
{
// Act
@@ -30,7 +31,8 @@ public class TcpTransportOptionsTests
options.MaxFrameSize.Should().Be(16 * 1024 * 1024);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Host_CanBeSet()
{
// Act
@@ -40,7 +42,8 @@ public class TcpTransportOptionsTests
options.Host.Should().Be("192.168.1.100");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Port_CanBeSet()
{
// Act
@@ -50,7 +53,8 @@ public class TcpTransportOptionsTests
options.Port.Should().Be(9999);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(1024)]
[InlineData(128 * 1024)]
[InlineData(1024 * 1024)]
@@ -63,7 +67,8 @@ public class TcpTransportOptionsTests
options.ReceiveBufferSize.Should().Be(bufferSize);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(1024)]
[InlineData(128 * 1024)]
[InlineData(1024 * 1024)]
@@ -76,7 +81,8 @@ public class TcpTransportOptionsTests
options.SendBufferSize.Should().Be(bufferSize);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MaxReconnectAttempts_CanBeSetToZero()
{
// Act
@@ -93,7 +99,8 @@ public class TcpTransportOptionsTests
public class FrameProtocolTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task WriteAndReadFrame_RoundTrip()
{
// Arrange
@@ -119,7 +126,8 @@ public class FrameProtocolTests
readFrame.Payload.ToArray().Should().Equal(originalFrame.Payload.ToArray());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task WriteAndReadFrame_EmptyPayload()
{
// Arrange
@@ -142,7 +150,8 @@ public class FrameProtocolTests
readFrame.Payload.ToArray().Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReadFrame_ReturnsNullOnEmptyStream()
{
// Arrange
@@ -155,7 +164,8 @@ public class FrameProtocolTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReadFrame_ThrowsOnOversizedFrame()
{
// Arrange
@@ -176,7 +186,8 @@ public class FrameProtocolTests
.WithMessage("*exceeds maximum*");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(FrameType.Request)]
[InlineData(FrameType.Response)]
[InlineData(FrameType.Cancel)]
@@ -203,7 +214,8 @@ public class FrameProtocolTests
readFrame!.Type.Should().Be(frameType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task WriteFrame_WithNullCorrelationId_GeneratesNewGuid()
{
// Arrange
@@ -226,7 +238,8 @@ public class FrameProtocolTests
Guid.TryParse(readFrame.CorrelationId, out _).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task WriteFrame_BigEndianLength_CorrectByteOrder()
{
// Arrange
@@ -252,7 +265,8 @@ public class FrameProtocolTests
actualLength.Should().Be(expectedLength);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReadFrame_IncompleteLengthPrefix_ThrowsException()
{
// Arrange - Only 2 bytes instead of 4 for length prefix
@@ -264,7 +278,8 @@ public class FrameProtocolTests
.WithMessage("*Incomplete length prefix*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReadFrame_InvalidPayloadLength_TooSmall_ThrowsException()
{
// Arrange - Length of 5 is too small (header is 17 bytes minimum)
@@ -280,7 +295,8 @@ public class FrameProtocolTests
.WithMessage("*Invalid payload length*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReadFrame_IncompletePayload_ThrowsException()
{
// Arrange - Claim to have 100 bytes but only provide 10
@@ -297,7 +313,8 @@ public class FrameProtocolTests
.WithMessage("*Incomplete payload*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReadFrame_WithLargePayload_ReadsCorrectly()
{
// Arrange
@@ -322,7 +339,8 @@ public class FrameProtocolTests
readFrame!.Payload.ToArray().Should().Equal(largePayload);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task WriteFrame_CancellationRequested_ThrowsOperationCanceled()
{
// Arrange
@@ -348,7 +366,8 @@ public class FrameProtocolTests
public class PendingRequestTrackerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TrackRequest_CompletesWithResponse()
{
// Arrange
@@ -371,7 +390,8 @@ public class PendingRequestTrackerTests
response.Type.Should().Be(expectedResponse.Type);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TrackRequest_CancelsOnTokenCancellation()
{
// Arrange
@@ -388,7 +408,8 @@ public class PendingRequestTrackerTests
await action.Should().ThrowAsync<TaskCanceledException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Count_ReturnsCorrectValue()
{
// Arrange
@@ -401,7 +422,8 @@ public class PendingRequestTrackerTests
tracker.Count.Should().Be(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CancelAll_CancelsAllPendingRequests()
{
// Arrange
@@ -417,7 +439,8 @@ public class PendingRequestTrackerTests
task2.IsCanceled.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FailRequest_SetsException()
{
// Arrange
@@ -433,7 +456,8 @@ public class PendingRequestTrackerTests
task.Exception?.InnerException.Should().BeOfType<InvalidOperationException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CancelRequest_CancelsSpecificRequest()
{
// Arrange
@@ -452,7 +476,8 @@ public class PendingRequestTrackerTests
task2.IsCompleted.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CompleteRequest_WithUnknownId_DoesNotThrow()
{
// Arrange
@@ -467,7 +492,8 @@ public class PendingRequestTrackerTests
action.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CancelRequest_WithUnknownId_DoesNotThrow()
{
// Arrange
@@ -481,7 +507,8 @@ public class PendingRequestTrackerTests
action.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FailRequest_WithUnknownId_DoesNotThrow()
{
// Arrange
@@ -495,7 +522,8 @@ public class PendingRequestTrackerTests
action.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CancelsAllPendingRequests()
{
// Arrange
@@ -509,7 +537,8 @@ public class PendingRequestTrackerTests
(task.IsCanceled || task.IsFaulted).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CanBeCalledMultipleTimes()
{
// Arrange
@@ -527,7 +556,8 @@ public class PendingRequestTrackerTests
action.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CompleteRequest_DecreasesCount()
{
// Arrange
@@ -553,7 +583,8 @@ public class PendingRequestTrackerTests
public class TcpTransportServerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_StartsListening()
{
// Arrange
@@ -568,7 +599,8 @@ public class TcpTransportServerTests
await server.StopAsync(CancellationToken.None);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StopAsync_CanBeCalledWithoutStart()
{
// Arrange
@@ -582,7 +614,8 @@ public class TcpTransportServerTests
await action.Should().NotThrowAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectionCount_InitiallyZero()
{
// Arrange
@@ -593,7 +626,8 @@ public class TcpTransportServerTests
server.ConnectionCount.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_CanBeCalledMultipleTimes()
{
// Arrange
@@ -612,7 +646,8 @@ public class TcpTransportServerTests
await action.Should().NotThrowAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_TwiceDoesNotThrow()
{
// Arrange
@@ -643,7 +678,8 @@ public class TcpTransportClientTests
NullLogger<TcpTransportClient>.Instance);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Constructor_InitializesCorrectly()
{
// Act
@@ -653,7 +689,8 @@ public class TcpTransportClientTests
client.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectAsync_WithoutHost_ThrowsInvalidOperationException()
{
// Arrange
@@ -675,7 +712,8 @@ public class TcpTransportClientTests
.WithMessage("*Host is not configured*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectAsync_WithEmptyHost_ThrowsInvalidOperationException()
{
// Arrange
@@ -697,7 +735,8 @@ public class TcpTransportClientTests
.WithMessage("*Host is not configured*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_CanBeCalledMultipleTimes()
{
// Arrange
@@ -715,7 +754,8 @@ public class TcpTransportClientTests
await action.Should().NotThrowAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisconnectAsync_WithoutConnect_DoesNotThrow()
{
// Arrange
@@ -728,12 +768,14 @@ public class TcpTransportClientTests
await action.Should().NotThrowAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CancelAllInflight_WithNoInflight_DoesNotThrow()
{
// Arrange
await using var client = CreateClient();
using StellaOps.TestKit;
// Act
var action = () => client.CancelAllInflight("test shutdown");

View File

@@ -20,7 +20,8 @@ public sealed class TlsTransportComplianceTests
{
#region TLS Options Compliance Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsOptions_DefaultProtocols_SecureDefaults()
{
// Arrange & Act
@@ -38,7 +39,8 @@ public sealed class TlsTransportComplianceTests
#pragma warning restore SYSLIB0039
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsOptions_RequireClientCertificate_DefaultFalse()
{
// Arrange & Act
@@ -48,7 +50,8 @@ public sealed class TlsTransportComplianceTests
options.RequireClientCertificate.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsOptions_AllowSelfSigned_DefaultFalse()
{
// Arrange & Act
@@ -58,7 +61,8 @@ public sealed class TlsTransportComplianceTests
options.AllowSelfSigned.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TlsOptions_CheckCertificateRevocation_DefaultFalse()
{
// Arrange & Act
@@ -72,7 +76,8 @@ public sealed class TlsTransportComplianceTests
#region Certificate Loading Compliance Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CertificateLoading_DirectCertificate_Preferred()
{
// Arrange
@@ -90,7 +95,8 @@ public sealed class TlsTransportComplianceTests
loaded.Should().BeSameAs(cert);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CertificateLoading_NoCertificate_ThrowsForServer()
{
// Arrange
@@ -101,7 +107,8 @@ public sealed class TlsTransportComplianceTests
action.Should().Throw<InvalidOperationException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CertificateLoading_NoCertificate_ReturnsNullForClient()
{
// Arrange
@@ -114,7 +121,8 @@ public sealed class TlsTransportComplianceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CertificateLoading_ClientCertificate_LoadsSuccessfully()
{
// Arrange
@@ -135,7 +143,8 @@ public sealed class TlsTransportComplianceTests
#region Protocol Negotiation Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(SslProtocols.Tls12)]
[InlineData(SslProtocols.Tls13)]
[InlineData(SslProtocols.Tls12 | SslProtocols.Tls13)]
@@ -151,7 +160,8 @@ public sealed class TlsTransportComplianceTests
options.EnabledProtocols.Should().Be(protocols);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ProtocolNegotiation_Tls12Only_Configurable()
{
// Arrange & Act
@@ -165,7 +175,8 @@ public sealed class TlsTransportComplianceTests
options.EnabledProtocols.HasFlag(SslProtocols.Tls13).Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ProtocolNegotiation_Tls13Only_Configurable()
{
// Arrange & Act
@@ -186,7 +197,8 @@ public sealed class TlsTransportComplianceTests
// TLS uses the same frame protocol as TCP after the TLS handshake
// These tests verify frames are correctly serialized before TLS encryption
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FrameRoundtrip_RequestFrame_PreTlsEncryption()
{
// Arrange - Test frame serialization (TLS encrypts the result)
@@ -225,7 +237,8 @@ public sealed class TlsTransportComplianceTests
Encoding.UTF8.GetString(restored.Payload.Span).Should().Be(@"{""sensitive"":""data""}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FrameRoundtrip_BinaryPayload_NotCorrupted()
{
// Arrange - Binary data should survive serialization before TLS encryption
@@ -252,7 +265,8 @@ public sealed class TlsTransportComplianceTests
#region Hostname Verification Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HostnameVerification_ExpectedHostname_Configurable()
{
// Arrange & Act
@@ -265,7 +279,8 @@ public sealed class TlsTransportComplianceTests
options.ExpectedServerHostname.Should().Be("api.stellaops.io");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HostnameVerification_NotSet_UsesHost()
{
// Arrange & Act
@@ -284,7 +299,8 @@ public sealed class TlsTransportComplianceTests
#region Certificate Path Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CertificatePath_Server_Configurable()
{
// Arrange & Act
@@ -299,7 +315,8 @@ public sealed class TlsTransportComplianceTests
options.ServerCertificatePassword.Should().Be("secure-password");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CertificatePath_Client_Configurable()
{
// Arrange & Act
@@ -318,7 +335,8 @@ public sealed class TlsTransportComplianceTests
#region Timeout and Buffer Configuration Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Timeouts_DefaultValues_Reasonable()
{
// Arrange & Act
@@ -331,7 +349,8 @@ public sealed class TlsTransportComplianceTests
options.MaxReconnectBackoff.Should().Be(TimeSpan.FromMinutes(1));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Buffers_DefaultValues_Reasonable()
{
// Arrange & Act
@@ -343,7 +362,8 @@ public sealed class TlsTransportComplianceTests
options.MaxFrameSize.Should().Be(16 * 1024 * 1024); // 16MB
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(8 * 1024)]
[InlineData(64 * 1024)]
[InlineData(256 * 1024)]
@@ -365,7 +385,8 @@ public sealed class TlsTransportComplianceTests
#region mTLS Configuration Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MutualTls_ClientCertRequired_Configurable()
{
// Arrange & Act
@@ -378,7 +399,8 @@ public sealed class TlsTransportComplianceTests
options.RequireClientCertificate.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MutualTls_FullConfiguration_AllOptionsCombine()
{
// Arrange
@@ -411,7 +433,8 @@ public sealed class TlsTransportComplianceTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameInput_SameOutput()
{
// Arrange - Frame serialization should be deterministic
@@ -449,6 +472,7 @@ public sealed class TlsTransportComplianceTests
private static X509Certificate2 CreateTestCertificate(string subject)
{
using var rsa = RSA.Create(2048);
using StellaOps.TestKit;
var request = new CertificateRequest(
$"CN={subject}",
rsa,

View File

@@ -16,7 +16,8 @@ namespace StellaOps.Router.Transport.Tls.Tests;
public class TlsTransportOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_HaveCorrectValues()
{
// Act
@@ -37,7 +38,8 @@ public class TlsTransportOptionsTests
options.EnabledProtocols.Should().Be(SslProtocols.Tls12 | SslProtocols.Tls13);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Host_CanBeSet()
{
// Act
@@ -47,7 +49,8 @@ public class TlsTransportOptionsTests
options.Host.Should().Be("tls.gateway.local");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Port_CanBeSet()
{
// Act
@@ -57,7 +60,8 @@ public class TlsTransportOptionsTests
options.Port.Should().Be(443);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(true)]
[InlineData(false)]
public void RequireClientCertificate_CanBeSet(bool required)
@@ -69,7 +73,8 @@ public class TlsTransportOptionsTests
options.RequireClientCertificate.Should().Be(required);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(true)]
[InlineData(false)]
public void AllowSelfSigned_CanBeSet(bool allowed)
@@ -81,7 +86,8 @@ public class TlsTransportOptionsTests
options.AllowSelfSigned.Should().Be(allowed);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(true)]
[InlineData(false)]
public void CheckCertificateRevocation_CanBeSet(bool check)
@@ -93,7 +99,8 @@ public class TlsTransportOptionsTests
options.CheckCertificateRevocation.Should().Be(check);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(SslProtocols.Tls12)]
[InlineData(SslProtocols.Tls13)]
[InlineData(SslProtocols.Tls12 | SslProtocols.Tls13)]
@@ -106,7 +113,8 @@ public class TlsTransportOptionsTests
options.EnabledProtocols.Should().Be(protocols);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExpectedServerHostname_CanBeSet()
{
// Act
@@ -116,7 +124,8 @@ public class TlsTransportOptionsTests
options.ExpectedServerHostname.Should().Be("expected.host.name");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ServerCertificatePath_CanBeSet()
{
// Act
@@ -126,7 +135,8 @@ public class TlsTransportOptionsTests
options.ServerCertificatePath.Should().Be("/etc/certs/server.pfx");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ClientCertificatePath_CanBeSet()
{
// Act
@@ -143,7 +153,8 @@ public class TlsTransportOptionsTests
public class CertificateLoaderTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadServerCertificate_WithDirectCertificate_ReturnsCertificate()
{
// Arrange
@@ -160,7 +171,8 @@ public class CertificateLoaderTests
loaded.Should().BeSameAs(cert);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadServerCertificate_WithNoCertificate_ThrowsException()
{
// Arrange
@@ -171,7 +183,8 @@ public class CertificateLoaderTests
action.Should().Throw<InvalidOperationException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadClientCertificate_WithNoCertificate_ReturnsNull()
{
// Arrange
@@ -184,7 +197,8 @@ public class CertificateLoaderTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadClientCertificate_WithDirectCertificate_ReturnsCertificate()
{
// Arrange
@@ -201,7 +215,8 @@ public class CertificateLoaderTests
loaded.Should().BeSameAs(cert);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadServerCertificate_WithInvalidPath_ThrowsException()
{
// Arrange
@@ -246,7 +261,8 @@ public class CertificateLoaderTests
public class TlsTransportServerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_WithValidCertificate_StartsListening()
{
// Arrange
@@ -268,7 +284,8 @@ public class TlsTransportServerTests
await server.StopAsync(CancellationToken.None);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_WithNoCertificate_ThrowsException()
{
// Arrange
@@ -280,7 +297,8 @@ public class TlsTransportServerTests
await action.Should().ThrowAsync<InvalidOperationException>();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StopAsync_CanBeCalledWithoutStart()
{
// Arrange
@@ -299,7 +317,8 @@ public class TlsTransportServerTests
await action.Should().NotThrowAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectionCount_InitiallyZero()
{
// Arrange
@@ -315,7 +334,8 @@ public class TlsTransportServerTests
server.ConnectionCount.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_CanBeCalledMultipleTimes()
{
// Arrange
@@ -382,7 +402,8 @@ public class TlsTransportClientTests
NullLogger<TlsTransportClient>.Instance);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Constructor_InitializesCorrectly()
{
// Act
@@ -392,7 +413,8 @@ public class TlsTransportClientTests
client.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectAsync_WithoutHost_ThrowsInvalidOperationException()
{
// Arrange
@@ -414,7 +436,8 @@ public class TlsTransportClientTests
.WithMessage("*Host is not configured*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectAsync_WithEmptyHost_ThrowsInvalidOperationException()
{
// Arrange
@@ -436,7 +459,8 @@ public class TlsTransportClientTests
.WithMessage("*Host is not configured*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_CanBeCalledMultipleTimes()
{
// Arrange
@@ -454,7 +478,8 @@ public class TlsTransportClientTests
await action.Should().NotThrowAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisconnectAsync_WithoutConnect_DoesNotThrow()
{
// Arrange
@@ -467,7 +492,8 @@ public class TlsTransportClientTests
await action.Should().NotThrowAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CancelAllInflight_WithNoInflight_DoesNotThrow()
{
// Arrange
@@ -487,7 +513,8 @@ public class TlsTransportClientTests
public class CertificateWatcherTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_LoadsServerCertificate()
{
// Arrange
@@ -504,7 +531,8 @@ public class CertificateWatcherTests
watcher.ServerCertificate.Should().BeSameAs(cert);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_LoadsClientCertificate()
{
// Arrange
@@ -521,7 +549,8 @@ public class CertificateWatcherTests
watcher.ClientCertificate.Should().BeSameAs(cert);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CanBeCalledMultipleTimes()
{
// Arrange
@@ -540,7 +569,8 @@ public class CertificateWatcherTests
action.Should().NotThrow();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OnServerCertificateReloaded_CanBeSubscribed()
{
// Arrange
@@ -556,7 +586,8 @@ public class CertificateWatcherTests
watcher.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OnClientCertificateReloaded_CanBeSubscribed()
{
// Arrange
@@ -602,7 +633,8 @@ public class CertificateWatcherTests
public class TlsIntegrationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ServerAndClient_CanEstablishConnection()
{
// Arrange - Create self-signed server certificate
@@ -626,7 +658,8 @@ public class TlsIntegrationTests
await server.StopAsync(CancellationToken.None);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ServerWithMtls_RequiresClientCertificate()
{
// Arrange
@@ -654,6 +687,7 @@ public class TlsIntegrationTests
private static X509Certificate2 CreateSelfSignedServerCertificate(string hostname)
{
using var rsa = RSA.Create(2048);
using StellaOps.TestKit;
var request = new CertificateRequest(
$"CN={hostname}",
rsa,
@@ -696,7 +730,8 @@ public class TlsIntegrationTests
public class ServiceCollectionExtensionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddTlsTransportServer_RegistersServices()
{
// Arrange
@@ -715,7 +750,8 @@ public class ServiceCollectionExtensionsTests
server.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddTlsTransportClient_RegistersServices()
{
// Arrange
@@ -735,7 +771,8 @@ public class ServiceCollectionExtensionsTests
client.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddTlsTransportServer_WithOptions_ConfiguresOptions()
{
// Arrange
@@ -757,7 +794,8 @@ public class ServiceCollectionExtensionsTests
optionsService.Value.RequireClientCertificate.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddTlsTransportClient_WithOptions_ConfiguresOptions()
{
// Arrange

View File

@@ -4,6 +4,7 @@ using StellaOps.Router.Common.Models;
using StellaOps.Router.Transport.Udp;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Router.Transport.Udp.Tests;
/// <summary>
@@ -13,7 +14,8 @@ public sealed class UdpFrameProtocolTests
{
#region ParseFrame Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_ValidFrame_ParsesCorrectly()
{
// Arrange
@@ -30,7 +32,8 @@ public sealed class UdpFrameProtocolTests
frame.Payload.ToArray().Should().Equal(payload);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_EmptyPayload_ParsesCorrectly()
{
// Arrange
@@ -46,7 +49,8 @@ public sealed class UdpFrameProtocolTests
frame.Payload.Length.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_DataTooSmall_ThrowsInvalidOperationException()
{
// Arrange
@@ -60,7 +64,8 @@ public sealed class UdpFrameProtocolTests
.WithMessage("*too small*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ParseFrame_MinimumHeaderSize_Works()
{
// Arrange - exactly header size (17 bytes)
@@ -75,7 +80,8 @@ public sealed class UdpFrameProtocolTests
frame.Payload.Length.Should().Be(0);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(FrameType.Request)]
[InlineData(FrameType.Response)]
[InlineData(FrameType.Hello)]
@@ -98,7 +104,8 @@ public sealed class UdpFrameProtocolTests
#region SerializeFrame Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SerializeFrame_ValidFrame_SerializesCorrectly()
{
// Arrange
@@ -118,7 +125,8 @@ public sealed class UdpFrameProtocolTests
data[0].Should().Be((byte)FrameType.Response);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SerializeFrame_EmptyPayload_SerializesCorrectly()
{
// Arrange
@@ -136,7 +144,8 @@ public sealed class UdpFrameProtocolTests
data.Length.Should().Be(17); // Header only
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SerializeFrame_NullCorrelationId_GeneratesNewGuid()
{
// Arrange
@@ -157,7 +166,8 @@ public sealed class UdpFrameProtocolTests
correlationBytes.ToArray().Should().NotBeEquivalentTo(new byte[16]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SerializeFrame_RoundTrip_PreservesData()
{
// Arrange
@@ -184,7 +194,8 @@ public sealed class UdpFrameProtocolTests
#region GetHeaderSize Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetHeaderSize_ReturnsExpectedValue()
{
// Act

View File

@@ -15,7 +15,8 @@ public sealed class UdpTransportClientTests
{
#region ConnectAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectAsync_WithNoHost_ThrowsInvalidOperationException()
{
// Arrange
@@ -42,7 +43,8 @@ public sealed class UdpTransportClientTests
.WithMessage("*Host is not configured*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConnectAsync_AfterDispose_ThrowsObjectDisposedException()
{
// Arrange
@@ -73,7 +75,8 @@ public sealed class UdpTransportClientTests
#region SendStreamingAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendStreamingAsync_ThrowsNotSupportedException()
{
// Arrange
@@ -127,7 +130,8 @@ public sealed class UdpTransportClientTests
#region CancelAllInflight Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CancelAllInflight_WithNoInflight_DoesNotThrow()
{
// Arrange
@@ -149,7 +153,8 @@ public sealed class UdpTransportClientTests
#region DisposeAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_CanBeCalledMultipleTimes()
{
// Arrange
@@ -176,7 +181,8 @@ public sealed class UdpTransportClientTests
#region Event Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnRequestReceived_CanBeSubscribed()
{
// Arrange
@@ -199,7 +205,8 @@ public sealed class UdpTransportClientTests
client.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnCancelReceived_CanBeSubscribed()
{
// Arrange
@@ -210,6 +217,7 @@ public sealed class UdpTransportClientTests
});
await using var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
using StellaOps.TestKit;
Guid? receivedCorrelationId = null;
// Act
@@ -227,7 +235,8 @@ public sealed class UdpTransportClientTests
#region SendCancelAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendCancelAsync_AfterDispose_ThrowsObjectDisposedException()
{
// Arrange
@@ -265,7 +274,8 @@ public sealed class UdpTransportClientTests
#region SendRequestAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendRequestAsync_AfterDispose_ThrowsObjectDisposedException()
{
// Arrange

View File

@@ -5,6 +5,7 @@ using StellaOps.Router.Common.Abstractions;
using StellaOps.Router.Transport.Udp;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Router.Transport.Udp.Tests;
/// <summary>
@@ -12,7 +13,8 @@ namespace StellaOps.Router.Transport.Udp.Tests;
/// </summary>
public sealed class UdpTransportOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultOptions_HaveCorrectValues()
{
// Arrange & Act
@@ -29,7 +31,8 @@ public sealed class UdpTransportOptionsTests
options.SendBufferSize.Should().Be(64 * 1024);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_CanBeModified()
{
// Arrange
@@ -62,7 +65,8 @@ public sealed class UdpTransportOptionsTests
/// </summary>
public sealed class PayloadTooLargeExceptionTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsProperties()
{
// Arrange
@@ -77,7 +81,8 @@ public sealed class PayloadTooLargeExceptionTests
exception.MaxSize.Should().Be(maxSize);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsMessage()
{
// Arrange
@@ -92,7 +97,8 @@ public sealed class PayloadTooLargeExceptionTests
exception.Message.Should().Contain("8192");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Exception_IsExceptionType()
{
// Arrange & Act
@@ -108,7 +114,8 @@ public sealed class PayloadTooLargeExceptionTests
/// </summary>
public sealed class UdpServiceCollectionExtensionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddUdpTransportServer_RegistersServices()
{
// Arrange
@@ -130,7 +137,8 @@ public sealed class UdpServiceCollectionExtensionsTests
transportServer.Should().BeSameAs(server);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddUdpTransportServer_WithNullConfigure_Works()
{
// Arrange
@@ -147,7 +155,8 @@ public sealed class UdpServiceCollectionExtensionsTests
server.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddUdpTransportClient_RegistersServices()
{
// Arrange
@@ -172,7 +181,8 @@ public sealed class UdpServiceCollectionExtensionsTests
microserviceTransport.Should().BeSameAs(client);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddUdpTransportClient_WithNullConfigure_Works()
{
// Arrange

View File

@@ -16,7 +16,8 @@ public sealed class UdpTransportServerTests
{
#region StartAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_StartsListening()
{
// Arrange
@@ -33,7 +34,8 @@ public sealed class UdpTransportServerTests
await server.StopAsync(CancellationToken.None);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_AfterDispose_ThrowsObjectDisposedException()
{
// Arrange
@@ -52,7 +54,8 @@ public sealed class UdpTransportServerTests
#region StopAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StopAsync_StopsServer()
{
// Arrange
@@ -67,7 +70,8 @@ public sealed class UdpTransportServerTests
server.ConnectionCount.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StopAsync_ClearsConnections()
{
// Arrange
@@ -86,7 +90,8 @@ public sealed class UdpTransportServerTests
#region GetConnectionState Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetConnectionState_UnknownConnection_ReturnsNull()
{
// Arrange
@@ -108,7 +113,8 @@ public sealed class UdpTransportServerTests
#region RemoveConnection Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RemoveConnection_UnknownConnection_DoesNotThrow()
{
// Arrange
@@ -130,7 +136,8 @@ public sealed class UdpTransportServerTests
#region SendFrameAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendFrameAsync_UnknownConnection_ThrowsInvalidOperationException()
{
// Arrange
@@ -156,7 +163,8 @@ public sealed class UdpTransportServerTests
await server.StopAsync(CancellationToken.None);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SendFrameAsync_AfterDispose_ThrowsObjectDisposedException()
{
// Arrange
@@ -182,7 +190,8 @@ public sealed class UdpTransportServerTests
#region GetConnections Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetConnections_InitiallyEmpty()
{
// Arrange
@@ -204,7 +213,8 @@ public sealed class UdpTransportServerTests
#region DisposeAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DisposeAsync_CanBeCalledMultipleTimes()
{
// Arrange
@@ -228,7 +238,8 @@ public sealed class UdpTransportServerTests
#region Event Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnConnection_EventCanBeSubscribed()
{
// Arrange
@@ -247,13 +258,15 @@ public sealed class UdpTransportServerTests
await server.StopAsync(CancellationToken.None);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OnFrame_EventCanBeSubscribed()
{
// Arrange
var options = Options.Create(new UdpTransportOptions { Port = 0 });
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
using StellaOps.TestKit;
Frame? receivedFrame = null;
server.OnFrame += (id, frame) => receivedFrame = frame;

View File

@@ -22,7 +22,8 @@ public class CallgraphIngestionTests : IClassFixture<SignalsTestFactory>
this.factory = factory;
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("java")]
[InlineData("nodejs")]
[InlineData("python")]
@@ -74,7 +75,8 @@ public class CallgraphIngestionTests : IClassFixture<SignalsTestFactory>
Assert.Equal(body.SchemaVersion, manifest.SchemaVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ingest_UnsupportedLanguage_ReturnsBadRequest()
{
using var client = factory.CreateClient();
@@ -86,7 +88,8 @@ public class CallgraphIngestionTests : IClassFixture<SignalsTestFactory>
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ingest_InvalidArtifactContent_ReturnsBadRequest()
{
using var client = factory.CreateClient();
@@ -98,7 +101,8 @@ public class CallgraphIngestionTests : IClassFixture<SignalsTestFactory>
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ingest_InvalidGraphStructure_ReturnsUnprocessableEntity()
{
using var client = factory.CreateClient();
@@ -111,7 +115,8 @@ public class CallgraphIngestionTests : IClassFixture<SignalsTestFactory>
Assert.True((int)response.StatusCode >= 400);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Ingest_SameComponentUpsertsDocument()
{
using var client = factory.CreateClient();
@@ -128,6 +133,7 @@ public class CallgraphIngestionTests : IClassFixture<SignalsTestFactory>
Assert.Equal(HttpStatusCode.Accepted, secondResponse.StatusCode);
using var scope = factory.Services.CreateScope();
using StellaOps.TestKit;
var repo = scope.ServiceProvider.GetRequiredService<ICallgraphRepository>();
var doc = await repo.GetByIdAsync((await secondResponse.Content.ReadFromJsonAsync<CallgraphIngestResponse>())!.CallgraphId, CancellationToken.None);

View File

@@ -17,10 +17,12 @@ public class SignalsApiTests : IClassFixture<SignalsTestFactory>
this.factory = factory;
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Callgraph_Ingest_Response_Includes_Extended_Fields()
{
using var client = factory.CreateClient();
using StellaOps.TestKit;
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write signals:read");
var req = CallgraphIngestionTests.CreateRequest("java", component: "api-test", version: "1.2.3");

View File

@@ -17,7 +17,8 @@ public class SyntheticRuntimeProbeTests : IClassFixture<SignalsTestFactory>
this.factory = factory;
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyntheticProbe_Generates_Runtime_Facts_And_Scoring()
{
using var client = factory.CreateClient();
@@ -47,6 +48,7 @@ public class SyntheticRuntimeProbeTests : IClassFixture<SignalsTestFactory>
Assert.Equal(HttpStatusCode.OK, factRes.StatusCode);
var factJson = await factRes.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(factJson);
using StellaOps.TestKit;
Assert.True(doc.RootElement.TryGetProperty("states", out var states));
Assert.True(states.GetArrayLength() > 0);
Assert.True(doc.RootElement.TryGetProperty("runtimeFacts", out var runtimeFacts));

Some files were not shown because too many files have changed in this diff Show More