Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.AirGap.Bundle\StellaOps.AirGap.Bundle.csproj" />
|
||||
<ProjectReference Include="../../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user