Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -87,9 +87,9 @@ public sealed class AirGapIntegrationTests : IDisposable
var offlineBundlePath = Path.Combine(_offlineEnvPath, "imported-bundle");
CopyDirectory(bundleOutputPath, offlineBundlePath);
// Import in offline environment
var loader = new BundleLoader();
var importedManifest = await loader.LoadAsync(offlineBundlePath);
// Import in offline environment - load manifest directly
var importedManifestJson = await File.ReadAllTextAsync(Path.Combine(offlineBundlePath, "manifest.json"));
var importedManifest = BundleManifestSerializer.Deserialize(importedManifestJson);
// Verify data integrity
var importedFeedPath = Path.Combine(offlineBundlePath, "feeds/nvd.json");
@@ -132,8 +132,9 @@ public sealed class AirGapIntegrationTests : IDisposable
var offlinePath = Path.Combine(_offlineEnvPath, "multi-imported");
CopyDirectory(bundlePath, offlinePath);
var loader = new BundleLoader();
var imported = await loader.LoadAsync(offlinePath);
// Load manifest directly
var loadedJson = await File.ReadAllTextAsync(Path.Combine(offlinePath, "manifest.json"));
var imported = BundleManifestSerializer.Deserialize(loadedJson);
// Assert - All components transferred
imported.Feeds.Should().HaveCount(1);
@@ -173,9 +174,9 @@ public sealed class AirGapIntegrationTests : IDisposable
// Corrupt the feed file after transfer
await File.WriteAllTextAsync(Path.Combine(offlinePath, "feeds/nvd.json"), """{"corrupted":"malicious data"}""");
// Act - Load (should succeed but digest verification would fail)
var loader = new BundleLoader();
var imported = await loader.LoadAsync(offlinePath);
// Act - Load manifest directly (digest verification would fail if validated)
var loadedJson = await File.ReadAllTextAsync(Path.Combine(offlinePath, "manifest.json"));
var imported = BundleManifestSerializer.Deserialize(loadedJson);
// Verify digest mismatch
var actualContent = await File.ReadAllTextAsync(Path.Combine(offlinePath, "feeds/nvd.json"));
@@ -230,9 +231,9 @@ public sealed class AirGapIntegrationTests : IDisposable
var offlinePath = Path.Combine(_offlineEnvPath, "policy-imported");
CopyDirectory(bundlePath, offlinePath);
// Load in offline
var loader = new BundleLoader();
var imported = await loader.LoadAsync(offlinePath);
// Load manifest directly
var loadedJson = await File.ReadAllTextAsync(Path.Combine(offlinePath, "manifest.json"));
var imported = BundleManifestSerializer.Deserialize(loadedJson);
// Verify policy content
var importedPolicyPath = Path.Combine(offlinePath, "policies/security.rego");
@@ -283,8 +284,9 @@ public sealed class AirGapIntegrationTests : IDisposable
var offlinePath = Path.Combine(_offlineEnvPath, "multi-policy-imported");
CopyDirectory(bundlePath, offlinePath);
var loader = new BundleLoader();
var imported = await loader.LoadAsync(offlinePath);
// Load manifest directly
var loadedJson = await File.ReadAllTextAsync(Path.Combine(offlinePath, "manifest.json"));
var imported = BundleManifestSerializer.Deserialize(loadedJson);
// Assert
imported.Policies.Should().HaveCount(3);
@@ -313,7 +315,7 @@ public sealed class AirGapIntegrationTests : IDisposable
null,
Array.Empty<FeedBuildConfig>(),
new[] { new PolicyBuildConfig("signed-policy", "signed", "1.0", policyPath, "policies/signed.rego", PolicyType.OpaRego) },
new[] { new CryptoBuildConfig("signing-cert", "signing", certPath, "certs/signing.pem", CryptoComponentType.SigningCertificate, null) });
new[] { new CryptoBuildConfig("signing-cert", "signing", certPath, "certs/signing.pem", CryptoComponentType.SigningKey, null) });
var bundlePath = Path.Combine(_onlineEnvPath, "signed-bundle");
@@ -324,8 +326,9 @@ public sealed class AirGapIntegrationTests : IDisposable
var offlinePath = Path.Combine(_offlineEnvPath, "signed-imported");
CopyDirectory(bundlePath, offlinePath);
var loader = new BundleLoader();
var imported = await loader.LoadAsync(offlinePath);
// Load manifest directly
var loadedJson = await File.ReadAllTextAsync(Path.Combine(offlinePath, "manifest.json"));
var imported = BundleManifestSerializer.Deserialize(loadedJson);
// Assert
imported.Policies.Should().HaveCount(1);

View File

@@ -5,6 +5,7 @@ using FluentAssertions;
using StellaOps.AirGap.Bundle.Models;
using StellaOps.AirGap.Bundle.Serialization;
using StellaOps.AirGap.Bundle.Services;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.AirGap.Bundle.Tests;
@@ -120,7 +121,7 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
#region Roundtrip Determinism Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task Roundtrip_ExportImportReexport_IdenticalBundle()
{
// Arrange
@@ -152,7 +153,6 @@ 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",

View File

@@ -12,6 +12,7 @@ using FluentAssertions;
using StellaOps.AirGap.Bundle.Models;
using StellaOps.AirGap.Bundle.Serialization;
using StellaOps.AirGap.Bundle.Services;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.AirGap.Bundle.Tests;
@@ -166,9 +167,9 @@ public sealed class BundleExportImportTests : IDisposable
var manifestPath = Path.Combine(bundlePath, "manifest.json");
await File.WriteAllTextAsync(manifestPath, BundleManifestSerializer.Serialize(manifest));
// Act - Load the bundle
var loader = new BundleLoader();
var loaded = await loader.LoadAsync(bundlePath);
// Act - Load the bundle manifest directly
var loadedJson = await File.ReadAllTextAsync(manifestPath);
var loaded = BundleManifestSerializer.Deserialize(loadedJson);
// Assert
loaded.Should().NotBeNull();
@@ -192,14 +193,14 @@ public sealed class BundleExportImportTests : IDisposable
var manifestPath = Path.Combine(bundlePath, "manifest.json");
await File.WriteAllTextAsync(manifestPath, BundleManifestSerializer.Serialize(manifest));
// Act
var loader = new BundleLoader();
var loaded = await loader.LoadAsync(bundlePath);
// Act - Load manifest directly
var loadedJson = await File.ReadAllTextAsync(manifestPath);
var loaded = BundleManifestSerializer.Deserialize(loadedJson);
// Assert - Verify file exists and digest matches
var feedPath = Path.Combine(bundlePath, "feeds", "nvd.json");
File.Exists(feedPath).Should().BeTrue();
var actualContent = await File.ReadAllTextAsync(feedPath);
var actualDigest = ComputeSha256Hex(actualContent);
loaded.Feeds[0].Digest.Should().Be(actualDigest);
@@ -224,9 +225,9 @@ public sealed class BundleExportImportTests : IDisposable
var corruptPath = Path.Combine(bundlePath, "feeds", "nvd.json");
await File.WriteAllTextAsync(corruptPath, """{"corrupted":"data"}""");
// Act
var loader = new BundleLoader();
var loaded = await loader.LoadAsync(bundlePath);
// Act - Load manifest directly (original digest was computed before corruption)
var loadedJson = await File.ReadAllTextAsync(manifestPath);
var loaded = BundleManifestSerializer.Deserialize(loadedJson);
// Assert - File content has changed, digest no longer matches
var actualContent = await File.ReadAllTextAsync(corruptPath);
@@ -326,7 +327,7 @@ public sealed class BundleExportImportTests : IDisposable
#region AIRGAP-5100-004: Roundtrip Determinism (Export Import Re-export)
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task Roundtrip_ExportImportReexport_ProducesIdenticalFileDigests()
{
// Arrange - Initial export
@@ -340,16 +341,15 @@ public sealed class BundleExportImportTests : IDisposable
var manifest1 = await builder.BuildAsync(request, bundlePath1);
var digest1 = manifest1.Feeds[0].Digest;
// Import by loading manifest
// Import by loading manifest directly
var manifestJson = BundleManifestSerializer.Serialize(manifest1);
await File.WriteAllTextAsync(Path.Combine(bundlePath1, "manifest.json"), manifestJson);
var loader = new BundleLoader();
var imported = await loader.LoadAsync(bundlePath1);
var loadedJson = await File.ReadAllTextAsync(Path.Combine(bundlePath1, "manifest.json"));
var imported = BundleManifestSerializer.Deserialize(loadedJson);
// 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,

View File

@@ -554,7 +554,6 @@ public sealed class BundleImportTests : IAsyncLifetime
private static async Task<string> ComputeFileDigestAsync(string filePath)
{
await using var stream = File.OpenRead(filePath);
using StellaOps.TestKit;
var hash = await SHA256.HashDataAsync(stream);
return Convert.ToHexString(hash).ToLowerInvariant();
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
@@ -6,16 +6,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="NSubstitute" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AirGap.Bundle\StellaOps.AirGap.Bundle.csproj" />
<ProjectReference Include="../../StellaOps.AirGap.Bundle/StellaOps.AirGap.Bundle.csproj" />
<ProjectReference Include="../../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>
</Project>