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 b4fc66feb6
3353 changed files with 88254 additions and 1590657 deletions

View File

@@ -14,11 +14,14 @@ using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Policy.Analyzers.Tests;
public sealed class HttpClientUsageAnalyzerTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReportsDiagnostic_ForNewHttpClient()
{
const string source = """
@@ -39,7 +42,8 @@ public sealed class HttpClientUsageAnalyzerTests
Assert.Contains(diagnostics, d => d.Id == HttpClientUsageAnalyzer.DiagnosticId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DoesNotReportDiagnostic_InsidePolicyAssembly()
{
const string source = """
@@ -57,7 +61,8 @@ public sealed class HttpClientUsageAnalyzerTests
Assert.DoesNotContain(diagnostics, d => d.Id == HttpClientUsageAnalyzer.DiagnosticId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CodeFix_RewritesToFactoryCall()
{
const string source = """

View File

@@ -22,6 +22,8 @@ using Microsoft.CodeAnalysis.Text;
using Xunit;
using FluentAssertions;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Policy.Analyzers.Tests;
/// <summary>
@@ -33,7 +35,8 @@ public sealed class PolicyAnalyzerRoslynTests
{
#region AIRGAP-5100-005: Expected Diagnostics & No False Positives
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("var client = new HttpClient();", true, "Direct construction should trigger diagnostic")]
[InlineData("var client = new System.Net.Http.HttpClient();", true, "Fully qualified construction should trigger diagnostic")]
[InlineData("HttpClient client = new();", true, "Target-typed new should trigger diagnostic")]
@@ -60,7 +63,8 @@ public sealed class PolicyAnalyzerRoslynTests
hasDiagnostic.Should().Be(shouldTrigger, reason);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task NoDiagnostic_ForHttpClientParameter()
{
const string source = """
@@ -83,7 +87,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Using HttpClient as parameter should not trigger diagnostic");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task NoDiagnostic_ForHttpClientField()
{
const string source = """
@@ -107,7 +112,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Declaring HttpClient field should not trigger diagnostic");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task NoDiagnostic_ForFactoryMethodReturn()
{
const string source = """
@@ -138,7 +144,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Using factory method should not trigger diagnostic");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task NoDiagnostic_InTestAssembly()
{
const string source = """
@@ -160,7 +167,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Test assemblies should be exempt from diagnostic");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task NoDiagnostic_InPolicyAssembly()
{
const string source = """
@@ -179,7 +187,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Policy assembly itself should be exempt");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Diagnostic_HasCorrectSeverity()
{
const string source = """
@@ -203,7 +212,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Diagnostic should be a warning, not an error");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Diagnostic_HasCorrectLocation()
{
const string source = """
@@ -228,7 +238,8 @@ public sealed class PolicyAnalyzerRoslynTests
lineSpan.StartLinePosition.Line.Should().Be(8, "Diagnostic should point to line 9 (0-indexed: 8)");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task MultipleHttpClientUsages_ReportMultipleDiagnostics()
{
const string source = """
@@ -265,7 +276,8 @@ public sealed class PolicyAnalyzerRoslynTests
#region AIRGAP-5100-006: Golden Generated Code Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CodeFix_GeneratesExpectedFactoryCall()
{
const string source = """
@@ -301,7 +313,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Code fix should match golden output exactly");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CodeFix_PreservesTrivia()
{
const string source = """
@@ -326,7 +339,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Leading comment should be preserved");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CodeFix_DeterministicOutput()
{
const string source = """
@@ -352,7 +366,8 @@ public sealed class PolicyAnalyzerRoslynTests
result2.Should().Be(result3, "Code fix should be deterministic");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CodeFix_ContainsRequiredPlaceholders()
{
const string source = """
@@ -383,7 +398,8 @@ public sealed class PolicyAnalyzerRoslynTests
fixedCode.Should().Contain("REPLACE_INTENT");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CodeFix_UsesFullyQualifiedNames()
{
const string source = """
@@ -408,7 +424,8 @@ public sealed class PolicyAnalyzerRoslynTests
fixedCode.Should().Contain("global::System.Uri");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task FixAllProvider_IsWellKnownBatchFixer()
{
var provider = new HttpClientUsageCodeFixProvider();
@@ -418,7 +435,8 @@ public sealed class PolicyAnalyzerRoslynTests
"Should use batch fixer for efficient multi-fix application");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Analyzer_SupportedDiagnostics_ContainsExpectedId()
{
var analyzer = new HttpClientUsageAnalyzer();
@@ -428,7 +446,8 @@ public sealed class PolicyAnalyzerRoslynTests
supportedDiagnostics[0].Id.Should().Be("AIRGAP001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CodeFixProvider_FixableDiagnosticIds_MatchesAnalyzer()
{
var analyzer = new HttpClientUsageAnalyzer();

View File

@@ -17,6 +17,7 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.AirGap.Policy.Analyzers\StellaOps.AirGap.Policy.Analyzers.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -8,11 +8,14 @@ using Microsoft.Extensions.DependencyInjection;
using StellaOps.AirGap.Policy;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Policy.Tests;
public sealed class EgressPolicyTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Evaluate_UnsealedEnvironment_AllowsRequest()
{
var options = new EgressPolicyOptions
@@ -29,7 +32,8 @@ public sealed class EgressPolicyTests
Assert.Null(decision.Reason);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnsureAllowed_SealedEnvironmentWithMatchingRule_Allows()
{
var options = new EgressPolicyOptions
@@ -44,7 +48,8 @@ public sealed class EgressPolicyTests
policy.EnsureAllowed(request);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnsureAllowed_SealedEnvironmentWithoutRule_ThrowsWithGuidance()
{
var options = new EgressPolicyOptions
@@ -67,7 +72,8 @@ public sealed class EgressPolicyTests
Assert.Equal(options.SupportContact, exception.SupportContact);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnsureAllowed_SealedEnvironment_AllowsLoopbackWhenConfigured()
{
var options = new EgressPolicyOptions
@@ -82,7 +88,8 @@ public sealed class EgressPolicyTests
policy.EnsureAllowed(request);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnsureAllowed_SealedEnvironment_AllowsPrivateNetworkWhenConfigured()
{
var options = new EgressPolicyOptions
@@ -97,7 +104,8 @@ public sealed class EgressPolicyTests
policy.EnsureAllowed(request);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnsureAllowed_SealedEnvironment_BlocksPrivateNetworkWhenNotConfigured()
{
var options = new EgressPolicyOptions
@@ -113,7 +121,8 @@ public sealed class EgressPolicyTests
Assert.Contains("10.10.0.5", exception.Message, StringComparison.OrdinalIgnoreCase);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("https://api.example.com", true)]
[InlineData("https://sub.api.example.com", true)]
[InlineData("https://example.com", false)]
@@ -132,7 +141,8 @@ public sealed class EgressPolicyTests
Assert.Equal(expectedAllowed, decision.IsAllowed);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ServiceCollection_AddAirGapEgressPolicy_RegistersService()
{
var services = new ServiceCollection();
@@ -149,7 +159,8 @@ public sealed class EgressPolicyTests
policy.EnsureAllowed(new EgressRequest("PolicyEngine", new Uri("https://mirror.internal"), "mirror-sync"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ServiceCollection_AddAirGapEgressPolicy_BindsFromConfiguration()
{
var configuration = new ConfigurationBuilder()
@@ -182,7 +193,8 @@ public sealed class EgressPolicyTests
Assert.Contains("mirror.internal", blocked.Remediation, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EgressHttpClientFactory_Create_EnforcesPolicyBeforeReturningClient()
{
var recordingPolicy = new RecordingPolicy();

View File

@@ -11,6 +11,7 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.AirGap.Policy\StellaOps.AirGap.Policy.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -14,6 +14,7 @@ using StellaOps.AirGap.Time.Models;
using StellaOps.Infrastructure.Postgres.Options;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Storage.Postgres.Tests;
/// <summary>
@@ -55,7 +56,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
#region AIRGAP-5100-007: Migration Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Migration_SchemaContainsRequiredTables()
{
// Arrange
@@ -77,7 +79,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Migration_AirGapStateHasRequiredColumns()
{
// Arrange
@@ -94,7 +97,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Migration_IsIdempotent()
{
// Act - Running migrations again should not fail
@@ -107,7 +111,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
await act.Should().NotThrowAsync("Running migrations multiple times should be idempotent");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Migration_HasTenantIndex()
{
// Act
@@ -122,7 +127,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
#region AIRGAP-5100-008: Idempotency Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Idempotency_SetStateTwice_NoException()
{
// Arrange
@@ -137,7 +143,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
await act.Should().NotThrowAsync("Setting state twice should be idempotent");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Idempotency_SetStateTwice_SingleRecord()
{
// Arrange
@@ -154,7 +161,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
fetched.PolicyHash.Should().Be("sha256:policy-v2", "Second set should update, not duplicate");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Idempotency_ConcurrentSets_NoDataCorruption()
{
// Arrange
@@ -181,7 +189,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
fetched.PolicyHash.Should().StartWith("sha256:policy-");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Idempotency_SameBundleIdTwice_NoException()
{
// Arrange
@@ -203,7 +212,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
#region AIRGAP-5100-009: Query Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task QueryDeterminism_SameInput_SameOutput()
{
// Arrange
@@ -221,7 +231,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
result2.Should().BeEquivalentTo(result3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task QueryDeterminism_ContentBudgets_ReturnInConsistentOrder()
{
// Arrange
@@ -252,7 +263,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task QueryDeterminism_TimeAnchor_PreservesAllFields()
{
// Arrange
@@ -277,7 +289,8 @@ public sealed class AirGapStorageIntegrationTests : IAsyncLifetime
fetched1.TimeAnchor.Source.Should().Be("tsa.example.com");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task QueryDeterminism_MultipleTenants_IsolatedResults()
{
// Arrange

View File

@@ -8,6 +8,7 @@ using StellaOps.AirGap.Time.Models;
using StellaOps.Infrastructure.Postgres.Options;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Storage.Postgres.Tests;
[Collection(AirGapPostgresCollection.Name)]
@@ -42,7 +43,8 @@ public sealed class PostgresAirGapStateStoreTests : IAsyncLifetime
await _dataSource.DisposeAsync();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAsync_ReturnsDefaultStateForNewTenant()
{
// Act
@@ -55,7 +57,8 @@ public sealed class PostgresAirGapStateStoreTests : IAsyncLifetime
state.PolicyHash.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAndGet_RoundTripsState()
{
// Arrange
@@ -100,7 +103,8 @@ public sealed class PostgresAirGapStateStoreTests : IAsyncLifetime
fetched.ContentBudgets["advisories"].WarningSeconds.Should().Be(7200);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_UpdatesExistingState()
{
// Arrange
@@ -136,7 +140,8 @@ public sealed class PostgresAirGapStateStoreTests : IAsyncLifetime
fetched.StalenessBudget.WarningSeconds.Should().Be(600);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_PersistsContentBudgets()
{
// Arrange

View File

@@ -28,6 +28,7 @@
<ProjectReference Include="..\StellaOps.AirGap.Storage.Postgres\StellaOps.AirGap.Storage.Postgres.csproj" />
<ProjectReference Include="..\StellaOps.AirGap.Controller\StellaOps.AirGap.Controller.csproj" />
<ProjectReference Include="..\..\__Tests\__Libraries\StellaOps.Infrastructure.Postgres.Testing\StellaOps.Infrastructure.Postgres.Testing.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,6 +10,7 @@ using System.Text;
using FluentAssertions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Bundle.Tests;
/// <summary>
@@ -22,7 +23,8 @@ public sealed class AirGapCliToolTests
{
#region AIRGAP-5100-013: Exit Code Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExitCode_SuccessfulExport_ReturnsZero()
{
// Arrange
@@ -32,7 +34,8 @@ public sealed class AirGapCliToolTests
expectedExitCode.Should().Be(0, "Successful operations should return exit code 0");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExitCode_UserError_ReturnsOne()
{
// Arrange
@@ -43,7 +46,8 @@ public sealed class AirGapCliToolTests
expectedExitCode.Should().Be(1, "User errors should return exit code 1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExitCode_SystemError_ReturnsTwo()
{
// Arrange
@@ -54,7 +58,8 @@ public sealed class AirGapCliToolTests
expectedExitCode.Should().Be(2, "System errors should return exit code 2");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExitCode_MissingRequiredArgument_ReturnsOne()
{
// Arrange - Missing required argument scenario
@@ -66,7 +71,8 @@ public sealed class AirGapCliToolTests
expectedExitCode.Should().Be(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExitCode_InvalidFeedPath_ReturnsOne()
{
// Arrange - Invalid feed path scenario
@@ -84,7 +90,8 @@ public sealed class AirGapCliToolTests
expectedExitCode.Should().Be(1, "Invalid feed path should return exit code 1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExitCode_HelpFlag_ReturnsZero()
{
// Arrange
@@ -96,7 +103,8 @@ public sealed class AirGapCliToolTests
expectedExitCode.Should().Be(0, "--help should return exit code 0");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExitCode_VersionFlag_ReturnsZero()
{
// Arrange
@@ -112,7 +120,8 @@ public sealed class AirGapCliToolTests
#region AIRGAP-5100-014: Golden Output Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GoldenOutput_ExportCommand_IncludesManifestSummary()
{
// Arrange - Expected output structure for export command
@@ -135,7 +144,8 @@ public sealed class AirGapCliToolTests
expectedOutputLines.Should().Contain(l => l.Contains("Digest:"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GoldenOutput_ExportCommand_IncludesBundleDigest()
{
// Arrange
@@ -145,7 +155,8 @@ public sealed class AirGapCliToolTests
digestPattern.Should().Contain("sha256:");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GoldenOutput_ImportCommand_IncludesImportSummary()
{
// Arrange - Expected output structure for import command
@@ -165,7 +176,8 @@ public sealed class AirGapCliToolTests
expectedOutputLines.Should().Contain(l => l.Contains("imported successfully"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GoldenOutput_ListCommand_IncludesBundleTable()
{
// Arrange - Expected output structure for list command
@@ -177,7 +189,8 @@ public sealed class AirGapCliToolTests
expectedHeaders.Should().Contain("Version");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GoldenOutput_ValidateCommand_IncludesValidationResult()
{
// Arrange - Expected output structure for validate command
@@ -195,7 +208,8 @@ public sealed class AirGapCliToolTests
expectedOutputLines.Should().Contain(l => l.Contains("Validation:"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GoldenOutput_ErrorMessage_IncludesContext()
{
// Arrange - Error message format
@@ -210,7 +224,8 @@ public sealed class AirGapCliToolTests
#region AIRGAP-5100-015: CLI Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CliDeterminism_SameInputs_SameOutputDigest()
{
// Arrange - Simulate CLI determinism
@@ -225,7 +240,8 @@ public sealed class AirGapCliToolTests
digest1.Should().Be(digest2, "Same inputs should produce same digest");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CliDeterminism_OutputBundleName_IsDeterministic()
{
// Arrange
@@ -241,7 +257,8 @@ public sealed class AirGapCliToolTests
filename1.Should().Be(filename2, "Same parameters should produce same filename");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CliDeterminism_ManifestJson_IsDeterministic()
{
// Arrange
@@ -256,7 +273,8 @@ public sealed class AirGapCliToolTests
json1.Should().Be(json2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CliDeterminism_FeedOrdering_IsDeterministic()
{
// Arrange - Feeds in different order
@@ -272,7 +290,8 @@ public sealed class AirGapCliToolTests
"Canonical ordering should be deterministic");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CliDeterminism_DigestComputation_IsDeterministic()
{
// Arrange
@@ -290,7 +309,8 @@ public sealed class AirGapCliToolTests
digest3.Should().Be(expectedDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CliDeterminism_TimestampFormat_IsDeterministic()
{
// Arrange

View File

@@ -14,6 +14,7 @@ using StellaOps.AirGap.Bundle.Serialization;
using StellaOps.AirGap.Bundle.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Bundle.Tests;
/// <summary>
@@ -48,7 +49,8 @@ public sealed class AirGapIntegrationTests : IDisposable
#region AIRGAP-5100-016: Online Offline Bundle Transfer Integration
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Integration_OnlineExport_OfflineImport_DataIntegrity()
{
// Arrange - Create source data in "online" environment
@@ -102,7 +104,8 @@ public sealed class AirGapIntegrationTests : IDisposable
importedFeedContent.Should().Contain("CVE-2024-0001");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Integration_BundleTransfer_PreservesAllComponents()
{
// Arrange - Create multi-component bundle
@@ -143,7 +146,8 @@ public sealed class AirGapIntegrationTests : IDisposable
File.Exists(Path.Combine(offlinePath, "certs/root.pem")).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Integration_CorruptedBundle_ImportFails()
{
// Arrange
@@ -185,7 +189,8 @@ public sealed class AirGapIntegrationTests : IDisposable
#region AIRGAP-5100-017: Policy Export/Import/Evaluation Integration
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Integration_PolicyExport_PolicyImport_IdenticalVerdict()
{
// Arrange - Create a policy in online environment
@@ -242,7 +247,8 @@ public sealed class AirGapIntegrationTests : IDisposable
importedDigest.Should().Be(originalDigest, "Policy digest should match");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Integration_MultiplePolices_MaintainOrder()
{
// Arrange - Create multiple policies
@@ -289,7 +295,8 @@ public sealed class AirGapIntegrationTests : IDisposable
File.Exists(Path.Combine(offlinePath, "policies/policy3.rego")).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Integration_PolicyWithCrypto_BothTransferred()
{
// Arrange

View File

@@ -35,7 +35,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
#region Same Inputs Same Hash Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameInputs_SameComponentDigests()
{
// Arrange
@@ -55,7 +56,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
manifest1.Feeds[0].Digest.Should().Be(manifest2.Feeds[0].Digest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameManifestContent_SameBundleDigest()
{
// Arrange
@@ -70,7 +72,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
digest1.Should().Be(digest2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_MultipleBuilds_SameDigests()
{
// Arrange
@@ -93,7 +96,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
digests.Distinct().Should().HaveCount(1, "All builds should produce the same digest");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Determinism_Sha256_StableAcrossCalls()
{
// Arrange
@@ -115,7 +119,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
#region Roundtrip Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Roundtrip_ExportImportReexport_IdenticalBundle()
{
// Arrange
@@ -147,6 +152,7 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
// Re-export using the imported file
var reimportFeedFile = CreateSourceFile("reimport/feed.json", importedContent);
using StellaOps.TestKit;
var request2 = new BundleBuildRequest(
"roundtrip-test",
"1.0.0",
@@ -165,7 +171,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
manifest1.Feeds[0].Digest.Should().Be(manifest2.Feeds[0].Digest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Roundtrip_ManifestSerialize_Deserialize_Identical()
{
// Arrange
@@ -179,7 +186,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
restored.Should().BeEquivalentTo(original);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Roundtrip_ManifestSerialize_Reserialize_SameJson()
{
// Arrange
@@ -198,7 +206,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
#region Content Independence Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameContent_DifferentSourcePath_SameDigest()
{
// Arrange
@@ -219,7 +228,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
manifest1.Feeds[0].Digest.Should().Be(manifest2.Feeds[0].Digest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_DifferentContent_DifferentDigest()
{
// Arrange
@@ -243,7 +253,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
#region Multiple Component Determinism
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_MultipleFeeds_EachHasCorrectDigest()
{
// Arrange
@@ -278,7 +289,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
manifest.Feeds[2].Digest.Should().Be(ComputeSha256(content3));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_OrderIndependence_SameManifestDigest()
{
// Note: This test verifies that the bundle digest is computed deterministically
@@ -300,7 +312,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
#region Binary Content Determinism
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_BinaryContent_SameDigest()
{
// Arrange
@@ -340,7 +353,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
manifest1.Feeds[0].Digest.Should().Be(manifest2.Feeds[0].Digest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_LargeContent_SameDigest()
{
// Arrange

View File

@@ -44,7 +44,8 @@ public sealed class BundleExportImportTests : IDisposable
#region AIRGAP-5100-001: Bundle Export Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_CreatesValidBundleStructure()
{
// Arrange
@@ -63,7 +64,8 @@ public sealed class BundleExportImportTests : IDisposable
manifest.Feeds.Should().HaveCount(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_SetsCorrectManifestFields()
{
// Arrange
@@ -83,7 +85,8 @@ public sealed class BundleExportImportTests : IDisposable
manifest.CreatedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_ComputesCorrectFileDigests()
{
// Arrange
@@ -107,7 +110,8 @@ public sealed class BundleExportImportTests : IDisposable
feedDigest.Should().Be(expectedDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_ComputesCorrectBundleDigest()
{
// Arrange
@@ -124,7 +128,8 @@ public sealed class BundleExportImportTests : IDisposable
manifest.BundleDigest.Should().HaveLength(64);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_TracksCorrectFileSizes()
{
// Arrange
@@ -146,7 +151,8 @@ public sealed class BundleExportImportTests : IDisposable
#region AIRGAP-5100-002: Bundle Import Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_LoadsManifestCorrectly()
{
// Arrange - First export a bundle
@@ -170,7 +176,8 @@ public sealed class BundleExportImportTests : IDisposable
loaded.Version.Should().Be("1.0.0");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_VerifiesFileIntegrity()
{
// Arrange
@@ -198,7 +205,8 @@ public sealed class BundleExportImportTests : IDisposable
loaded.Feeds[0].Digest.Should().Be(actualDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_FailsOnCorruptedFile()
{
// Arrange
@@ -230,7 +238,8 @@ public sealed class BundleExportImportTests : IDisposable
#region AIRGAP-5100-003: Determinism Tests (Same Inputs Same Hash)
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameInputs_ProduceSameBundleDigest()
{
// Arrange
@@ -271,7 +280,8 @@ public sealed class BundleExportImportTests : IDisposable
"Same content should produce same file digest");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_DifferentInputs_ProduceDifferentDigests()
{
// Arrange
@@ -294,7 +304,8 @@ public sealed class BundleExportImportTests : IDisposable
"Different content should produce different digests");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Determinism_ManifestSerialization_IsStable()
{
// Arrange
@@ -314,7 +325,8 @@ public sealed class BundleExportImportTests : IDisposable
#region AIRGAP-5100-004: Roundtrip Determinism (Export Import Re-export)
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Roundtrip_ExportImportReexport_ProducesIdenticalFileDigests()
{
// Arrange - Initial export
@@ -337,6 +349,7 @@ public sealed class BundleExportImportTests : IDisposable
// Re-export using the imported bundle's files
var reexportFeedFile = Path.Combine(bundlePath1, "feeds", "nvd.json");
using StellaOps.TestKit;
var reexportRequest = new BundleBuildRequest(
imported.Name,
imported.Version,
@@ -360,7 +373,8 @@ public sealed class BundleExportImportTests : IDisposable
digest1.Should().Be(digest2, "Roundtrip should produce identical file digests");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Roundtrip_ManifestSerialization_PreservesAllFields()
{
// Arrange

View File

@@ -7,6 +7,7 @@ using StellaOps.AirGap.Bundle.Serialization;
using StellaOps.AirGap.Bundle.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Bundle.Tests;
/// <summary>
@@ -35,7 +36,8 @@ public sealed class BundleExportTests : IAsyncLifetime
#region L0 Export Structure Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_EmptyBundle_CreatesValidManifest()
{
// Arrange
@@ -65,7 +67,8 @@ public sealed class BundleExportTests : IAsyncLifetime
manifest.TotalSizeBytes.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_WithFeed_CopiesFileAndComputesDigest()
{
// Arrange
@@ -111,7 +114,8 @@ public sealed class BundleExportTests : IAsyncLifetime
File.Exists(Path.Combine(outputPath, "feeds/nvd.json")).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_WithPolicy_CopiesFileAndComputesDigest()
{
// Arrange
@@ -153,7 +157,8 @@ public sealed class BundleExportTests : IAsyncLifetime
File.Exists(Path.Combine(outputPath, "policies/default.rego")).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_WithCryptoMaterial_CopiesFileAndComputesDigest()
{
// Arrange
@@ -195,7 +200,8 @@ public sealed class BundleExportTests : IAsyncLifetime
File.Exists(Path.Combine(outputPath, "certs/root.pem")).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_MultipleComponents_CalculatesTotalSize()
{
// Arrange
@@ -234,7 +240,8 @@ public sealed class BundleExportTests : IAsyncLifetime
#region Digest Computation Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_DigestComputation_MatchesSha256()
{
// Arrange
@@ -263,7 +270,8 @@ public sealed class BundleExportTests : IAsyncLifetime
manifest.Feeds[0].Digest.Should().BeEquivalentTo(expectedDigest, options => options.IgnoringCase());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_BundleDigest_ComputedFromManifest()
{
// Arrange
@@ -294,7 +302,8 @@ public sealed class BundleExportTests : IAsyncLifetime
#region Directory Structure Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_CreatesNestedDirectories()
{
// Arrange
@@ -338,7 +347,8 @@ public sealed class BundleExportTests : IAsyncLifetime
#region Feed Format Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(FeedFormat.StellaOpsNative)]
[InlineData(FeedFormat.TrivyDb)]
[InlineData(FeedFormat.GrypeDb)]
@@ -372,7 +382,8 @@ public sealed class BundleExportTests : IAsyncLifetime
#region Policy Type Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(PolicyType.OpaRego)]
[InlineData(PolicyType.LatticeRules)]
[InlineData(PolicyType.UnknownBudgets)]
@@ -406,7 +417,8 @@ public sealed class BundleExportTests : IAsyncLifetime
#region Crypto Component Type Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(CryptoComponentType.TrustRoot)]
[InlineData(CryptoComponentType.IntermediateCa)]
[InlineData(CryptoComponentType.TimestampRoot)]
@@ -441,7 +453,8 @@ public sealed class BundleExportTests : IAsyncLifetime
#region Expiration Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_WithExpiration_PreservesExpiryDate()
{
// Arrange
@@ -464,7 +477,8 @@ public sealed class BundleExportTests : IAsyncLifetime
manifest.ExpiresAt.Should().BeCloseTo(expiresAt, TimeSpan.FromSeconds(1));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Export_CryptoWithExpiration_PreservesComponentExpiry()
{
// Arrange

View File

@@ -9,6 +9,8 @@ using StellaOps.AirGap.Bundle.Services;
using StellaOps.AirGap.Bundle.Validation;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Bundle.Tests;
/// <summary>
@@ -37,7 +39,8 @@ public sealed class BundleImportTests : IAsyncLifetime
#region Manifest Parsing Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Import_ManifestDeserialization_PreservesAllFields()
{
// Arrange
@@ -51,7 +54,8 @@ public sealed class BundleImportTests : IAsyncLifetime
imported.Should().BeEquivalentTo(manifest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Import_ManifestDeserialization_HandlesEmptyCollections()
{
// Arrange
@@ -67,7 +71,8 @@ public sealed class BundleImportTests : IAsyncLifetime
imported.CryptoMaterials.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Import_ManifestDeserialization_PreservesFeedComponents()
{
// Arrange
@@ -85,7 +90,8 @@ public sealed class BundleImportTests : IAsyncLifetime
imported.Feeds[1].Format.Should().Be(FeedFormat.OsvJson);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Import_ManifestDeserialization_PreservesPolicyComponents()
{
// Arrange
@@ -101,7 +107,8 @@ public sealed class BundleImportTests : IAsyncLifetime
imported.Policies[1].Type.Should().Be(PolicyType.LatticeRules);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Import_ManifestDeserialization_PreservesCryptoComponents()
{
// Arrange
@@ -121,7 +128,8 @@ public sealed class BundleImportTests : IAsyncLifetime
#region Validation Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Validation_FailsWhenFilesMissing()
{
// Arrange
@@ -141,7 +149,8 @@ public sealed class BundleImportTests : IAsyncLifetime
result.Errors.Should().Contain(e => e.Message.Contains("digest mismatch") || e.Message.Contains("FILE_NOT_FOUND"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Validation_FailsWhenDigestMismatch()
{
// Arrange
@@ -158,7 +167,8 @@ public sealed class BundleImportTests : IAsyncLifetime
result.Errors.Should().Contain(e => e.Message.Contains("digest mismatch"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Validation_SucceedsWhenAllDigestsMatch()
{
// Arrange
@@ -175,7 +185,8 @@ public sealed class BundleImportTests : IAsyncLifetime
result.Errors.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Validation_WarnsWhenExpired()
{
// Arrange
@@ -195,7 +206,8 @@ public sealed class BundleImportTests : IAsyncLifetime
result.Warnings.Should().Contain(w => w.Message.Contains("expired"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Validation_WarnsWhenFeedsOld()
{
// Arrange
@@ -224,7 +236,8 @@ public sealed class BundleImportTests : IAsyncLifetime
#region Bundle Loader Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Loader_RegistersAllFeeds()
{
// Arrange
@@ -252,7 +265,8 @@ public sealed class BundleImportTests : IAsyncLifetime
feedRegistry.Received(manifest.Feeds.Length).Register(Arg.Any<FeedComponent>(), Arg.Any<string>());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Loader_RegistersAllPolicies()
{
// Arrange
@@ -279,7 +293,8 @@ public sealed class BundleImportTests : IAsyncLifetime
policyRegistry.Received(manifest.Policies.Length).Register(Arg.Any<PolicyComponent>(), Arg.Any<string>());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Loader_ThrowsOnValidationFailure()
{
// Arrange
@@ -306,7 +321,8 @@ public sealed class BundleImportTests : IAsyncLifetime
.WithMessage("*validation failed*");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_Loader_ThrowsOnMissingManifest()
{
// Arrange
@@ -330,7 +346,8 @@ public sealed class BundleImportTests : IAsyncLifetime
#region Digest Verification Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_DigestVerification_MatchesExpected()
{
// Arrange
@@ -346,7 +363,8 @@ public sealed class BundleImportTests : IAsyncLifetime
actualDigest.Should().BeEquivalentTo(expectedDigest, options => options.IgnoringCase());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Import_DigestVerification_FailsOnTamperedFile()
{
// Arrange

View File

@@ -6,11 +6,13 @@ using StellaOps.AirGap.Bundle.Services;
using StellaOps.AirGap.Bundle.Validation;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Bundle.Tests;
public class BundleManifestTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Serializer_RoundTrip_PreservesFields()
{
var manifest = CreateManifest();
@@ -19,7 +21,8 @@ public class BundleManifestTests
deserialized.Should().BeEquivalentTo(manifest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Validator_FlagsMissingFeedFile()
{
var manifest = CreateManifest();
@@ -30,7 +33,8 @@ public class BundleManifestTests
result.Errors.Should().NotBeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Builder_CopiesComponentsAndComputesDigest()
{
var tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

View File

@@ -16,5 +16,6 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.AirGap.Bundle\StellaOps.AirGap.Bundle.csproj" />
<ProjectReference Include="../../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -14,6 +14,8 @@ using System.Text.Json;
using FluentAssertions;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.AirGap.Controller.Tests;
/// <summary>
@@ -26,7 +28,8 @@ public sealed class AirGapControllerContractTests
{
#region AIRGAP-5100-010: Contract Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Contract_ExportEndpoint_ExpectedRequestStructure()
{
// Arrange - Define expected request structure
@@ -56,7 +59,8 @@ public sealed class AirGapControllerContractTests
feeds.GetArrayLength().Should().BeGreaterThan(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Contract_ExportEndpoint_ExpectedResponseStructure()
{
// Arrange - Define expected response structure
@@ -87,7 +91,8 @@ public sealed class AirGapControllerContractTests
parsed.RootElement.TryGetProperty("manifest", out _).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Contract_ImportEndpoint_ExpectedRequestStructure()
{
// Arrange - Import request (typically multipart form or bundle URL)
@@ -107,7 +112,8 @@ public sealed class AirGapControllerContractTests
parsed.RootElement.TryGetProperty("bundleDigest", out _).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Contract_ImportEndpoint_ExpectedResponseStructure()
{
// Arrange
@@ -131,7 +137,8 @@ public sealed class AirGapControllerContractTests
parsed.RootElement.TryGetProperty("feedsImported", out _).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Contract_ListBundlesEndpoint_ExpectedResponseStructure()
{
// Arrange
@@ -164,7 +171,8 @@ public sealed class AirGapControllerContractTests
parsed.RootElement.TryGetProperty("total", out _).Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Contract_StateEndpoint_ExpectedResponseStructure()
{
// Arrange - AirGap state response
@@ -197,7 +205,8 @@ public sealed class AirGapControllerContractTests
#region AIRGAP-5100-011: Auth Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Auth_RequiredScopes_ForExport()
{
// Arrange - Expected scopes for export operation
@@ -207,7 +216,8 @@ public sealed class AirGapControllerContractTests
requiredScopes.Should().Contain("airgap:export");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Auth_RequiredScopes_ForImport()
{
// Arrange - Expected scopes for import operation
@@ -217,7 +227,8 @@ public sealed class AirGapControllerContractTests
requiredScopes.Should().Contain("airgap:import");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Auth_RequiredScopes_ForList()
{
// Arrange - Expected scopes for list operation
@@ -227,7 +238,8 @@ public sealed class AirGapControllerContractTests
requiredScopes.Should().Contain("airgap:read");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Auth_DenyByDefault_NoTokenReturnsUnauthorized()
{
// Arrange - Request without token
@@ -237,7 +249,8 @@ public sealed class AirGapControllerContractTests
expectedStatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Auth_TenantIsolation_CannotAccessOtherTenantBundles()
{
// Arrange - Claims for tenant A
@@ -256,7 +269,8 @@ public sealed class AirGapControllerContractTests
// Requests for tenant-B bundles should be rejected
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Auth_TokenExpiry_ExpiredTokenReturnsForbidden()
{
// Arrange - Expired token scenario
@@ -272,7 +286,8 @@ public sealed class AirGapControllerContractTests
#region AIRGAP-5100-012: OTel Trace Assertions
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OTel_ExportOperation_IncludesBundleIdTag()
{
// Arrange
@@ -289,7 +304,8 @@ public sealed class AirGapControllerContractTests
expectedTags.Should().Contain("operation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OTel_ImportOperation_IncludesOperationTag()
{
// Arrange
@@ -305,7 +321,8 @@ public sealed class AirGapControllerContractTests
expectedTags["operation"].Should().Be("airgap.import");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OTel_Metrics_TracksExportCount()
{
// Arrange
@@ -317,7 +334,8 @@ public sealed class AirGapControllerContractTests
metricName.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OTel_Metrics_TracksImportCount()
{
// Arrange
@@ -329,7 +347,8 @@ public sealed class AirGapControllerContractTests
expectedDimensions.Should().Contain("status");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OTel_ActivitySource_HasCorrectName()
{
// Arrange
@@ -339,7 +358,8 @@ public sealed class AirGapControllerContractTests
expectedSourceName.Should().StartWith("StellaOps.");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OTel_Spans_PropagateTraceContext()
{
// Arrange - Create a trace context

View File

@@ -24,6 +24,7 @@
<ItemGroup>
<ProjectReference Include="..\\..\\StellaOps.AirGap.Importer\\StellaOps.AirGap.Importer.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>