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

@@ -2,11 +2,13 @@ using System.Text;
using System.Text.Json;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Canonical.Json.Tests;
public class CanonJsonTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_SameInput_ProducesSameHash()
{
var obj = new { foo = "bar", baz = 42, nested = new { x = 1, y = 2 } };
@@ -18,7 +20,8 @@ public class CanonJsonTests
Assert.Equal(CanonJson.Sha256Hex(bytes1), CanonJson.Sha256Hex(bytes2));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_SortsKeysAlphabetically()
{
var obj = new { z = 3, a = 1, m = 2 };
@@ -28,7 +31,8 @@ public class CanonJsonTests
Assert.Matches(@"\{""a"":1,""m"":2,""z"":3\}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_HandlesNestedObjects()
{
var obj = new { outer = new { z = 9, a = 1 }, inner = new { b = 2 } };
@@ -39,7 +43,8 @@ public class CanonJsonTests
Assert.Contains(@"""outer"":{""a"":1,""z"":9}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_HandlesArrays()
{
var obj = new { items = new[] { 3, 1, 2 } };
@@ -49,7 +54,8 @@ public class CanonJsonTests
Assert.Contains(@"""items"":[3,1,2]", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_HandlesNullValues()
{
var obj = new { name = "test", value = (string?)null };
@@ -58,7 +64,8 @@ public class CanonJsonTests
Assert.Contains(@"""value"":null", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_HandlesBooleans()
{
var obj = new { enabled = true, disabled = false };
@@ -68,7 +75,8 @@ public class CanonJsonTests
Assert.Contains(@"""enabled"":true", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_HandlesDecimals()
{
var obj = new { value = 3.14159, integer = 42 };
@@ -78,7 +86,8 @@ public class CanonJsonTests
Assert.Contains(@"""value"":3.14159", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_HandlesEmptyObject()
{
var obj = new { };
@@ -87,7 +96,8 @@ public class CanonJsonTests
Assert.Equal("{}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_HandlesEmptyArray()
{
var obj = new { items = Array.Empty<int>() };
@@ -96,7 +106,8 @@ public class CanonJsonTests
Assert.Equal(@"{""items"":[]}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_WithCustomOptions_UsesOptions()
{
var obj = new { MyProperty = "test" };
@@ -109,7 +120,8 @@ public class CanonJsonTests
Assert.Contains(@"""my_property"":""test""", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_RawJsonBytes_SortsKeys()
{
var rawJson = Encoding.UTF8.GetBytes(@"{""z"":3,""a"":1}");
@@ -119,7 +131,8 @@ public class CanonJsonTests
Assert.Equal(@"{""a"":1,""z"":3}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Sha256Hex_ProducesLowercaseHex()
{
var bytes = Encoding.UTF8.GetBytes("test");
@@ -128,7 +141,8 @@ public class CanonJsonTests
Assert.Matches(@"^[0-9a-f]{64}$", hash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Sha256Hex_ProducesConsistentHash()
{
var bytes = Encoding.UTF8.GetBytes("deterministic input");
@@ -139,7 +153,8 @@ public class CanonJsonTests
Assert.Equal(hash1, hash2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Sha256Prefixed_IncludesPrefix()
{
var bytes = Encoding.UTF8.GetBytes("test");
@@ -149,7 +164,8 @@ public class CanonJsonTests
Assert.Equal(71, hash.Length); // "sha256:" (7) + 64 hex chars
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Hash_CanonicalizesAndHashes()
{
var obj = new { z = 3, a = 1 };
@@ -161,7 +177,8 @@ public class CanonJsonTests
Assert.Matches(@"^[0-9a-f]{64}$", hash1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HashPrefixed_CanonicalizesAndHashesWithPrefix()
{
var obj = new { name = "test" };
@@ -171,7 +188,8 @@ public class CanonJsonTests
Assert.StartsWith("sha256:", hash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DifferentObjects_ProduceDifferentHashes()
{
var obj1 = new { value = 1 };
@@ -183,7 +201,8 @@ public class CanonJsonTests
Assert.NotEqual(hash1, hash2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void KeyOrderDoesNotAffectHash()
{
// These should produce the same hash because keys are sorted
@@ -198,7 +217,8 @@ public class CanonJsonTests
CanonJson.Sha256Hex(canonical2));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_DeeplyNestedStructure()
{
var obj = new
@@ -219,7 +239,8 @@ public class CanonJsonTests
Assert.Contains(@"""a"":{""nested"":{""a"":1,""b"":2}}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_ArrayOfObjects_SortsObjectKeys()
{
// Use raw JSON to test mixed object shapes in array
@@ -232,7 +253,8 @@ public class CanonJsonTests
Assert.Contains(@"{""a"":1,""b"":2}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_UnicodeStrings()
{
var obj = new { greeting = "Привет мир", emoji = "🚀" };
@@ -249,7 +271,8 @@ public class CanonJsonTests
Assert.Contains("emoji", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Canonicalize_SpecialCharactersInStrings()
{
var obj = new { path = "C:\\Users\\test", quote = "He said \"hello\"" };

View File

@@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Canonical.Json.Tests;
/// <summary>
@@ -13,20 +14,23 @@ public class CanonVersionTests
{
#region Version Constants
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void V1_HasExpectedValue()
{
Assert.Equal("stella:canon:v1", CanonVersion.V1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VersionFieldName_HasUnderscorePrefix()
{
Assert.Equal("_canonVersion", CanonVersion.VersionFieldName);
Assert.StartsWith("_", CanonVersion.VersionFieldName);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Current_EqualsV1()
{
Assert.Equal(CanonVersion.V1, CanonVersion.Current);
@@ -36,35 +40,40 @@ public class CanonVersionTests
#region IsVersioned Detection
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsVersioned_VersionedJson_ReturnsTrue()
{
var json = """{"_canonVersion":"stella:canon:v1","foo":"bar"}"""u8;
Assert.True(CanonVersion.IsVersioned(json));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsVersioned_LegacyJson_ReturnsFalse()
{
var json = """{"foo":"bar"}"""u8;
Assert.False(CanonVersion.IsVersioned(json));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsVersioned_EmptyJson_ReturnsFalse()
{
var json = "{}"u8;
Assert.False(CanonVersion.IsVersioned(json));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsVersioned_TooShort_ReturnsFalse()
{
var json = """{"_ca":"v"}"""u8;
Assert.False(CanonVersion.IsVersioned(json));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsVersioned_WrongFieldName_ReturnsFalse()
{
var json = """{"_version":"stella:canon:v1","foo":"bar"}"""u8;
@@ -75,28 +84,32 @@ public class CanonVersionTests
#region ExtractVersion
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractVersion_VersionedJson_ReturnsVersion()
{
var json = """{"_canonVersion":"stella:canon:v1","foo":"bar"}"""u8;
Assert.Equal("stella:canon:v1", CanonVersion.ExtractVersion(json));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractVersion_CustomVersion_ReturnsVersion()
{
var json = """{"_canonVersion":"custom:v2","foo":"bar"}"""u8;
Assert.Equal("custom:v2", CanonVersion.ExtractVersion(json));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractVersion_LegacyJson_ReturnsNull()
{
var json = """{"foo":"bar"}"""u8;
Assert.Null(CanonVersion.ExtractVersion(json));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExtractVersion_EmptyVersion_ReturnsNull()
{
var json = """{"_canonVersion":"","foo":"bar"}"""u8;
@@ -107,7 +120,8 @@ public class CanonVersionTests
#region CanonicalizeVersioned
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_IncludesVersionMarker()
{
var obj = new { foo = "bar" };
@@ -118,7 +132,8 @@ public class CanonVersionTests
Assert.Contains("\"foo\":\"bar\"", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_VersionMarkerIsFirst()
{
var obj = new { aaa = 1, zzz = 2 };
@@ -131,7 +146,8 @@ public class CanonVersionTests
Assert.True(versionIndex < aaaIndex);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_SortsOtherKeys()
{
var obj = new { z = 3, a = 1, m = 2 };
@@ -142,7 +158,8 @@ public class CanonVersionTests
Assert.Matches(@"\{""_canonVersion"":""[^""]+"",""a"":1,""m"":2,""z"":3\}", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_CustomVersion_UsesProvidedVersion()
{
var obj = new { foo = "bar" };
@@ -152,14 +169,16 @@ public class CanonVersionTests
Assert.Contains("\"_canonVersion\":\"custom:v99\"", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_NullVersion_ThrowsArgumentException()
{
var obj = new { foo = "bar" };
Assert.ThrowsAny<ArgumentException>(() => CanonJson.CanonicalizeVersioned(obj, null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_EmptyVersion_ThrowsArgumentException()
{
var obj = new { foo = "bar" };
@@ -170,7 +189,8 @@ public class CanonVersionTests
#region Hash Difference (Versioned vs Legacy)
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HashVersioned_DiffersFromLegacyHash()
{
var obj = new { foo = "bar", count = 42 };
@@ -181,7 +201,8 @@ public class CanonVersionTests
Assert.NotEqual(legacyHash, versionedHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HashVersionedPrefixed_DiffersFromLegacyHashPrefixed()
{
var obj = new { foo = "bar", count = 42 };
@@ -194,7 +215,8 @@ public class CanonVersionTests
Assert.StartsWith("sha256:", legacyHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HashVersioned_SameInput_ProducesSameHash()
{
var obj = new { foo = "bar", count = 42 };
@@ -205,7 +227,8 @@ public class CanonVersionTests
Assert.Equal(hash1, hash2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HashVersioned_DifferentVersions_ProduceDifferentHashes()
{
var obj = new { foo = "bar" };
@@ -220,7 +243,8 @@ public class CanonVersionTests
#region Determinism
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_SameInput_ProducesSameBytes()
{
var obj = new { name = "test", value = 123, nested = new { x = 1, y = 2 } };
@@ -231,7 +255,8 @@ public class CanonVersionTests
Assert.Equal(bytes1, bytes2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_DifferentPropertyOrder_ProducesSameBytes()
{
// Create two objects with same properties but defined in different order
@@ -247,7 +272,8 @@ public class CanonVersionTests
Assert.Equal(bytes1, bytes2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_StableAcrossMultipleCalls()
{
var obj = new { id = Guid.Parse("12345678-1234-1234-1234-123456789012"), name = "stable" };
@@ -264,7 +290,8 @@ public class CanonVersionTests
#region Golden File / Snapshot Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_KnownInput_ProducesKnownOutput()
{
// Golden test: exact output for known input to detect algorithm changes
@@ -276,7 +303,8 @@ public class CanonVersionTests
Assert.Equal("""{"_canonVersion":"stella:canon:v1","message":"hello","number":42}""", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HashVersioned_KnownInput_ProducesKnownHash()
{
// Golden test: exact hash for known input to detect algorithm changes
@@ -294,7 +322,8 @@ public class CanonVersionTests
Assert.Equal(hash, hash2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_NestedObject_ProducesCorrectOutput()
{
var obj = new
@@ -313,7 +342,8 @@ public class CanonVersionTests
#region Backward Compatibility
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanVersion_CanDistinguishLegacyFromVersioned()
{
var obj = new { foo = "bar" };
@@ -325,7 +355,8 @@ public class CanonVersionTests
Assert.True(CanonVersion.IsVersioned(versioned));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LegacyCanonicalize_StillWorks()
{
// Ensure we haven't broken the legacy canonicalize method
@@ -341,7 +372,8 @@ public class CanonVersionTests
#region Edge Cases
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_EmptyObject_IncludesOnlyVersion()
{
var obj = new { };
@@ -351,7 +383,8 @@ public class CanonVersionTests
Assert.Equal("""{"_canonVersion":"stella:canon:v1"}""", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_WithSpecialCharacters_HandlesCorrectly()
{
var obj = new { message = "hello\nworld", special = "quote:\"test\"" };
@@ -365,7 +398,8 @@ public class CanonVersionTests
Assert.Equal("stella:canon:v1", parsed.GetProperty("_canonVersion").GetString());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanonicalizeVersioned_WithUnicodeCharacters_HandlesCorrectly()
{
var obj = new { greeting = "こんにちは", emoji = "🚀" };

View File

@@ -12,6 +12,7 @@ using StellaOps.Cryptography.Plugin.EIDAS.DependencyInjection;
using StellaOps.Cryptography.Plugin.EIDAS.Models;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Cryptography.Plugin.EIDAS.Tests;
public class EidasCryptoProviderTests
@@ -70,13 +71,15 @@ public class EidasCryptoProviderTests
?? throw new InvalidOperationException("Failed to resolve EidasCryptoProvider");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Provider_Name_IsEidas()
{
Assert.Equal("eidas", _provider.Name);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(CryptoCapability.Signing, "ECDSA-P256", true)]
[InlineData(CryptoCapability.Signing, "ECDSA-P384", true)]
[InlineData(CryptoCapability.Signing, "ECDSA-P521", true)]
@@ -94,19 +97,22 @@ public class EidasCryptoProviderTests
Assert.Equal(expected, result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetPasswordHasher_ThrowsNotSupported()
{
Assert.Throws<NotSupportedException>(() => _provider.GetPasswordHasher("PBKDF2"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetHasher_ThrowsNotSupported()
{
Assert.Throws<NotSupportedException>(() => _provider.GetHasher("SHA256"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetSigner_ReturnsEidasSigner()
{
var keyRef = new CryptoKeyReference("test-key-local");
@@ -117,7 +123,8 @@ public class EidasCryptoProviderTests
Assert.Equal("ECDSA-P256", signer.AlgorithmId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpsertSigningKey_AddsKey()
{
var keyRef = new CryptoKeyReference("test-upsert");
@@ -134,7 +141,8 @@ public class EidasCryptoProviderTests
Assert.Contains(keys, k => k.Reference.KeyId == "test-upsert");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RemoveSigningKey_RemovesKey()
{
var keyRef = new CryptoKeyReference("test-remove");
@@ -153,14 +161,16 @@ public class EidasCryptoProviderTests
Assert.DoesNotContain(_provider.GetSigningKeys(), k => k.Reference.KeyId == "test-remove");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RemoveSigningKey_ReturnsFalseForNonExistentKey()
{
var removed = _provider.RemoveSigningKey("non-existent-key");
Assert.False(removed);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SignAsync_WithLocalKey_ReturnsSignature()
{
// Note: This test will use the stub implementation
@@ -176,7 +186,8 @@ public class EidasCryptoProviderTests
Assert.NotEmpty(signature);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyAsync_WithLocalKey_ReturnsTrue()
{
// Note: This test will use the stub implementation
@@ -192,7 +203,8 @@ public class EidasCryptoProviderTests
Assert.True(isValid);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SignAsync_WithTspKey_ReturnsSignature()
{
// Note: This test will use the stub TSP implementation
@@ -208,7 +220,8 @@ public class EidasCryptoProviderTests
Assert.NotEmpty(signature);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExportPublicJsonWebKey_ReturnsStubJwk()
{
var keyRef = new CryptoKeyReference("test-key-local");
@@ -226,7 +239,8 @@ public class EidasCryptoProviderTests
public class EidasDependencyInjectionTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddEidasCryptoProviders_RegistersServices()
{
var services = new ServiceCollection();
@@ -249,7 +263,8 @@ public class EidasDependencyInjectionTests
Assert.IsType<EidasCryptoProvider>(provider);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddEidasCryptoProviders_WithAction_RegistersServices()
{
var services = new ServiceCollection();

View File

@@ -15,13 +15,15 @@ namespace StellaOps.Cryptography.Plugin.SmRemote.Tests;
public class SmRemoteHttpProviderTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Service_EndToEnd_SignsAndVerifies()
{
Environment.SetEnvironmentVariable("SM_SOFT_ALLOWED", "1");
using var app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(_ => { });
using StellaOps.TestKit;
var client = app.CreateClient();
var status = await client.GetFromJsonAsync<SmStatusResponse>("/status");
status.Should().NotBeNull();
@@ -39,7 +41,8 @@ public class SmRemoteHttpProviderTests
verify!.Valid.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SignsAndVerifiesViaHttp()
{
var handler = new StubHandler();

View File

@@ -10,6 +10,7 @@ using StellaOps.Cryptography;
using StellaOps.Cryptography.Plugin.SmSoft;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Cryptography.Plugin.SmSoft.Tests;
/// <summary>
@@ -38,13 +39,15 @@ public class Sm2ComplianceTests
?? throw new InvalidOperationException("Failed to resolve SmSoftCryptoProvider");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Provider_Name_IsCnSmSoft()
{
Assert.Equal("cn.sm.soft", _provider.Name);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(CryptoCapability.Signing, "SM2", true)]
[InlineData(CryptoCapability.Verification, "SM2", true)]
[InlineData(CryptoCapability.ContentHashing, "SM3", true)]
@@ -56,13 +59,15 @@ public class Sm2ComplianceTests
Assert.Equal(expected, result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetPasswordHasher_ThrowsNotSupported()
{
Assert.Throws<NotSupportedException>(() => _provider.GetPasswordHasher("PBKDF2"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetHasher_WithSm3_ReturnsSm3Hasher()
{
var hasher = _provider.GetHasher("SM3");
@@ -70,13 +75,15 @@ public class Sm2ComplianceTests
Assert.Equal("SM3", hasher.AlgorithmId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetHasher_WithInvalidAlgorithm_Throws()
{
Assert.Throws<InvalidOperationException>(() => _provider.GetHasher("SHA256"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Sm3_ComputeHash_EmptyInput_ReturnsCorrectHash()
{
// OSCCA GM/T 0004-2012 test vector for empty string
@@ -88,7 +95,8 @@ public class Sm2ComplianceTests
Assert.Equal("1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b", hash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Sm3_ComputeHash_AbcInput_ReturnsCorrectHash()
{
// OSCCA GM/T 0004-2012 test vector for "abc"
@@ -100,7 +108,8 @@ public class Sm2ComplianceTests
Assert.Equal("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0", hash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Sm3_ComputeHash_LongInput_ReturnsCorrectHash()
{
// OSCCA GM/T 0004-2012 test vector for 64-byte string
@@ -113,7 +122,8 @@ public class Sm2ComplianceTests
Assert.Equal("debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732", hash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Sm2_SignAndVerify_WithTestKey_Succeeds()
{
// Note: This test uses the existing BouncyCastle SM2 implementation
@@ -157,7 +167,8 @@ public class Sm2ComplianceTests
Assert.False(isInvalid);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Sm2_ExportPublicJsonWebKey_ReturnsValidJwk()
{
var keyPair = GenerateTestSm2KeyPair();

View File

@@ -2,11 +2,13 @@ using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Cryptography.PluginLoader.Tests;
public class CryptoPluginLoaderTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_WithNullConfiguration_ThrowsArgumentNullException()
{
// Arrange & Act
@@ -17,7 +19,8 @@ public class CryptoPluginLoaderTests
.WithParameterName("configuration");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadProviders_WithEmptyEnabledList_ReturnsEmptyCollection()
{
// Arrange
@@ -38,7 +41,8 @@ public class CryptoPluginLoaderTests
providers.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadProviders_WithMissingManifest_ThrowsFileNotFoundException()
{
// Arrange
@@ -58,7 +62,8 @@ public class CryptoPluginLoaderTests
.WithMessage("*manifest.json*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadProviders_WithRequireAtLeastOneAndNoProviders_ThrowsCryptoPluginLoadException()
{
// Arrange
@@ -80,7 +85,8 @@ public class CryptoPluginLoaderTests
.WithMessage("*at least one provider*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void LoadProviders_WithDisabledPattern_FiltersMatchingPlugins()
{
// Arrange
@@ -115,7 +121,8 @@ public class CryptoPluginLoaderTests
public class CryptoPluginConfigurationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_SetsDefaultValues()
{
// Arrange & Act
@@ -133,7 +140,8 @@ public class CryptoPluginConfigurationTests
public class CryptoPluginManifestTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CryptoPluginDescriptor_WithRequiredProperties_IsValid()
{
// Arrange & Act

View File

@@ -9,7 +9,8 @@ namespace StellaOps.Cryptography.Tests;
public class PolicyProvidersTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FipsSoft_Signs_And_Verifies_Es256()
{
Environment.SetEnvironmentVariable("FIPS_SOFT_ALLOWED", "1");
@@ -32,13 +33,15 @@ public class PolicyProvidersTests
provider.GetHasher(HashAlgorithms.Sha256).ComputeHash(data).Length.Should().Be(32);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EidasSoft_Signs_And_Verifies_Es384()
{
Environment.SetEnvironmentVariable("EIDAS_SOFT_ALLOWED", "1");
var provider = new EidasSoftCryptoProvider();
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP384);
using StellaOps.TestKit;
var key = new CryptoSigningKey(
new CryptoKeyReference("eidas-es384"),
SignatureAlgorithms.Es384,
@@ -55,7 +58,8 @@ public class PolicyProvidersTests
provider.GetHasher(HashAlgorithms.Sha384).ComputeHash(data).Length.Should().Be(48);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void KcmvpHashOnly_Computes_Hash()
{
Environment.SetEnvironmentVariable("KCMVP_HASH_ALLOWED", "1");

View File

@@ -9,6 +9,7 @@ using StellaOps.Cryptography;
using StellaOps.Cryptography.Plugin.PqSoft;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Cryptography.Tests;
public class PqSoftCryptoProviderTests
@@ -20,7 +21,8 @@ public class PqSoftCryptoProviderTests
private const string FalconPrivate = "CADAE9CBC9D6BC+DE88+D+9A+7EKAE9/E9B96FI+BF/83BI/AE/++CG7+4AEDE+E76CC8A+++E8CABBDB7/CCA9C4BFD/6G62CBDBHAAF4/BGBJD+BAGA/89JFC+9AB9FG78+EA8EDE/C+D78/J/8B7+/BEF+CBD7+/7F596AADGD/F/EE+DB/A++7CFI/D/A//+9/E+AA/7+D99A988C7ACJ/B+F+BADAB98KFBBEJ+D7+A8EBD/75A9/99BI9BE8A8D/IC/638/A8/6CH+A5CAB+/I9CDG76ALB/ABD947E579AA6BA5/9B5AFCDA7KE97A5DB/FACE//D/F+MD8978/AEE7F3B++C3BEB7C+H/CBE+188EDAD+8+C9EE9AF6IG/4+CCEFFD5HF+7FE/E+/B76+LI8G/A/777A898E98CG8AB8H5EE9CJE+A/+I5DAHD8BFCD+76+9B+/7H7FEA8/A/BHA+JB+D7DE3CFF8B+E898BI6B8FA6//BBA8BFGFB8F9/F+A+BD4A/MA+CBC9+IA/E7D+AC/DFD+DA8B9+CF6BBB9/L2+/A+FEF6+ECB9FC866E2/5DA+C/9C1+HC/AF88A+FFB/C//97A7/BBDD/AHCE8+FC9EBA88H9B7B6AA48BD+DFCC8+/58/GG8CAF+D8AAH/CK/KJACB7/6ICDE8F/DC/ABF6+HEzDHBGBC4B9D+CBDA/++/+AE69FBB7ABCC89785CDFGD6A9/ADKD9C5BD895CC/DFBAD45AAFAB6+EI79C+BBDGK+HC46E9CB7AD+B977/ECC+A/+3ABC+8G878CHGBCG9DDD2+97C7+6DCHCE/6AEG6E0B+AADBDIF8DA39B/85C9//6C9+6G/+BFEAAD57/H/BCB9DJ/AF+ABBH+AABG8EEA/9B8FED6D/D9E9BGEMC+GFBE+9A6+A8JFE7CAAD9BDCBAEC7BADBBF2/EAEB/GKBHC/D/FDDA89//EA/B6/5/GB/AD6A6AE74GAF+CAFd4f/wwCEBw0CS0R9trX5BPh+x4BAcYl+wUJ/xH55e0ICe0IDgINBBf8ABoVJPr/Ggb4HvIvBf3JAxz3/foCDgv4+hm31hkK7QsG5+LmKwUoxBoTAvMgFN/eAB/8+/Ah3/kD9gDpB/X1+A8czukx8w/yGAfw9+vV/fITJQMZGRwCBOL15tMJ9RMJG/732hbU0wcuKPk76/wM8gcK2B//K93j6Ojo6hj7A/8G9cQDE+bpA/In9Q0IDtAxEwH38hQ+CfMj++Tw/vX7Afoe6/gqz/f0AwIIENkU+u/1AggxF+z07AkM1hra5QYnMOvn9wv84O7lFSUx+Q0JChEY9gzqAvz/Lfz3BQj5GgQi/xcYLAvvA9AS9gAL+Qzl8hfk8wgVDx/96eHcH98aIPYPKhjV8vvkIiMELAUm1dcP8+gh7egiAhQK/AsW9dD9AQsD9gMNBxj6HO8Q/xvzBQkSDPEC6P30Hw3X0iX37uUFORr16hzd7+VC2/H2AxbtJxUEGusbJf7iDuTtG+MrFAHSz//m9P4KEQj95wTq//wS+vvWJwbnFgnyDvIRJ+TxGQX2FCgAzfPMAAPdCQoDAy7fuv8OFSIPBv0G6wbftwHuJPspKPnm+vgS3tT1Fe7w3QXT5g4L5DPg6AEVCPX3GEUpIh7xFtssp/n2MAn+LSQF/MTrDMc=";
private const string FalconPublic = "Jv6LkSCSV25Fk8XuSAZVAYuFsivvFkrde2eUhjeNOIi7FnEOVhDy2tulOoFJYB1eLnKWcfcB4B6iWvuQvZ+NRHYbZJNnlG/JrcCTSQFybqYY5tv7TAYHcozO9Qe6Nju25ThfuANwomnNsCWsizGs4xO2IdZ1inooJGnmcCAhIsa8WvXVIBhoSlSjV/Nxjmw7c5KSFUqUiQ6KFn8dELZNK/10OLBzBMWQIrmCi+0NmUieiKcVk1MdeKJFslEISCHi1vDiUpgQFPTH2MAsOqz591JFAD7FyWarfGLh6Rgd1WwZAWtWxpAnSXGkY9ADO0R+wXnuIoiEOQi1axW0UwjMZCoivmoJf5ZsEn4CyfenMNKqe5K+Z7fiw5Vdjoh4ZqoH0gzbWYZoGlRpC6XmWlQIwbnBaQRFRdkphuMTTBK8NKDHxod1xzEJrXmAiG4xZQGXvopGQMNdqGja8sOcncMN4lN4dp9VDOK4algsVBglrOuwinwp+GxLz+rcHwV0hkNUswmpWOPplUZLJtlmeJ1YJjRk8egce6oQRYde4QsWIIyoKYOfh/7hV2NYdEcIPCknRZsPiXyqcGoFqAZTMlZblFQ5A1e+1FyQ8DPQeKzlO8A1JRAJ7A6eioHBKJ1zYZblxiwyggHOOoXK1HbpU4PvJROQ6MW08ZK4tAkXZI0lw3s50ViSBFd5zI8xtFrzpjRLOZzMY0dkVDGLlr8+9gsBCU48dfyMga6lrbgVJVcW6rFgPD9lG09mhDArGR8opVeSm6WR60Wq9RRI9WCmDFL+9N/Z8YsdGgo8y2Fx9mNz88DRd0hGyQ/pZTluDuuWEHxx0UCyzWofcq7TkT2pLGq5cBBB3klRWK4CgpRns+HRQhuWeEEtH8Pm4pyJ1NYkXeEbOohkk5NgEmfCF/NmlORdZDxAQSi9r/bvBnlGdS6aV9BvpWugvJV3wcDFvQVjR4HT0IlBwwpHyQhJdcrKsebSaynfQfySaUgMOgUGARAn4jl34sZZia3UCQhpO7yYcacxXRwHSK5nTGePiOoiWCfZYwGQ92k0aLO5fIL4GWo6VmrQu/HiJrF1/GCHa9ozFvcW4wHkejos9Nkf8at5c7QoRoNfEl4zkKjvSKivpZyetasiOuf/6UW06A8m2xfHU0ixQB1emzSGRpGP0gWwicUbdE1tcakAKtMV2I3NolR5wO4=";
private const string FalconSignature = "OfUX6CmZ14QRj1xzxUFf657ng0GtWjPuT3HVLNV+es9pwMAMZe98/2POuD5YQ9z85g2fkSKqDx9pgyiMLG4lTERJ/qorc9KgSKomkUgS1JL+78qdsnkwUS4cA5BDHB0ipP3thSLVkEWt74ReIz2TDDfbzqzX/ZgFf5ZiYPhsZBSQ0fq23cqYYKTWZ4rjDOkiawuFEMUmCaEp9amx9essVLyEcTqQMCfjdFbJ3LTPweD8Q2SGECYNL7PdUbqJWRWWGR9LE14EpNLNckyFPP3uEYevr1CYF+kbUd3nNyXPG0zHKe2KEffTP3EcmO/x5HjGXZSUdrElbQU0FjYiwm8s2vOO7+IfOm8Fj4rslTRlDuuwDHGB40q57r4P3JMv61tqa0ynSglolqkOBp7EZfAyb7sMwrU6arvXpIDlzCmagDW8DGw7SPUxcV1CZ/XvoNs1jfjWQfVOiTrFDWOvm6WSRYZnLkHaVKUN7CMbFI1lzaNdu2UfMQKA6zsRs+NwVeZGJ26q0rvMmkIvVuMUlaav+Rp/kKKIZVM7Sj7LnrkU2zrII+WSsy9JaZmbHi+XxIXUi9XeT2Xc5anTE9manN4x/wvpByQogntxZb6ej8ZXD1t/a6sPQKc9MJWX9UVl2/wr07iHww0317LqLLC2FNHclYcLR7KK/GEVnAvrDn94r4wY88nsusNDmli3Thh1C6UNg17gjsazteq6m0y+Yrzrb3ZmMzl0bkganSnNTOIsgwyCbDo0d8/WTKjMeoNWQQhKUJM2elWn2TvmaFdOZHMV6Iuw17ociTXHko3J/MZKncSPjYE12pw70mHZ2B+ZxvM1Cj4RUbLHfYz0ANGuSbUGjlgzmewiNXJS4KOsY2K6PYcZ";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Dilithium3_Signs_And_Verifies()
{
var provider = CreateProvider();
@@ -46,7 +48,8 @@ public class PqSoftCryptoProviderTests
(await signer.VerifyAsync(data, signature)).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Falcon512_Signs_And_Verifies()
{
var provider = CreateProvider();

View File

@@ -14,7 +14,8 @@ namespace StellaOps.Cryptography.Tests;
public class SimRemoteProviderTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Supports_DefaultAlgorithms_CoversStandardIds()
{
var handler = new NoopHandler();
@@ -27,7 +28,8 @@ public class SimRemoteProviderTests
Assert.True(provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Dilithium3));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SignAndVerify_WithSimProvider_Succeeds()
{
// Arrange
@@ -47,6 +49,7 @@ public class SimRemoteProviderTests
services.AddSingleton<SimRemoteProvider>();
using var providerScope = services.BuildServiceProvider();
using StellaOps.TestKit;
var provider = providerScope.GetRequiredService<SimRemoteProvider>();
var signer = provider.GetSigner("pq.sim", new CryptoKeyReference("sim-key"));
var payload = Encoding.UTF8.GetBytes("hello-sim");

View File

@@ -18,7 +18,8 @@ public class EvidenceRecordTests
#region ComputeEvidenceId
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_ValidInputs_ReturnsSha256Prefixed()
{
var subjectId = "sha256:abc123";
@@ -34,7 +35,8 @@ public class EvidenceRecordTests
Assert.Equal(71, evidenceId.Length); // "sha256:" + 64 hex chars
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_SameInputs_ReturnsSameId()
{
var subjectId = "sha256:abc123";
@@ -46,7 +48,8 @@ public class EvidenceRecordTests
Assert.Equal(id1, id2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_DifferentSubjects_ReturnsDifferentIds()
{
var payload = Encoding.UTF8.GetBytes("""{"vulnerability":"CVE-2021-44228"}""");
@@ -57,7 +60,8 @@ public class EvidenceRecordTests
Assert.NotEqual(id1, id2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_DifferentTypes_ReturnsDifferentIds()
{
var subjectId = "sha256:abc123";
@@ -69,7 +73,8 @@ public class EvidenceRecordTests
Assert.NotEqual(id1, id2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_DifferentPayloads_ReturnsDifferentIds()
{
var subjectId = "sha256:abc123";
@@ -82,7 +87,8 @@ public class EvidenceRecordTests
Assert.NotEqual(id1, id2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_DifferentProvenance_ReturnsDifferentIds()
{
var subjectId = "sha256:abc123";
@@ -108,7 +114,8 @@ public class EvidenceRecordTests
Assert.NotEqual(id1, id2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_NullSubject_ThrowsArgumentException()
{
var payload = Encoding.UTF8.GetBytes("""{"data":"test"}""");
@@ -116,7 +123,8 @@ public class EvidenceRecordTests
EvidenceRecord.ComputeEvidenceId(null!, EvidenceType.Scan, payload, TestProvenance));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_EmptySubject_ThrowsArgumentException()
{
var payload = Encoding.UTF8.GetBytes("""{"data":"test"}""");
@@ -124,7 +132,8 @@ public class EvidenceRecordTests
EvidenceRecord.ComputeEvidenceId("", EvidenceType.Scan, payload, TestProvenance));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_NullProvenance_ThrowsArgumentNullException()
{
var payload = Encoding.UTF8.GetBytes("""{"data":"test"}""");
@@ -136,7 +145,8 @@ public class EvidenceRecordTests
#region Create Factory Method
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Create_ValidInputs_ReturnsRecordWithComputedId()
{
var subjectId = "sha256:abc123";
@@ -158,7 +168,8 @@ public class EvidenceRecordTests
Assert.Null(record.ExternalPayloadCid);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Create_WithSignatures_IncludesSignatures()
{
var subjectId = "sha256:abc123";
@@ -184,7 +195,8 @@ public class EvidenceRecordTests
Assert.Equal("key-123", record.Signatures[0].SignerId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Create_WithExternalCid_IncludesCid()
{
var subjectId = "sha256:abc123";
@@ -198,6 +210,7 @@ public class EvidenceRecordTests
"reachability/v1",
externalPayloadCid: "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
using StellaOps.TestKit;
Assert.Equal("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", record.ExternalPayloadCid);
}
@@ -205,7 +218,8 @@ public class EvidenceRecordTests
#region VerifyIntegrity
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerifyIntegrity_ValidRecord_ReturnsTrue()
{
var record = EvidenceRecord.Create(
@@ -218,7 +232,8 @@ public class EvidenceRecordTests
Assert.True(record.VerifyIntegrity());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerifyIntegrity_TamperedPayload_ReturnsFalse()
{
var originalPayload = Encoding.UTF8.GetBytes("""{"vulnerability":"CVE-2021-44228"}""");
@@ -237,7 +252,8 @@ public class EvidenceRecordTests
Assert.False(tampered.VerifyIntegrity());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerifyIntegrity_TamperedSubject_ReturnsFalse()
{
var record = EvidenceRecord.Create(
@@ -256,7 +272,8 @@ public class EvidenceRecordTests
#region Determinism
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Create_SameInputs_ProducesSameEvidenceId()
{
var subjectId = "sha256:abc123";
@@ -271,7 +288,8 @@ public class EvidenceRecordTests
Assert.Single(ids);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeEvidenceId_EmptyPayload_Works()
{
var id = EvidenceRecord.ComputeEvidenceId(

View File

@@ -6,6 +6,7 @@ using System.Collections.Immutable;
using StellaOps.Evidence.Core;
using StellaOps.Evidence.Core.Adapters;
using StellaOps.TestKit;
namespace StellaOps.Evidence.Core.Tests;
public sealed class ExceptionApplicationAdapterTests
@@ -24,7 +25,8 @@ public sealed class ExceptionApplicationAdapterTests
};
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithValidApplication_ReturnsTrue()
{
var application = CreateValidApplication();
@@ -34,7 +36,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.True(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithNullApplication_ReturnsFalse()
{
var result = _adapter.CanConvert(null!);
@@ -42,7 +45,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithEmptyExceptionId_ReturnsFalse()
{
var application = CreateValidApplication() with { ExceptionId = "" };
@@ -52,7 +56,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithEmptyFindingId_ReturnsFalse()
{
var application = CreateValidApplication() with { FindingId = "" };
@@ -62,7 +67,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_CreatesSingleRecord()
{
var application = CreateValidApplication();
@@ -72,7 +78,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.Single(results);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasExceptionType()
{
var application = CreateValidApplication();
@@ -82,7 +89,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.Equal(EvidenceType.Exception, results[0].EvidenceType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasCorrectSubjectNodeId()
{
var application = CreateValidApplication();
@@ -92,7 +100,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.Equal(_subjectNodeId, results[0].SubjectNodeId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasNonEmptyPayload()
{
var application = CreateValidApplication();
@@ -102,7 +111,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasPayloadSchemaVersion()
{
var application = CreateValidApplication();
@@ -112,7 +122,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.Equal("1.0.0", results[0].PayloadSchemaVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasEmptySignatures()
{
var application = CreateValidApplication();
@@ -122,7 +133,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.Empty(results[0].Signatures);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_UsesProvidedProvenance()
{
var application = CreateValidApplication();
@@ -133,7 +145,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.Equal(_provenance.GeneratorVersion, results[0].Provenance.GeneratorVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasUniqueEvidenceId()
{
var application = CreateValidApplication();
@@ -144,7 +157,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.NotEmpty(results[0].EvidenceId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithNullSubjectNodeId_ThrowsArgumentNullException()
{
var application = CreateValidApplication();
@@ -153,7 +167,8 @@ public sealed class ExceptionApplicationAdapterTests
_adapter.Convert(application, null!, _provenance));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithEmptySubjectNodeId_ThrowsArgumentException()
{
var application = CreateValidApplication();
@@ -162,7 +177,8 @@ public sealed class ExceptionApplicationAdapterTests
_adapter.Convert(application, "", _provenance));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithNullProvenance_ThrowsArgumentNullException()
{
var application = CreateValidApplication();
@@ -171,7 +187,8 @@ public sealed class ExceptionApplicationAdapterTests
_adapter.Convert(application, _subjectNodeId, null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithVulnerabilityId_IncludesInPayload()
{
var application = CreateValidApplication() with { VulnerabilityId = "CVE-2024-9999" };
@@ -181,7 +198,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithEvaluationRunId_IncludesInPayload()
{
var runId = Guid.NewGuid();
@@ -192,7 +210,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithPolicyBundleDigest_IncludesInPayload()
{
var application = CreateValidApplication() with { PolicyBundleDigest = "sha256:policy123" };
@@ -202,7 +221,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithMetadata_IncludesInPayload()
{
var metadata = ImmutableDictionary<string, string>.Empty
@@ -216,7 +236,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_DifferentApplications_ProduceDifferentEvidenceIds()
{
var app1 = CreateValidApplication() with { ExceptionId = "exc-001" };
@@ -228,7 +249,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.NotEqual(results1[0].EvidenceId, results2[0].EvidenceId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_SameApplicationTwice_ProducesSameEvidenceId()
{
var application = CreateValidApplication();
@@ -239,7 +261,8 @@ public sealed class ExceptionApplicationAdapterTests
Assert.Equal(results1[0].EvidenceId, results2[0].EvidenceId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_AllStatusTransitions_Supported()
{
var transitions = new[]

View File

@@ -1,6 +1,7 @@
using System.Text;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Evidence.Core.Tests;
/// <summary>
@@ -28,7 +29,8 @@ public class InMemoryEvidenceStoreTests
#region StoreAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreAsync_ValidEvidence_ReturnsEvidenceId()
{
var evidence = CreateTestEvidence("sha256:subject1");
@@ -39,7 +41,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(1, _store.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreAsync_DuplicateEvidence_IsIdempotent()
{
var evidence = CreateTestEvidence("sha256:subject1");
@@ -50,7 +53,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(1, _store.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreAsync_NullEvidence_ThrowsArgumentNullException()
{
await Assert.ThrowsAsync<ArgumentNullException>(() => _store.StoreAsync(null!));
@@ -60,7 +64,8 @@ public class InMemoryEvidenceStoreTests
#region StoreBatchAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreBatchAsync_MultipleRecords_StoresAll()
{
var evidence1 = CreateTestEvidence("sha256:subject1");
@@ -73,7 +78,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(3, _store.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreBatchAsync_WithDuplicates_SkipsDuplicates()
{
var evidence1 = CreateTestEvidence("sha256:subject1");
@@ -86,7 +92,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(2, _store.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreBatchAsync_EmptyList_ReturnsZero()
{
var count = await _store.StoreBatchAsync([]);
@@ -99,7 +106,8 @@ public class InMemoryEvidenceStoreTests
#region GetByIdAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_ExistingEvidence_ReturnsEvidence()
{
var evidence = CreateTestEvidence("sha256:subject1");
@@ -112,7 +120,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(evidence.SubjectNodeId, result.SubjectNodeId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_NonExistingEvidence_ReturnsNull()
{
var result = await _store.GetByIdAsync("sha256:nonexistent");
@@ -120,13 +129,15 @@ public class InMemoryEvidenceStoreTests
Assert.Null(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_NullId_ThrowsArgumentException()
{
await Assert.ThrowsAnyAsync<ArgumentException>(() => _store.GetByIdAsync(null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_EmptyId_ThrowsArgumentException()
{
await Assert.ThrowsAnyAsync<ArgumentException>(() => _store.GetByIdAsync(""));
@@ -136,7 +147,8 @@ public class InMemoryEvidenceStoreTests
#region GetBySubjectAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBySubjectAsync_ExistingSubject_ReturnsAllEvidence()
{
var subjectId = "sha256:subject1";
@@ -151,7 +163,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(2, results.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBySubjectAsync_WithTypeFilter_ReturnsFilteredResults()
{
var subjectId = "sha256:subject1";
@@ -167,7 +180,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(EvidenceType.Scan, results[0].EvidenceType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBySubjectAsync_NonExistingSubject_ReturnsEmptyList()
{
var results = await _store.GetBySubjectAsync("sha256:nonexistent");
@@ -179,7 +193,8 @@ public class InMemoryEvidenceStoreTests
#region GetByTypeAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByTypeAsync_ExistingType_ReturnsMatchingEvidence()
{
await _store.StoreAsync(CreateTestEvidence("sha256:sub1", EvidenceType.Scan));
@@ -192,7 +207,8 @@ public class InMemoryEvidenceStoreTests
Assert.All(results, r => Assert.Equal(EvidenceType.Scan, r.EvidenceType));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByTypeAsync_WithLimit_RespectsLimit()
{
for (int i = 0; i < 10; i++)
@@ -205,7 +221,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(5, results.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByTypeAsync_NonExistingType_ReturnsEmptyList()
{
await _store.StoreAsync(CreateTestEvidence("sha256:sub1", EvidenceType.Scan));
@@ -219,7 +236,8 @@ public class InMemoryEvidenceStoreTests
#region ExistsAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExistsAsync_ExistingEvidenceForType_ReturnsTrue()
{
var subjectId = "sha256:subject1";
@@ -230,7 +248,8 @@ public class InMemoryEvidenceStoreTests
Assert.True(exists);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExistsAsync_DifferentType_ReturnsFalse()
{
var subjectId = "sha256:subject1";
@@ -241,7 +260,8 @@ public class InMemoryEvidenceStoreTests
Assert.False(exists);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExistsAsync_NonExistingSubject_ReturnsFalse()
{
var exists = await _store.ExistsAsync("sha256:nonexistent", EvidenceType.Scan);
@@ -253,7 +273,8 @@ public class InMemoryEvidenceStoreTests
#region DeleteAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DeleteAsync_ExistingEvidence_ReturnsTrue()
{
var evidence = CreateTestEvidence("sha256:subject1");
@@ -265,7 +286,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(0, _store.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DeleteAsync_NonExistingEvidence_ReturnsFalse()
{
var deleted = await _store.DeleteAsync("sha256:nonexistent");
@@ -273,7 +295,8 @@ public class InMemoryEvidenceStoreTests
Assert.False(deleted);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DeleteAsync_RemovedEvidence_NotRetrievable()
{
var evidence = CreateTestEvidence("sha256:subject1");
@@ -289,7 +312,8 @@ public class InMemoryEvidenceStoreTests
#region CountBySubjectAsync
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CountBySubjectAsync_MultipleEvidence_ReturnsCorrectCount()
{
var subjectId = "sha256:subject1";
@@ -302,7 +326,8 @@ public class InMemoryEvidenceStoreTests
Assert.Equal(3, count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CountBySubjectAsync_NoEvidence_ReturnsZero()
{
var count = await _store.CountBySubjectAsync("sha256:nonexistent");
@@ -314,7 +339,8 @@ public class InMemoryEvidenceStoreTests
#region Clear
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Clear_RemovesAllEvidence()
{
await _store.StoreAsync(CreateTestEvidence("sha256:sub1"));
@@ -329,7 +355,8 @@ public class InMemoryEvidenceStoreTests
#region Cancellation
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StoreAsync_CancelledToken_ThrowsOperationCancelledException()
{
var cts = new CancellationTokenSource();
@@ -341,7 +368,8 @@ public class InMemoryEvidenceStoreTests
_store.StoreAsync(evidence, cts.Token));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByIdAsync_CancelledToken_ThrowsOperationCancelledException()
{
var cts = new CancellationTokenSource();

View File

@@ -6,6 +6,7 @@ using System.Collections.Immutable;
using StellaOps.Evidence.Core;
using StellaOps.Evidence.Core.Adapters;
using StellaOps.TestKit;
namespace StellaOps.Evidence.Core.Tests;
public sealed class ProofSegmentAdapterTests
@@ -24,7 +25,8 @@ public sealed class ProofSegmentAdapterTests
};
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithValidSegment_ReturnsTrue()
{
var segment = CreateValidSegment();
@@ -34,7 +36,8 @@ public sealed class ProofSegmentAdapterTests
Assert.True(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithNullSegment_ReturnsFalse()
{
var result = _adapter.CanConvert(null!);
@@ -42,7 +45,8 @@ public sealed class ProofSegmentAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithEmptySegmentId_ReturnsFalse()
{
var segment = CreateValidSegment() with { SegmentId = "" };
@@ -52,7 +56,8 @@ public sealed class ProofSegmentAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithEmptyInputHash_ReturnsFalse()
{
var segment = CreateValidSegment() with { InputHash = "" };
@@ -62,7 +67,8 @@ public sealed class ProofSegmentAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_CreatesSingleRecord()
{
var segment = CreateValidSegment();
@@ -72,7 +78,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Single(results);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasCorrectSubjectNodeId()
{
var segment = CreateValidSegment();
@@ -82,7 +89,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Equal(_subjectNodeId, results[0].SubjectNodeId);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("SbomSlice", EvidenceType.Artifact)]
[InlineData("Match", EvidenceType.Scan)]
[InlineData("Reachability", EvidenceType.Reachability)]
@@ -98,7 +106,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Equal(expectedType, results[0].EvidenceType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_UnknownSegmentType_DefaultsToCustomType()
{
var segment = CreateValidSegment() with { SegmentType = "UnknownType" };
@@ -108,7 +117,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Equal(EvidenceType.Custom, results[0].EvidenceType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasNonEmptyPayload()
{
var segment = CreateValidSegment();
@@ -118,7 +128,8 @@ public sealed class ProofSegmentAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasPayloadSchemaVersion()
{
var segment = CreateValidSegment();
@@ -128,7 +139,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Equal("proof-segment/v1", results[0].PayloadSchemaVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasEmptySignatures()
{
var segment = CreateValidSegment();
@@ -138,7 +150,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Empty(results[0].Signatures);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_UsesProvidedProvenance()
{
var segment = CreateValidSegment();
@@ -149,7 +162,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Equal(_provenance.GeneratorVersion, results[0].Provenance.GeneratorVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordHasUniqueEvidenceId()
{
var segment = CreateValidSegment();
@@ -160,7 +174,8 @@ public sealed class ProofSegmentAdapterTests
Assert.NotEmpty(results[0].EvidenceId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithNullSubjectNodeId_ThrowsArgumentNullException()
{
var segment = CreateValidSegment();
@@ -169,7 +184,8 @@ public sealed class ProofSegmentAdapterTests
_adapter.Convert(segment, null!, _provenance));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithNullProvenance_ThrowsArgumentNullException()
{
var segment = CreateValidSegment();
@@ -178,7 +194,8 @@ public sealed class ProofSegmentAdapterTests
_adapter.Convert(segment, _subjectNodeId, null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_DifferentSegments_ProduceDifferentEvidenceIds()
{
var segment1 = CreateValidSegment() with { SegmentId = "seg-001" };
@@ -190,7 +207,8 @@ public sealed class ProofSegmentAdapterTests
Assert.NotEqual(results1[0].EvidenceId, results2[0].EvidenceId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_SameSegmentTwice_ProducesSameEvidenceId()
{
var segment = CreateValidSegment();
@@ -201,7 +219,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Equal(results1[0].EvidenceId, results2[0].EvidenceId);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("Pending")]
[InlineData("Verified")]
[InlineData("Partial")]
@@ -216,7 +235,8 @@ public sealed class ProofSegmentAdapterTests
Assert.Single(results);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithToolInfo_IncludesInPayload()
{
var segment = CreateValidSegment() with
@@ -230,7 +250,8 @@ public sealed class ProofSegmentAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithPrevSegmentHash_IncludesInPayload()
{
var segment = CreateValidSegment() with { PrevSegmentHash = "sha256:prevhash" };
@@ -240,7 +261,8 @@ public sealed class ProofSegmentAdapterTests
Assert.False(results[0].Payload.IsEmpty);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithSpineId_IncludesInPayload()
{
var segment = CreateValidSegment() with { SpineId = "spine-001" };

View File

@@ -6,6 +6,7 @@ using System.Collections.Immutable;
using StellaOps.Evidence.Core;
using StellaOps.Evidence.Core.Adapters;
using StellaOps.TestKit;
namespace StellaOps.Evidence.Core.Tests;
public sealed class VexObservationAdapterTests
@@ -24,7 +25,8 @@ public sealed class VexObservationAdapterTests
};
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithValidObservation_ReturnsTrue()
{
var observation = CreateValidObservation();
@@ -34,7 +36,8 @@ public sealed class VexObservationAdapterTests
Assert.True(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithNullObservation_ReturnsFalse()
{
var result = _adapter.CanConvert(null!);
@@ -42,7 +45,8 @@ public sealed class VexObservationAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithEmptyObservationId_ReturnsFalse()
{
var observation = CreateValidObservation() with { ObservationId = "" };
@@ -52,7 +56,8 @@ public sealed class VexObservationAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanConvert_WithEmptyProviderId_ReturnsFalse()
{
var observation = CreateValidObservation() with { ProviderId = "" };
@@ -62,7 +67,8 @@ public sealed class VexObservationAdapterTests
Assert.False(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_CreatesObservationLevelRecord()
{
var observation = CreateValidObservation();
@@ -75,7 +81,8 @@ public sealed class VexObservationAdapterTests
Assert.Equal(_subjectNodeId, observationRecord.SubjectNodeId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_CreatesStatementRecordsForEachStatement()
{
var statements = ImmutableArray.Create(
@@ -100,7 +107,8 @@ public sealed class VexObservationAdapterTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithSingleStatement_CreatesCorrectRecords()
{
var observation = CreateValidObservation();
@@ -111,7 +119,8 @@ public sealed class VexObservationAdapterTests
Assert.Equal(2, results.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithEmptyStatements_CreatesOnlyObservationRecord()
{
var observation = CreateValidObservation() with { Statements = [] };
@@ -122,7 +131,8 @@ public sealed class VexObservationAdapterTests
Assert.Equal(EvidenceType.Provenance, results[0].EvidenceType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithSignature_IncludesSignatureInRecords()
{
var signature = new VexObservationSignatureInput
@@ -147,7 +157,8 @@ public sealed class VexObservationAdapterTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithoutSignature_CreatesRecordsWithEmptySignatures()
{
var signature = new VexObservationSignatureInput
@@ -169,7 +180,8 @@ public sealed class VexObservationAdapterTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_UsesProvidedProvenance()
{
var observation = CreateValidObservation();
@@ -183,7 +195,8 @@ public sealed class VexObservationAdapterTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithNullSubjectNodeId_ThrowsArgumentNullException()
{
var observation = CreateValidObservation();
@@ -192,7 +205,8 @@ public sealed class VexObservationAdapterTests
_adapter.Convert(observation, null!, _provenance));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_WithNullProvenance_ThrowsArgumentNullException()
{
var observation = CreateValidObservation();
@@ -201,7 +215,8 @@ public sealed class VexObservationAdapterTests
_adapter.Convert(observation, _subjectNodeId, null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_EachRecordHasUniqueEvidenceId()
{
var statements = ImmutableArray.Create(
@@ -216,7 +231,8 @@ public sealed class VexObservationAdapterTests
Assert.Equal(evidenceIds.Count, evidenceIds.Distinct().Count());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Convert_RecordsHavePayloadSchemaVersion()
{
var observation = CreateValidObservation();

View File

@@ -2,9 +2,11 @@ using System.Text.Json;
using StellaOps.Replay.Core;
using Xunit;
using StellaOps.TestKit;
public class ReplayManifestTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SerializesWithNamespacesAndAnalysis_V1()
{
var manifest = new ReplayManifest
@@ -48,7 +50,8 @@ public class ReplayManifestTests
Assert.Contains("\"namespace\":\"runtime_traces\"", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SerializesWithV2HashFields()
{
var manifest = new ReplayManifest

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using StellaOps.Replay.Core;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Replay.Core.Tests;
/// <summary>
@@ -19,7 +20,8 @@ public class ReplayManifestV2Tests
#region Section 4.1: Minimal Valid Manifest v2
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MinimalValidManifestV2_SerializesCorrectly()
{
var manifest = new ReplayManifest
@@ -67,7 +69,8 @@ public class ReplayManifestV2Tests
#region Section 4.2: Manifest with Runtime Traces
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestWithRuntimeTraces_SerializesCorrectly()
{
var manifest = new ReplayManifest
@@ -116,7 +119,8 @@ public class ReplayManifestV2Tests
#region Section 4.3: Sorting Validation
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SortingValidation_UnsortedGraphs_FailsValidation()
{
var manifest = new ReplayManifest
@@ -151,7 +155,8 @@ public class ReplayManifestV2Tests
Assert.Contains(result.Errors, e => e.ErrorCode == ReplayManifestErrorCodes.UnsortedEntries);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SortingValidation_SortedGraphs_PassesValidation()
{
var manifest = new ReplayManifest
@@ -189,7 +194,8 @@ public class ReplayManifestV2Tests
#region Section 4.4: Invalid Manifest Vectors
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InvalidManifest_MissingSchemaVersion_FailsValidation()
{
var manifest = new ReplayManifest
@@ -204,7 +210,8 @@ public class ReplayManifestV2Tests
Assert.Contains(result.Errors, e => e.ErrorCode == ReplayManifestErrorCodes.MissingVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InvalidManifest_VersionMismatch_WhenV2Required_FailsValidation()
{
var manifest = new ReplayManifest
@@ -219,7 +226,8 @@ public class ReplayManifestV2Tests
Assert.Contains(result.Errors, e => e.ErrorCode == ReplayManifestErrorCodes.VersionMismatch);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InvalidManifest_MissingHashAlg_InV2_FailsValidation()
{
var manifest = new ReplayManifest
@@ -246,7 +254,8 @@ public class ReplayManifestV2Tests
Assert.Contains(result.Errors, e => e.ErrorCode == ReplayManifestErrorCodes.MissingHashAlg);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task InvalidManifest_MissingCasReference_FailsValidation()
{
var casValidator = new InMemoryCasValidator();
@@ -276,7 +285,8 @@ public class ReplayManifestV2Tests
Assert.Contains(result.Errors, e => e.ErrorCode == ReplayManifestErrorCodes.CasNotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task InvalidManifest_HashMismatch_FailsValidation()
{
var casValidator = new InMemoryCasValidator();
@@ -315,7 +325,8 @@ public class ReplayManifestV2Tests
#region Section 5: Migration Path
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeToV2_ConvertsV1ManifestCorrectly()
{
var v1 = new ReplayManifest
@@ -347,7 +358,8 @@ public class ReplayManifestV2Tests
Assert.Equal("sha256", v2.Reachability.Graphs[0].HashAlgorithm);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeToV2_SortsGraphsByUri()
{
var v1 = new ReplayManifest
@@ -373,7 +385,8 @@ public class ReplayManifestV2Tests
#region ReachabilityReplayWriter Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BuildManifestV2_WithValidGraphs_CreatesSortedManifest()
{
var scan = new ReplayScanMetadata { Id = "test-scan" };
@@ -401,7 +414,8 @@ public class ReplayManifestV2Tests
Assert.Equal("cas://graphs/zzzz", manifest.Reachability.Graphs[1].CasUri);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BuildManifestV2_WithLegacySha256_MigratesHashField()
{
var scan = new ReplayScanMetadata { Id = "test-scan" };
@@ -423,7 +437,8 @@ public class ReplayManifestV2Tests
Assert.Equal("sha256", manifest.Reachability.Graphs[0].HashAlgorithm);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BuildManifestV2_InfersHashAlgorithmFromPrefix()
{
var scan = new ReplayScanMetadata { Id = "test-scan" };
@@ -444,7 +459,8 @@ public class ReplayManifestV2Tests
Assert.Equal("blake3-256", manifest.Reachability.Graphs[0].HashAlgorithm);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BuildManifestV2_RequiresAtLeastOneGraph()
{
var scan = new ReplayScanMetadata { Id = "test-scan" };
@@ -460,7 +476,8 @@ public class ReplayManifestV2Tests
#region CodeIdCoverage Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CodeIdCoverage_SerializesWithSnakeCaseKeys()
{
var coverage = new CodeIdCoverage

View File

@@ -6,11 +6,13 @@
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Resolver.Tests;
public class CycleDetectionTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GraphWithMarkedCycleCutEdge_IsValid()
{
// CYCLE-9100-016: Graph with marked cycle-cut edge passes validation
@@ -33,7 +35,8 @@ public class CycleDetectionTests
Assert.True(result.IsValid, $"Expected valid graph. Errors: {string.Join(", ", result.Errors)}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GraphWithUnmarkedCycle_ThrowsInvalidGraphException()
{
// CYCLE-9100-017: Graph with unmarked cycle throws exception
@@ -57,7 +60,8 @@ public class CycleDetectionTests
Assert.Contains(result.Errors, e => e.Contains("Cycle detected without IsCycleCut edge"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GraphWithMultipleCycles_AllMarked_IsValid()
{
// CYCLE-9100-018: Multiple cycles, all marked
@@ -84,7 +88,8 @@ public class CycleDetectionTests
Assert.True(result.IsValid);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GraphWithMultipleCycles_OneUnmarked_HasError()
{
// CYCLE-9100-019: Multiple cycles, one unmarked
@@ -112,7 +117,8 @@ public class CycleDetectionTests
Assert.Single(result.Errors.Where(e => e.Contains("Cycle detected")));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CycleDetection_IsDeterministic()
{
// CYCLE-9100-020: Property test - deterministic detection
@@ -142,7 +148,8 @@ public class CycleDetectionTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CycleCutEdge_IncludedInGraphDigest()
{
// CYCLE-9100-021: Cycle-cut edges affect graph digest

View File

@@ -7,6 +7,7 @@
using System.Text.Json;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Resolver.Tests;
public class DeterministicResolverTests
@@ -15,7 +16,8 @@ public class DeterministicResolverTests
private readonly IGraphOrderer _orderer = new TopologicalGraphOrderer();
private readonly ITrustLatticeEvaluator _evaluator = new DefaultTrustLatticeEvaluator();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Run_SameInputTwice_IdenticalFinalDigest()
{
// RESOLVER-9100-020: Replay test
@@ -31,7 +33,8 @@ public class DeterministicResolverTests
Assert.Equal(result1.TraversalSequence.Length, result2.TraversalSequence.Length);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Run_ShuffledNodesAndEdges_IdenticalFinalDigest()
{
// RESOLVER-9100-021: Permutation test
@@ -60,7 +63,8 @@ public class DeterministicResolverTests
Assert.Equal(result1.FinalDigest, result2.FinalDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Run_IsIdempotent()
{
// RESOLVER-9100-022: Idempotency property test
@@ -76,7 +80,8 @@ public class DeterministicResolverTests
Assert.Equal(result2.FinalDigest, result3.FinalDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Run_TraversalSequence_MatchesTopologicalOrder()
{
// RESOLVER-9100-023: Traversal order test
@@ -106,7 +111,8 @@ public class DeterministicResolverTests
"Root should appear before at least one child in traversal");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ResolutionResult_CanonicalJsonStructure()
{
// RESOLVER-9100-024: Snapshot test for canonical JSON

View File

@@ -6,11 +6,13 @@
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Resolver.Tests;
public class EdgeIdTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EdgeId_ComputedDeterministically()
{
// EDGEID-9100-015: EdgeId computed deterministically
@@ -25,7 +27,8 @@ public class EdgeIdTests
Assert.Equal(64, edgeId1.Value.Length); // SHA256 hex
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EdgeId_OrderingConsistentWithStringOrdering()
{
// EDGEID-9100-016: EdgeId ordering is consistent
@@ -43,7 +46,8 @@ public class EdgeIdTests
Assert.Equal(sorted1, sorted2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GraphHash_ChangesWhenEdgeAddedOrRemoved()
{
// EDGEID-9100-017: Graph hash changes with edge changes
@@ -63,7 +67,8 @@ public class EdgeIdTests
Assert.NotEqual(graph2.GraphDigest, graph3.GraphDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EdgeDelta_CorrectlyIdentifiesChanges()
{
// EDGEID-9100-018: Delta detection identifies changes
@@ -86,7 +91,8 @@ public class EdgeIdTests
Assert.Empty(delta.ModifiedEdges);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EdgeId_IsIdempotent()
{
// EDGEID-9100-019: Property test - idempotent computation

View File

@@ -7,6 +7,7 @@
using System.Text.Json;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Resolver.Tests;
public class FinalDigestTests
@@ -15,7 +16,8 @@ public class FinalDigestTests
private readonly IGraphOrderer _orderer = new TopologicalGraphOrderer();
private readonly ITrustLatticeEvaluator _evaluator = new DefaultTrustLatticeEvaluator();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FinalDigest_IsDeterministic()
{
// DIGEST-9100-018: Same inputs → same digest
@@ -29,7 +31,8 @@ public class FinalDigestTests
Assert.Equal(result1.FinalDigest, result2.FinalDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FinalDigest_ChangesWhenVerdictChanges()
{
// DIGEST-9100-019: FinalDigest changes when any verdict changes
@@ -53,7 +56,8 @@ public class FinalDigestTests
Assert.Equal(64, result1.FinalDigest.Length);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FinalDigest_ChangesWhenGraphChanges()
{
// DIGEST-9100-020: FinalDigest changes when graph changes
@@ -76,7 +80,8 @@ public class FinalDigestTests
Assert.NotEqual(result1.FinalDigest, result2.FinalDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FinalDigest_ChangesWhenPolicyChanges()
{
// DIGEST-9100-021: FinalDigest changes when policy changes
@@ -97,7 +102,8 @@ public class FinalDigestTests
Assert.NotEqual(result1.FinalDigest, result2.FinalDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerificationApi_CorrectlyIdentifiesMatch()
{
// DIGEST-9100-022: Verification API works
@@ -116,7 +122,8 @@ public class FinalDigestTests
Assert.Empty(verification.Differences);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerificationApi_CorrectlyIdentifiesMismatch()
{
// DIGEST-9100-022 continued: Verification API detects mismatch
@@ -137,7 +144,8 @@ public class FinalDigestTests
Assert.NotEmpty(verification.Differences);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FinalDigest_IsCollisionResistant()
{
// DIGEST-9100-024: Property test - different inputs → different digest

View File

@@ -6,11 +6,13 @@
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Resolver.Tests;
public class GraphValidationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NfcNormalization_ProducesConsistentNodeIds()
{
// VALID-9100-021: NFC normalization produces consistent NodeIds
@@ -28,7 +30,8 @@ public class GraphValidationTests
Assert.Equal(nodeId1, nodeId2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EdgeReferencingNonExistentNode_Detected()
{
// VALID-9100-022
@@ -45,7 +48,8 @@ public class GraphValidationTests
Assert.Contains(violations, v => v.ViolationType == "DanglingEdgeDestination");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DuplicateNodeIds_Detected()
{
// VALID-9100-023
@@ -64,7 +68,8 @@ public class GraphValidationTests
Assert.Contains(violations, v => v.ViolationType == "DuplicateNodeId");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DuplicateEdgeIds_Detected()
{
// VALID-9100-024
@@ -86,7 +91,8 @@ public class GraphValidationTests
Assert.Contains(violations, v => v.ViolationType == "DuplicateEdgeId");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ValidGraph_PassesAllChecks()
{
// VALID-9100-027
@@ -106,7 +112,8 @@ public class GraphValidationTests
Assert.Empty(result.Errors);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NfcNormalization_IsIdempotent()
{
// VALID-9100-028: Property test - NFC is idempotent
@@ -121,7 +128,8 @@ public class GraphValidationTests
Assert.Equal(normalized2, normalized3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EmptyGraph_IsValid()
{
var graph = EvidenceGraph.Empty;

View File

@@ -7,11 +7,13 @@
using StellaOps.Resolver.Purity;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Resolver.Tests;
public class RuntimePurityTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ProhibitedTimeProvider_ThrowsOnAccess()
{
// PURITY-9100-021
@@ -20,7 +22,8 @@ public class RuntimePurityTests
Assert.Throws<AmbientAccessViolationException>(() => _ = provider.Now);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ProhibitedEnvironmentAccessor_ThrowsOnAccess()
{
// PURITY-9100-024
@@ -29,7 +32,8 @@ public class RuntimePurityTests
Assert.Throws<AmbientAccessViolationException>(() => accessor.GetVariable("PATH"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InjectedTimeProvider_ReturnsInjectedTime()
{
// PURITY-9100-025
@@ -39,7 +43,8 @@ public class RuntimePurityTests
Assert.Equal(injectedTime, provider.Now);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InjectedEnvironmentAccessor_ReturnsInjectedValues()
{
var vars = new Dictionary<string, string> { { "TEST_VAR", "test_value" } };
@@ -49,7 +54,8 @@ public class RuntimePurityTests
Assert.Null(accessor.GetVariable("NONEXISTENT"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PureEvaluationContext_StrictMode_ThrowsOnAmbientAccess()
{
var context = PureEvaluationContext.CreateStrict();
@@ -57,7 +63,8 @@ public class RuntimePurityTests
Assert.Throws<AmbientAccessViolationException>(() => _ = context.InjectedNow);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PureEvaluationContext_WithInjectedValues_WorksCorrectly()
{
var injectedTime = DateTimeOffset.Parse("2025-12-24T12:00:00Z");
@@ -66,7 +73,8 @@ public class RuntimePurityTests
Assert.Equal(injectedTime, context.InjectedNow);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AmbientAccessViolationException_ContainsDetails()
{
var ex = new AmbientAccessViolationException("Time", "Attempted DateTime.Now access");
@@ -76,7 +84,8 @@ public class RuntimePurityTests
Assert.Contains("Time", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FullResolution_CompletesWithoutAmbientAccess()
{
// PURITY-9100-027: Integration test

View File

@@ -7,11 +7,13 @@
using System.Text.Json;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Resolver.Tests;
public class VerdictDigestTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerdictDigest_IsDeterministic()
{
// VDIGEST-9100-016: Same verdict → same digest
@@ -24,7 +26,8 @@ public class VerdictDigestTests
Assert.Equal(verdict1.VerdictDigest, verdict2.VerdictDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerdictDigest_ChangesWhenStatusChanges()
{
// VDIGEST-9100-017: Digest changes with status
@@ -37,7 +40,8 @@ public class VerdictDigestTests
Assert.NotEqual(passVerdict.VerdictDigest, failVerdict.VerdictDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerdictDigest_ChangesWhenEvidenceChanges()
{
// VDIGEST-9100-018: Digest changes with evidence
@@ -51,7 +55,8 @@ public class VerdictDigestTests
Assert.NotEqual(verdict1.VerdictDigest, verdict2.VerdictDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerdictDelta_CorrectlyIdentifiesChangedVerdicts()
{
// VDIGEST-9100-019: Delta detection identifies changed verdicts
@@ -95,7 +100,8 @@ public class VerdictDigestTests
Assert.Equal(nodeId2, delta.ChangedVerdicts[0].Old.Node);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerdictDelta_HandlesAddedRemovedNodes()
{
// VDIGEST-9100-020: Delta handles added/removed nodes
@@ -136,7 +142,8 @@ public class VerdictDigestTests
Assert.Equal(nodeId2, delta.RemovedVerdicts[0].Node);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerdictDigest_ExcludesItselfFromComputation()
{
// VDIGEST-9100-021: Property test - no recursion

View File

@@ -60,4 +60,48 @@ public static class TestCategories
/// Live tests: Require external services (e.g., Rekor, NuGet feeds). Disabled by default in CI.
/// </summary>
public const string Live = "Live";
// =========================================================================
// Additional categories aligned with test-matrix.yml pipeline
// =========================================================================
/// <summary>
/// Architecture tests: Module dependency rules, naming conventions, forbidden packages.
/// </summary>
public const string Architecture = "Architecture";
/// <summary>
/// Golden tests: Output comparison against known-good reference files.
/// </summary>
public const string Golden = "Golden";
/// <summary>
/// Benchmark tests: BenchmarkDotNet performance measurements. Scheduled runs only.
/// </summary>
public const string Benchmark = "Benchmark";
/// <summary>
/// AirGap tests: Offline/air-gapped environment validation. On-demand only.
/// </summary>
public const string AirGap = "AirGap";
/// <summary>
/// Chaos tests: Fault injection, failure recovery, resilience under adverse conditions.
/// </summary>
public const string Chaos = "Chaos";
/// <summary>
/// Determinism tests: Reproducibility validation, stable ordering, idempotency.
/// </summary>
public const string Determinism = "Determinism";
/// <summary>
/// Resilience tests: Retry policies, circuit breakers, timeout handling.
/// </summary>
public const string Resilience = "Resilience";
/// <summary>
/// Observability tests: OpenTelemetry traces, metrics, structured logging validation.
/// </summary>
public const string Observability = "Observability";
}

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

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