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

@@ -3,11 +3,13 @@ using System.Net;
using StellaOps.Auth.Abstractions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Abstractions.Tests;
public class NetworkMaskMatcherTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_SingleAddress_YieldsHostMask()
{
var mask = NetworkMask.Parse("192.168.1.42");
@@ -17,7 +19,8 @@ public class NetworkMaskMatcherTests
Assert.False(mask.Contains(IPAddress.Parse("192.168.1.43")));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_Cidr_NormalisesHostBits()
{
var mask = NetworkMask.Parse("10.0.15.9/20");
@@ -27,7 +30,8 @@ public class NetworkMaskMatcherTests
Assert.False(mask.Contains(IPAddress.Parse("10.0.32.1")));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Contains_ReturnsFalse_ForMismatchedAddressFamily()
{
var mask = NetworkMask.Parse("192.168.0.0/16");
@@ -35,7 +39,8 @@ public class NetworkMaskMatcherTests
Assert.False(mask.Contains(IPAddress.IPv6Loopback));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Matcher_AllowsAll_WhenStarProvided()
{
var matcher = new NetworkMaskMatcher(new[] { "*" });
@@ -45,7 +50,8 @@ public class NetworkMaskMatcherTests
Assert.True(matcher.IsAllowed(IPAddress.IPv6Loopback));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Matcher_ReturnsFalse_WhenNoMasksConfigured()
{
var matcher = new NetworkMaskMatcher(Array.Empty<string>());
@@ -55,7 +61,8 @@ public class NetworkMaskMatcherTests
Assert.False(matcher.IsAllowed(null));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Matcher_SupportsIpv4AndIpv6Masks()
{
var matcher = new NetworkMaskMatcher(new[] { "192.168.0.0/24", "::1/128" });
@@ -66,7 +73,8 @@ public class NetworkMaskMatcherTests
Assert.False(matcher.IsAllowed(IPAddress.IPv6Any));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Matcher_Throws_ForInvalidEntries()
{
var exception = Assert.Throws<FormatException>(() => new NetworkMaskMatcher(new[] { "invalid-mask" }));

View File

@@ -4,11 +4,13 @@ using System.Security.Claims;
using StellaOps.Auth.Abstractions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Abstractions.Tests;
public class StellaOpsPrincipalBuilderTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizedScopes_AreSortedDeduplicatedLowerCased()
{
var builder = new StellaOpsPrincipalBuilder()
@@ -24,7 +26,8 @@ public class StellaOpsPrincipalBuilderTests
builder.Audiences);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Build_ConstructsClaimsPrincipalWithNormalisedValues()
{
var now = DateTimeOffset.UtcNow;

View File

@@ -4,11 +4,13 @@ using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Abstractions.Tests;
public class StellaOpsProblemResultFactoryTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AuthenticationRequired_ReturnsCanonicalProblem()
{
var result = StellaOpsProblemResultFactory.AuthenticationRequired(instance: "/jobs");
@@ -22,7 +24,8 @@ public class StellaOpsProblemResultFactoryTests
Assert.Equal(details.Detail, details.Extensions["error_description"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InvalidToken_UsesProvidedDetail()
{
var result = StellaOpsProblemResultFactory.InvalidToken("expired refresh token");
@@ -33,7 +36,8 @@ public class StellaOpsProblemResultFactoryTests
Assert.Equal("invalid_token", details.Extensions["error"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InsufficientScope_AddsScopeExtensions()
{
var result = StellaOpsProblemResultFactory.InsufficientScope(

View File

@@ -1,13 +1,15 @@
using StellaOps.Auth.Abstractions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Abstractions.Tests;
#pragma warning disable CS0618
public class StellaOpsScopesTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(StellaOpsScopes.AdvisoryRead)]
[InlineData(StellaOpsScopes.AdvisoryIngest)]
[InlineData(StellaOpsScopes.AdvisoryAiView)]
@@ -73,7 +75,8 @@ public class StellaOpsScopesTests
Assert.Contains(scope, StellaOpsScopes.All);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("Advisory:Read", StellaOpsScopes.AdvisoryRead)]
[InlineData(" VEX:Ingest ", StellaOpsScopes.VexIngest)]
[InlineData("AOC:VERIFY", StellaOpsScopes.AocVerify)]

View File

@@ -564,6 +564,11 @@ public static class StellaOpsScopes
/// </summary>
public const string ExceptionsWrite = "exceptions:write";
/// <summary>
/// Scope granting permission to request exceptions (initiate approval workflow).
/// </summary>
public const string ExceptionsRequest = "exceptions:request";
/// <summary>
/// Scope granting administrative control over Graph resources.
/// </summary>
@@ -684,6 +689,7 @@ public static class StellaOpsScopes
ZastavaAdmin,
ExceptionsRead,
ExceptionsWrite,
ExceptionsRequest,
GraphAdmin
};

View File

@@ -20,7 +20,8 @@ namespace StellaOps.Auth.Client.Tests;
public class ServiceCollectionExtensionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task AddStellaOpsAuthClient_ConfiguresRetryPolicy()
{
var services = new ServiceCollection();
@@ -75,7 +76,8 @@ public class ServiceCollectionExtensionsTests
Assert.Contains(recordedHandlers, handler => handler.GetType().Name.Contains("PolicyHttpMessageHandler", StringComparison.Ordinal));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnsureEgressAllowed_InvokesPolicyWhenAuthorityProvided()
{
var services = new ServiceCollection();
@@ -131,7 +133,8 @@ public class ServiceCollectionExtensionsTests
=> responder(request, cancellationToken);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task AddStellaOpsApiAuthentication_AttachesPatAndTenantHeader()
{
var services = new ServiceCollection();
@@ -177,7 +180,8 @@ public class ServiceCollectionExtensionsTests
Assert.Equal(0, tokenClient.RequestCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task AddStellaOpsApiAuthentication_UsesClientCredentialsWithCaching()
{
var services = new ServiceCollection();
@@ -210,6 +214,7 @@ public class ServiceCollectionExtensionsTests
});
using var provider = services.BuildServiceProvider();
using StellaOps.TestKit;
var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient("notify");
await client.GetAsync("https://notify.example/api");

View File

@@ -2,11 +2,13 @@ using System;
using StellaOps.Auth.Client;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Client.Tests;
public class StellaOpsAuthClientOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_NormalizesScopes()
{
var options = new StellaOpsAuthClientOptions
@@ -26,7 +28,8 @@ public class StellaOpsAuthClientOptionsTests
Assert.Equal<TimeSpan>(options.RetryDelays, options.NormalizedRetryDelays);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_When_AuthorityMissing()
{
var options = new StellaOpsAuthClientOptions();
@@ -36,7 +39,8 @@ public class StellaOpsAuthClientOptionsTests
Assert.Contains("Authority", exception.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_NormalizesRetryDelays()
{
var options = new StellaOpsAuthClientOptions
@@ -54,7 +58,8 @@ public class StellaOpsAuthClientOptionsTests
Assert.Equal<TimeSpan>(options.NormalizedRetryDelays, options.RetryDelays);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_DisabledRetries_ProducesEmptyDelays()
{
var options = new StellaOpsAuthClientOptions
@@ -68,7 +73,8 @@ public class StellaOpsAuthClientOptionsTests
Assert.Empty(options.NormalizedRetryDelays);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_When_OfflineToleranceNegative()
{
var options = new StellaOpsAuthClientOptions

View File

@@ -10,11 +10,13 @@ using Microsoft.Extensions.Time.Testing;
using StellaOps.Auth.Client;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Client.Tests;
public class StellaOpsDiscoveryCacheTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_UsesOfflineFallbackWithinTolerance()
{
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-01-01T00:00:00Z"));

View File

@@ -18,6 +18,7 @@ using Microsoft.Extensions.Time.Testing;
using StellaOps.Auth.Client;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Client.Tests;
/// <summary>
@@ -31,7 +32,8 @@ public class StellaOpsTokenClientTests
{
#region Task 1: Token Issuance Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestPasswordToken_ReturnsResultAndCaches()
{
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-02-01T00:00:00Z"));
@@ -76,7 +78,8 @@ public class StellaOpsTokenClientTests
Assert.Empty(jwks.Keys);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestClientCredentialsToken_ReturnsTokenWithCorrectExpiry()
{
// Arrange
@@ -121,7 +124,8 @@ public class StellaOpsTokenClientTests
Assert.Equal(expectedExpiry, result.ExpiresAt);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestClientCredentialsToken_WithCustomScope_UsesCustomScope()
{
// Arrange
@@ -160,7 +164,8 @@ public class StellaOpsTokenClientTests
Assert.Contains("policy.evaluate", result.Scopes);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestClientCredentialsToken_WithoutClientId_ThrowsInvalidOperation()
{
// Arrange
@@ -186,7 +191,8 @@ public class StellaOpsTokenClientTests
client.RequestClientCredentialsTokenAsync());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestPasswordToken_WithAdditionalParameters_IncludesParameters()
{
// Arrange
@@ -237,7 +243,8 @@ public class StellaOpsTokenClientTests
#region Task 2: Token Validation/Rejection Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestPasswordToken_WhenServerReturnsError_ThrowsInvalidOperation()
{
// Arrange
@@ -278,7 +285,8 @@ public class StellaOpsTokenClientTests
Assert.Contains("401", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestPasswordToken_WhenResponseMissingAccessToken_ThrowsInvalidOperation()
{
// Arrange
@@ -313,7 +321,8 @@ public class StellaOpsTokenClientTests
Assert.Contains("access_token", ex.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CachedToken_WhenExpired_ReturnsNull()
{
// Arrange
@@ -338,7 +347,8 @@ public class StellaOpsTokenClientTests
// The cache may have already evicted it or it won't be returned
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestPasswordToken_DefaultsToBearer_WhenTokenTypeNotProvided()
{
// Arrange
@@ -375,7 +385,8 @@ public class StellaOpsTokenClientTests
Assert.Equal("Bearer", result.TokenType); // Defaults to Bearer
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RequestPasswordToken_DefaultsTo3600ExpiresIn_WhenNotProvided()
{
// Arrange

View File

@@ -6,11 +6,13 @@ using Microsoft.Extensions.Time.Testing;
using StellaOps.Auth.Client;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.Client.Tests;
public class TokenCacheTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task InMemoryTokenCache_ExpiresEntries()
{
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-01-01T00:00:00Z"));
@@ -28,7 +30,8 @@ public class TokenCacheTests
Assert.Null(retrieved);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FileTokenCache_PersistsEntries()
{
var directory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));

View File

@@ -12,7 +12,8 @@ namespace StellaOps.Auth.ServerIntegration.Tests;
public class ServiceCollectionExtensionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddStellaOpsResourceServerAuthentication_ConfiguresJwtBearer()
{
var configuration = new ConfigurationBuilder()
@@ -31,6 +32,7 @@ public class ServiceCollectionExtensionsTests
using var provider = services.BuildServiceProvider();
using StellaOps.TestKit;
var resourceOptions = provider.GetRequiredService<IOptionsMonitor<StellaOpsResourceServerOptions>>().CurrentValue;
var jwtOptions = provider.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().Get(StellaOpsAuthenticationDefaults.AuthenticationScheme);

View File

@@ -3,11 +3,13 @@ using System.Net;
using StellaOps.Auth.ServerIntegration;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.ServerIntegration.Tests;
public class StellaOpsResourceServerOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_NormalisesCollections()
{
var options = new StellaOpsResourceServerOptions
@@ -43,7 +45,8 @@ public class StellaOpsResourceServerOptionsTests
Assert.True(options.BypassMatcher.IsAllowed(IPAddress.IPv6Loopback));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_When_AuthorityMissing()
{
var options = new StellaOpsResourceServerOptions();

View File

@@ -4,11 +4,13 @@ using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.ServerIntegration.Tests;
public class StellaOpsResourceServerPoliciesTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddObservabilityResourcePolicies_RegistersExpectedPolicies()
{
var options = new AuthorizationOptions();
@@ -28,7 +30,8 @@ public class StellaOpsResourceServerPoliciesTests
AssertPolicy(options, StellaOpsResourceServerPolicies.ExportAdmin, StellaOpsScopes.ExportAdmin);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AddPacksResourcePolicies_RegistersExpectedPolicies()
{
var options = new AuthorizationOptions();

View File

@@ -16,11 +16,13 @@ using StellaOps.Cryptography.Audit;
using OpenIddict.Abstractions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Auth.ServerIntegration.Tests;
public class StellaOpsScopeAuthorizationHandlerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Succeeds_WhenScopePresent()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -52,7 +54,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.False(string.IsNullOrWhiteSpace(record.CorrelationId));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenTenantMismatch()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -83,7 +86,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("true", GetPropertyValue(record, "resource.tenant.mismatch"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Succeeds_WhenBypassNetworkMatches()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -107,7 +111,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("true", GetPropertyValue(record, "resource.authorization.bypass"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenScopeMissingAndNoBypass()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -130,7 +135,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("false", GetPropertyValue(record, "principal.authenticated"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenDefaultScopeMissing()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -159,7 +165,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal(StellaOpsScopes.PolicyRun, GetPropertyValue(record, "resource.scopes.missing"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Succeeds_WhenDefaultScopePresent()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -187,7 +194,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("true", GetPropertyValue(record, "principal.authenticated"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenIncidentAuthTimeMissing()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -220,7 +228,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("Sev1 drill", GetPropertyValue(record, "incident.reason"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenIncidentAuthTimeStale()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -256,7 +265,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("Sev1 drill", GetPropertyValue(record, "incident.reason"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Succeeds_WhenIncidentFreshAuthValid()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -291,7 +301,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("Sev1 drill", GetPropertyValue(record, "incident.reason"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenBackfillMetadataMissing()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -321,7 +332,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("false", GetPropertyValue(record, "backfill.metadata_satisfied"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Succeeds_WhenBackfillMetadataPresent()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -354,7 +366,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal("INC-741", GetPropertyValue(record, "backfill.ticket"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenPackApprovalMetadataMissing()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -385,7 +398,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal(StellaOpsScopes.PacksApprove, Assert.Single(record.Scopes));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Fails_WhenPackApprovalFreshAuthStale()
{
var optionsMonitor = CreateOptionsMonitor(options =>
@@ -421,7 +435,8 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal(StellaOpsScopes.PacksApprove, Assert.Single(record.Scopes));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HandleRequirement_Succeeds_WhenPackApprovalMetadataPresent()
{
var optionsMonitor = CreateOptionsMonitor(options =>

View File

@@ -7,6 +7,7 @@ using Microsoft.Extensions.Options;
using StellaOps.Authority.Plugins.Abstractions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugin.Ldap.Tests;
public class LdapPluginOptionsTests : IDisposable
@@ -19,7 +20,8 @@ public class LdapPluginOptionsTests : IDisposable
Directory.CreateDirectory(tempRoot);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_ResolvesRelativeClientCertificateAndBundlePaths()
{
var configPath = Path.Combine(tempRoot, "ldap.yaml");
@@ -53,7 +55,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Equal(expectedBundle, options.Connection.TrustStore.BundlePath);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenHostMissing()
{
var options = new LdapPluginOptions
@@ -70,7 +73,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Contains("connection.host", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenBundleModeWithoutPath()
{
var options = new LdapPluginOptions
@@ -95,7 +99,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Contains("connection.trustStore.bundlePath", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenClientCertificateIncomplete()
{
var options = new LdapPluginOptions
@@ -119,7 +124,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Contains("clientCertificate.pfxPath", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenTlsDisabledWithoutEnvToggle()
{
var options = ValidOptions();
@@ -132,7 +138,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Contains("allowInsecureWithEnvToggle", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenTlsDisabledWithoutEnvironmentVariable()
{
var options = ValidOptions();
@@ -145,7 +152,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Contains(LdapSecurityOptions.AllowInsecureEnvironmentVariable, ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_AllowsTlsDisabledWhenEnvToggleSet()
{
const string envVar = "STELLAOPS_LDAP_ALLOW_INSECURE";
@@ -167,7 +175,8 @@ public class LdapPluginOptionsTests : IDisposable
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenRequireTlsWithoutTlsConfiguration()
{
var options = ValidOptions();
@@ -182,7 +191,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Contains("requires TLS", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_AllowsRequireTlsWithStartTls()
{
var options = ValidOptions();
@@ -195,7 +205,8 @@ public class LdapPluginOptionsTests : IDisposable
options.Validate("corp-ldap");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenRequireClientCertificateWithoutConfiguration()
{
var options = ValidOptions();
@@ -207,7 +218,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Contains("requireClientCertificate", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_ParsesLdapsSchemeAndSetsPort()
{
var options = ValidOptions();
@@ -220,7 +232,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Equal(1636, options.Connection.Port);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_DeduplicatesCipherSuites()
{
var options = ValidOptions();
@@ -234,7 +247,8 @@ public class LdapPluginOptionsTests : IDisposable
item => Assert.Equal("TLS_AES_128_GCM_SHA256", item));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Registrar_BindsOptionsAndAppliesNormalization()
{
var services = new ServiceCollection();
@@ -283,7 +297,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Equal("TLS_AES_256_GCM_SHA384", Assert.Single(options.Security.AllowedCipherSuites));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_TrimsClaimsConfiguration()
{
var options = ValidOptions();
@@ -317,7 +332,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Equal(0, options.Claims.Cache.MaxEntries);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_AllowsClaimsCacheWithoutExplicitCollection()
{
var options = ValidOptions();
@@ -330,7 +346,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Equal("ldap_claims_cache_corp-ldap", options.Claims.Cache.ResolveCollectionName("corp-ldap"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_ClientProvisioningOptions()
{
var options = ValidOptions();
@@ -346,7 +363,8 @@ public class LdapPluginOptionsTests : IDisposable
Assert.Equal("audit_log", options.ClientProvisioning.AuditMirror.CollectionName);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenClientProvisioningMissingContainer()
{
var options = ValidOptions();

View File

@@ -10,11 +10,13 @@ using StellaOps.Authority.Storage.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardClientProvisioningStoreTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateOrUpdateAsync_HashesSecretAndPersistsDocument()
{
var store = new TrackingClientStore();
@@ -45,7 +47,8 @@ public class StandardClientProvisioningStoreTests
Assert.Contains("scopea", descriptor.AllowedScopes);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateOrUpdateAsync_NormalisesTenant()
{
var store = new TrackingClientStore();
@@ -71,7 +74,8 @@ public class StandardClientProvisioningStoreTests
Assert.NotNull(descriptor);
Assert.Equal("tenant-alpha", descriptor!.Tenant);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateOrUpdateAsync_StoresAudiences()
{
var store = new TrackingClientStore();
@@ -99,7 +103,8 @@ public class StandardClientProvisioningStoreTests
Assert.Equal(new[] { "attestor", "signer" }, descriptor!.AllowedAudiences.OrderBy(value => value, StringComparer.Ordinal));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateOrUpdateAsync_MapsCertificateBindings()
{
var store = new TrackingClientStore();

View File

@@ -3,11 +3,13 @@ using System.IO;
using StellaOps.Authority.Plugin.Standard;
using StellaOps.Cryptography;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardPluginOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_AllowsBootstrapWhenCredentialsProvided()
{
var options = new StandardPluginOptions
@@ -23,7 +25,8 @@ public class StandardPluginOptionsTests
options.Validate("standard");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenBootstrapUserIncomplete()
{
var options = new StandardPluginOptions
@@ -39,7 +42,8 @@ public class StandardPluginOptionsTests
Assert.Contains("bootstrapUser", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenLockoutWindowMinutesInvalid()
{
var options = new StandardPluginOptions
@@ -56,7 +60,8 @@ public class StandardPluginOptionsTests
Assert.Contains("lockout.windowMinutes", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_ResolvesRelativeTokenSigningDirectory()
{
var configDir = Path.Combine(Path.GetTempPath(), "stellaops-standard-plugin", Guid.NewGuid().ToString("N"));
@@ -84,7 +89,8 @@ public class StandardPluginOptionsTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_PreservesAbsoluteTokenSigningDirectory()
{
var absolute = Path.Combine(Path.GetTempPath(), "stellaops-standard-plugin", Guid.NewGuid().ToString("N"), "keys");
@@ -98,7 +104,8 @@ public class StandardPluginOptionsTests
Assert.Equal(Path.GetFullPath(absolute), options.TokenSigning.KeyDirectory);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenPasswordHashingMemoryInvalid()
{
var options = new StandardPluginOptions
@@ -113,7 +120,8 @@ public class StandardPluginOptionsTests
Assert.Contains("memory", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenPasswordHashingIterationsInvalid()
{
var options = new StandardPluginOptions
@@ -128,7 +136,8 @@ public class StandardPluginOptionsTests
Assert.Contains("iteration", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Validate_Throws_WhenPasswordHashingParallelismInvalid()
{
var options = new StandardPluginOptions

View File

@@ -21,7 +21,8 @@ namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardPluginRegistrarTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Register_ConfiguresIdentityProviderAndSeedsBootstrapUser()
{
var client = new InMemoryClient();
@@ -83,7 +84,8 @@ public class StandardPluginRegistrarTests
Assert.True(verification.User?.RequiresPasswordReset);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Register_LogsWarning_WhenPasswordPolicyWeaker()
{
var client = new InMemoryClient();
@@ -128,7 +130,8 @@ public class StandardPluginRegistrarTests
entry.Message.Contains("weaker password policy", StringComparison.OrdinalIgnoreCase));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Register_ForcesPasswordCapability_WhenManifestMissing()
{
var client = new InMemoryClient();
@@ -160,7 +163,8 @@ public class StandardPluginRegistrarTests
Assert.True(plugin.Capabilities.SupportsClientProvisioning);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Register_Throws_WhenBootstrapConfigurationIncomplete()
{
var client = new InMemoryClient();
@@ -194,7 +198,8 @@ public class StandardPluginRegistrarTests
Assert.Throws<InvalidOperationException>(() => scope.ServiceProvider.GetRequiredService<IIdentityProviderPlugin>());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Register_NormalizesTokenSigningKeyDirectory()
{
var client = new InMemoryClient();
@@ -231,6 +236,7 @@ public class StandardPluginRegistrarTests
registrar.Register(new AuthorityPluginRegistrationContext(services, pluginContext, configuration));
using var provider = services.BuildServiceProvider();
using StellaOps.TestKit;
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<StandardPluginOptions>>();
var options = optionsMonitor.Get("standard");

View File

@@ -12,6 +12,7 @@ using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Cryptography;
using StellaOps.Cryptography.Audit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardUserCredentialStoreTests : IAsyncLifetime
@@ -60,7 +61,8 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
NullLogger<StandardUserCredentialStore>.Instance);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyPasswordAsync_ReturnsSuccess_ForValidCredentials()
{
auditLogger.Reset();
@@ -87,7 +89,8 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
Assert.Null(auditEntry.FailureCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyPasswordAsync_EnforcesLockout_AfterRepeatedFailures()
{
auditLogger.Reset();
@@ -135,7 +138,8 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
Assert.Contains(lastAudit.Properties, property => property.Name == "plugin.retry_after_seconds");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyPasswordAsync_RehashesLegacyHashesToArgon2()
{
auditLogger.Reset();
@@ -179,7 +183,8 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
Assert.StartsWith("$argon2id$", updated!.PasswordHash, StringComparison.Ordinal);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyPasswordAsync_RecordsAudit_ForUnknownUser()
{
auditLogger.Reset();

View File

@@ -1,23 +1,27 @@
using System;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugins.Abstractions.Tests;
public class AuthorityClientRegistrationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Throws_WhenClientIdMissing()
{
Assert.Throws<ArgumentException>(() => new AuthorityClientRegistration(string.Empty, false, null, null));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_RequiresSecret_ForConfidentialClients()
{
Assert.Throws<ArgumentException>(() => new AuthorityClientRegistration("cli", true, null, null));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void WithClientSecret_ReturnsCopy()
{
var registration = new AuthorityClientRegistration("cli", false, null, null, tenant: "Tenant-Alpha");

View File

@@ -2,11 +2,13 @@ using System;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Cryptography.Audit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugins.Abstractions.Tests;
public class AuthorityCredentialVerificationResultTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Success_SetsUserAndClearsFailure()
{
var user = new AuthorityUserDescriptor("subject-1", "user", "User", false);
@@ -25,13 +27,15 @@ public class AuthorityCredentialVerificationResultTests
Assert.Collection(result.AuditProperties, property => Assert.Equal("test", property.Name));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Success_Throws_WhenUserNull()
{
Assert.Throws<ArgumentNullException>(() => AuthorityCredentialVerificationResult.Success(null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failure_SetsFailureCode()
{
var auditProperties = new[]

View File

@@ -1,11 +1,13 @@
using System;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugins.Abstractions.Tests;
public class AuthorityIdentityProviderCapabilitiesTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FromCapabilities_SetsFlags_WhenTokensPresent()
{
var capabilities = AuthorityIdentityProviderCapabilities.FromCapabilities(new[]
@@ -22,7 +24,8 @@ public class AuthorityIdentityProviderCapabilitiesTests
Assert.True(capabilities.SupportsBootstrap);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FromCapabilities_DefaultsToFalse_WhenEmpty()
{
var capabilities = AuthorityIdentityProviderCapabilities.FromCapabilities(Array.Empty<string>());
@@ -33,7 +36,8 @@ public class AuthorityIdentityProviderCapabilitiesTests
Assert.False(capabilities.SupportsBootstrap);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FromCapabilities_IgnoresNullSet()
{
var capabilities = AuthorityIdentityProviderCapabilities.FromCapabilities(null!);

View File

@@ -1,10 +1,12 @@
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugins.Abstractions.Tests;
public class AuthorityPluginHealthResultTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Healthy_ReturnsHealthyStatus()
{
var result = AuthorityPluginHealthResult.Healthy("ready");
@@ -14,7 +16,8 @@ public class AuthorityPluginHealthResultTests
Assert.NotNull(result.Details);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Degraded_ReturnsDegradedStatus()
{
var result = AuthorityPluginHealthResult.Degraded("slow");
@@ -22,7 +25,8 @@ public class AuthorityPluginHealthResultTests
Assert.Equal(AuthorityPluginHealthStatus.Degraded, result.Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Unavailable_ReturnsUnavailableStatus()
{
var result = AuthorityPluginHealthResult.Unavailable("down");

View File

@@ -1,11 +1,13 @@
using System;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugins.Abstractions.Tests;
public class AuthorityPluginOperationResultTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Success_ReturnsSucceededResult()
{
var result = AuthorityPluginOperationResult.Success("ok");
@@ -15,7 +17,8 @@ public class AuthorityPluginOperationResultTests
Assert.Equal("ok", result.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failure_PopulatesErrorCode()
{
var result = AuthorityPluginOperationResult.Failure("ERR_CODE", "failure");
@@ -25,13 +28,15 @@ public class AuthorityPluginOperationResultTests
Assert.Equal("failure", result.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failure_Throws_WhenErrorCodeMissing()
{
Assert.Throws<ArgumentException>(() => AuthorityPluginOperationResult.Failure(string.Empty));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GenericSuccess_ReturnsValue()
{
var result = AuthorityPluginOperationResult<string>.Success("value", "created");
@@ -41,7 +46,8 @@ public class AuthorityPluginOperationResultTests
Assert.Equal("created", result.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GenericFailure_PopulatesErrorCode()
{
var result = AuthorityPluginOperationResult<int>.Failure("CONFLICT", "duplicate");
@@ -52,7 +58,8 @@ public class AuthorityPluginOperationResultTests
Assert.Equal("duplicate", result.Message);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GenericFailure_Throws_WhenErrorCodeMissing()
{
Assert.Throws<ArgumentException>(() => AuthorityPluginOperationResult<string>.Failure(" "));

View File

@@ -1,23 +1,27 @@
using System;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugins.Abstractions.Tests;
public class AuthorityUserDescriptorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Throws_WhenSubjectMissing()
{
Assert.Throws<ArgumentException>(() => new AuthorityUserDescriptor(string.Empty, "user", null, false));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Throws_WhenUsernameMissing()
{
Assert.Throws<ArgumentException>(() => new AuthorityUserDescriptor("subject", " ", null, false));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_MaterialisesCollections()
{
var descriptor = new AuthorityUserDescriptor("subject", "user", null, false);

View File

@@ -1,17 +1,20 @@
using System;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugins.Abstractions.Tests;
public class AuthorityUserRegistrationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_Throws_WhenUsernameMissing()
{
Assert.Throws<ArgumentException>(() => new AuthorityUserRegistration(string.Empty, null, null, null, false));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void WithPassword_ReturnsCopyWithPassword()
{
var registration = new AuthorityUserRegistration("alice", null, "Alice", null, true);

View File

@@ -59,7 +59,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
await _npgsqlDataSource.DisposeAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParallelCreates_DifferentIds_All_Succeed()
{
// Arrange
@@ -77,7 +78,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
allKeys.Should().HaveCount(parallelCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConcurrentReads_SameKey_All_Succeed()
{
// Arrange
@@ -97,7 +99,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
"all concurrent reads should return same key");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParallelReadsDuringWrite_ReturnsConsistentState()
{
// Arrange
@@ -124,7 +127,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
});
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConcurrentUpdateLastUsed_SameKey_NoConflict()
{
// Arrange
@@ -146,7 +150,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
result!.LastUsedAt.Should().NotBeNull("at least one update should have succeeded");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParallelListOperations_NoDeadlock()
{
// Arrange - Create some keys first
@@ -166,7 +171,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
completedInTime.Should().BeTrue("parallel list operations should not deadlock");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task MixedOperations_NoDeadlock()
{
// Arrange
@@ -200,7 +206,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
completedInTime.Should().BeTrue("mixed operations should not deadlock");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RapidSuccessiveWrites_AllSucceed()
{
// Arrange
@@ -217,7 +224,8 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
allKeys.Should().HaveCount(iterations);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConcurrentDeleteAndRead_ReturnsConsistentState()
{
// Arrange

View File

@@ -59,7 +59,8 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
await _npgsqlDataSource.DisposeAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_SameId_Twice_Should_Not_Duplicate()
{
// Arrange
@@ -88,7 +89,8 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_DifferentIds_SamePrefix_Should_Not_Duplicate()
{
// Arrange
@@ -116,7 +118,8 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpdateLastUsedAsync_Twice_Should_Be_Idempotent()
{
// Arrange
@@ -138,7 +141,8 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
after2!.Id.Should().Be(key.Id);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RevokeAsync_Twice_Should_Be_Idempotent()
{
// Arrange
@@ -160,7 +164,8 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
after2!.Status.Should().Be(ApiKeyStatus.Revoked);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DeleteAsync_Twice_Should_Be_Idempotent()
{
// Arrange
@@ -182,7 +187,8 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
afterSecond.Should().BeNull("second delete should also succeed");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAsync_Multiple_Keys_For_Same_User_Allowed()
{
// Arrange - Create 5 keys for same user

View File

@@ -5,6 +5,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -32,7 +33,8 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetByPrefix_RoundTripsApiKey()
{
var keyPrefix = "sk_live_" + Guid.NewGuid().ToString("N")[..8];
@@ -59,7 +61,8 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
fetched.Scopes.Should().BeEquivalentTo(["scan:read", "scan:write"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetById_ReturnsApiKey()
{
var apiKey = CreateApiKey(Guid.NewGuid(), "Test Key");
@@ -72,7 +75,8 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
fetched!.Name.Should().Be("Test Key");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByUserId_ReturnsUserApiKeys()
{
var userId = Guid.NewGuid();
@@ -87,7 +91,8 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
keys.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_ReturnsAllKeysForTenant()
{
var key1 = CreateApiKey(Guid.NewGuid(), "Key A");
@@ -101,7 +106,8 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
keys.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Revoke_UpdatesStatusAndRevokedFields()
{
var apiKey = CreateApiKey(Guid.NewGuid(), "ToRevoke");
@@ -116,7 +122,8 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
fetched.RevokedBy.Should().Be("security@test.com");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Delete_RemovesApiKey()
{
var apiKey = CreateApiKey(Guid.NewGuid(), "DeleteKey");

View File

@@ -5,6 +5,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -27,7 +28,8 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Create_ReturnsGeneratedId()
{
// Arrange
@@ -50,7 +52,8 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
id.Should().BeGreaterThan(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_ReturnsAuditEntriesOrderedByCreatedAtDesc()
{
// Arrange
@@ -68,7 +71,8 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
audits[0].Action.Should().Be("action2"); // Most recent first
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByUserId_ReturnsUserAudits()
{
// Arrange
@@ -90,7 +94,8 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
audits[0].UserId.Should().Be(userId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByResource_ReturnsResourceAudits()
{
// Arrange
@@ -112,7 +117,8 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
audits[0].ResourceId.Should().Be(resourceId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByCorrelationId_ReturnsCorrelatedAudits()
{
// Arrange
@@ -142,7 +148,8 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
audits.Should().AllSatisfy(a => a.CorrelationId.Should().Be(correlationId));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByAction_ReturnsMatchingAudits()
{
// Arrange
@@ -158,7 +165,8 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
audits.Should().AllSatisfy(a => a.Action.Should().Be("user.login"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Create_StoresJsonbValues()
{
// Arrange

View File

@@ -17,7 +17,8 @@ public sealed class AuthorityMigrationTests
_fixture = fixture;
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task MigrationsApplied_SchemaHasTables()
{
// Arrange
@@ -47,11 +48,13 @@ public sealed class AuthorityMigrationTests
// Add more specific table assertions based on Authority migrations
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task MigrationsApplied_SchemaVersionRecorded()
{
// Arrange
await using var connection = new NpgsqlConnection(_fixture.ConnectionString);
using StellaOps.TestKit;
await connection.OpenAsync();
// Act - Check schema_migrations table

View File

@@ -5,6 +5,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -26,7 +27,8 @@ public sealed class OfflineKitAuditRepositoryTests : IAsyncLifetime
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Insert_ThenList_ReturnsRecord()
{
var tenantId = Guid.NewGuid().ToString("N");
@@ -52,7 +54,8 @@ public sealed class OfflineKitAuditRepositoryTests : IAsyncLifetime
listed[0].Details.Should().Contain("kitFilename");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_WithFilters_ReturnsMatchingRows()
{
var tenantId = Guid.NewGuid().ToString("N");
@@ -88,7 +91,8 @@ public sealed class OfflineKitAuditRepositoryTests : IAsyncLifetime
validated[0].EventType.Should().Be("IMPORT_VALIDATED");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_IsTenantIsolated()
{
var tenantA = Guid.NewGuid().ToString("N");

View File

@@ -5,6 +5,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -32,7 +33,8 @@ public sealed class PermissionRepositoryTests : IAsyncLifetime
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGet_RoundTripsPermission()
{
var permission = new PermissionEntity
@@ -54,7 +56,8 @@ public sealed class PermissionRepositoryTests : IAsyncLifetime
fetched.Action.Should().Be("read");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByName_ReturnsCorrectPermission()
{
var permission = BuildPermission("tokens:revoke", "tokens", "revoke", "Revoke tokens");
@@ -66,7 +69,8 @@ public sealed class PermissionRepositoryTests : IAsyncLifetime
fetched!.Action.Should().Be("revoke");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByResource_ReturnsResourcePermissions()
{
var p1 = BuildPermission("users:read", "users", "read", "Read");
@@ -79,7 +83,8 @@ public sealed class PermissionRepositoryTests : IAsyncLifetime
perms.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_ReturnsAllPermissionsForTenant()
{
var p1 = BuildPermission("orch:read", "orch", "read", "Read orch");
@@ -92,7 +97,8 @@ public sealed class PermissionRepositoryTests : IAsyncLifetime
perms.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Delete_RemovesPermission()
{
var permission = BuildPermission("tokens:revoke", "tokens", "revoke", "Revoke tokens");

View File

@@ -6,6 +6,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -33,7 +34,8 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetByHash_RoundTripsRefreshToken()
{
var refresh = BuildToken(Guid.NewGuid());
@@ -47,7 +49,8 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
fetched!.Id.Should().Be(refresh.Id);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetById_ReturnsToken()
{
var refresh = BuildToken(Guid.NewGuid());
@@ -61,7 +64,8 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
fetched!.UserId.Should().Be(refresh.UserId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByUserId_ReturnsUserTokens()
{
var userId = Guid.NewGuid();
@@ -77,7 +81,8 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
tokens.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Revoke_SetsRevokedFields()
{
var refresh = BuildToken(Guid.NewGuid());
@@ -92,7 +97,8 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
fetched.RevokedBy.Should().Be("tester");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RevokeByUserId_RevokesAllUserTokens()
{
var userId = Guid.NewGuid();
@@ -111,7 +117,8 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
revoked2!.RevokedAt.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Revoke_WithReplacedBy_SetsReplacedByField()
{
var refresh = BuildToken(Guid.NewGuid());
@@ -126,7 +133,8 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
fetched!.ReplacedBy.Should().Be(newTokenId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByUserId_IsDeterministic_WhenIssuedAtTies()
{
var userId = Guid.NewGuid();

View File

@@ -13,6 +13,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
/// <summary>
@@ -56,7 +57,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
#region User-Role Assignment Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithRole_GetsRolePermissions()
{
// Arrange
@@ -81,7 +83,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
userPermissions.Should().Contain(p => p.Resource == "scanner" && p.Action == "view");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithoutRole_HasNoPermissions_DenyByDefault()
{
// Arrange
@@ -101,7 +104,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
userPermissions.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithExpiredRole_HasNoPermissions()
{
// Arrange
@@ -124,7 +128,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
userPermissions.Should().BeEmpty("expired role should not grant permissions");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithFutureExpiryRole_HasPermissions()
{
// Arrange
@@ -148,7 +153,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
userPermissions.Should().Contain(p => p.Resource == "policy" && p.Action == "read");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithNoExpiryRole_HasPermissions()
{
// Arrange
@@ -174,7 +180,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
#region Multiple Roles Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithMultipleRoles_AccumulatesPermissions()
{
// Arrange
@@ -209,7 +216,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
userPermissions.Should().Contain(p => p.Action == "delete");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithOverlappingRolePermissions_GetsDistinctPermissions()
{
// Arrange
@@ -239,7 +247,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
userPermissions.Select(p => p.Id).Should().OnlyHaveUniqueItems();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UserWithOneExpiredRole_StillHasOtherRolePermissions()
{
// Arrange
@@ -277,7 +286,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
#region Role Removal Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RemovingRole_RemovesPermissions()
{
// Arrange
@@ -303,7 +313,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
afterRemoval.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RemovingPermissionFromRole_AffectsAllUsersWithRole()
{
// Arrange
@@ -337,7 +348,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
#region Role Permission Enforcement Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRolePermissions_ReturnsOnlyAssignedPermissions()
{
// Arrange
@@ -357,7 +369,8 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
rolePermissions.Should().NotContain(p => p.Resource == "notallowed");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SystemRole_CanHaveSpecialPermissions()
{
// Arrange

View File

@@ -5,6 +5,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -32,7 +33,8 @@ public sealed class RoleRepositoryTests : IAsyncLifetime
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGet_RoundTripsRole()
{
var role = BuildRole("Admin");
@@ -44,7 +46,8 @@ public sealed class RoleRepositoryTests : IAsyncLifetime
fetched!.Name.Should().Be("Admin");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByName_ReturnsCorrectRole()
{
var role = BuildRole("Reader");
@@ -56,7 +59,8 @@ public sealed class RoleRepositoryTests : IAsyncLifetime
fetched!.Description.Should().Be("Reader role");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task List_ReturnsAllRolesForTenant()
{
await _repository.CreateAsync(_tenantId, BuildRole("Reader"));
@@ -67,7 +71,8 @@ public sealed class RoleRepositoryTests : IAsyncLifetime
roles.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Update_ModifiesRole()
{
var role = BuildRole("Updater");
@@ -92,7 +97,8 @@ public sealed class RoleRepositoryTests : IAsyncLifetime
fetched!.Description.Should().Be("Updated description");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Delete_RemovesRole()
{
var role = BuildRole("Deleter");

View File

@@ -5,6 +5,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -32,7 +33,8 @@ public sealed class SessionRepositoryTests : IAsyncLifetime
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGet_RoundTripsSession()
{
var session = BuildSession();
@@ -45,7 +47,8 @@ public sealed class SessionRepositoryTests : IAsyncLifetime
fetched!.Id.Should().Be(session.Id);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByTokenHash_ReturnsSession()
{
var session = BuildSession();
@@ -58,7 +61,8 @@ public sealed class SessionRepositoryTests : IAsyncLifetime
fetched!.UserId.Should().Be(session.UserId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EndByUserId_EndsAllUserSessions()
{
var userId = Guid.NewGuid();

View File

@@ -6,6 +6,7 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Storage.Postgres.Tests;
[Collection(AuthorityPostgresCollection.Name)]
@@ -32,7 +33,8 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAndGetByHash_RoundTripsToken()
{
// Arrange
@@ -61,7 +63,8 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
fetched.Scopes.Should().BeEquivalentTo(["read", "write"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetById_ReturnsToken()
{
// Arrange
@@ -77,7 +80,8 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
fetched!.Id.Should().Be(token.Id);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByUserId_ReturnsUserTokens()
{
// Arrange
@@ -95,7 +99,8 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
tokens.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Revoke_SetsRevokedFields()
{
// Arrange
@@ -112,7 +117,8 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
fetched.RevokedBy.Should().Be("admin@test.com");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RevokeByUserId_RevokesAllUserTokens()
{
// Arrange
@@ -133,7 +139,8 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
revoked2!.RevokedAt.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetByUserId_IsDeterministic_WhenIssuedAtTies()
{
// Arrange: same IssuedAt, fixed IDs to validate ordering