consolidate the tests locations

This commit is contained in:
StellaOps Bot
2025-12-26 01:48:24 +02:00
parent 17613acf57
commit 39359da171
2031 changed files with 2607 additions and 476 deletions

View File

@@ -0,0 +1,53 @@
namespace StellaOps.Testing.AirGap.Docker;
/// <summary>
/// Builds containers with network isolation for air-gap testing.
/// </summary>
public sealed class IsolatedContainerBuilder
{
/// <summary>
/// Creates a container configuration with no network access.
/// </summary>
public ContainerConfiguration CreateIsolatedConfiguration(
string image,
IReadOnlyList<string> volumes)
{
return new ContainerConfiguration
{
Image = image,
NetworkMode = "none", // No network!
Volumes = volumes,
AutoRemove = true,
Environment = new Dictionary<string, string>
{
["STELLAOPS_OFFLINE_MODE"] = "true",
["HTTP_PROXY"] = "",
["HTTPS_PROXY"] = "",
["NO_PROXY"] = "*"
}
};
}
/// <summary>
/// Verifies that a container has no network access.
/// </summary>
public async Task<bool> VerifyNoNetworkAsync(
string containerId,
CancellationToken ct = default)
{
// TODO: Implement actual container exec to test network
// For now, return true (assume configuration is correct)
await Task.CompletedTask;
return true;
}
}
public sealed record ContainerConfiguration
{
public required string Image { get; init; }
public required string NetworkMode { get; init; }
public IReadOnlyList<string> Volumes { get; init; } = [];
public bool AutoRemove { get; init; }
public IReadOnlyDictionary<string, string> Environment { get; init; } =
new Dictionary<string, string>();
}

View File

@@ -0,0 +1,148 @@
namespace StellaOps.Testing.AirGap;
using System.Net.Sockets;
using System.Runtime.ExceptionServices;
using Xunit;
/// <summary>
/// Base class for tests that must run without network access.
/// Monitors and blocks any network calls during test execution.
/// </summary>
public abstract class NetworkIsolatedTestBase : IAsyncLifetime
{
private readonly NetworkMonitor _monitor;
private readonly List<NetworkAttempt> _blockedAttempts = [];
protected NetworkIsolatedTestBase()
{
_monitor = new NetworkMonitor(OnNetworkAttempt);
}
public virtual async Task InitializeAsync()
{
// Install network interception
await _monitor.StartMonitoringAsync();
// Configure HttpClient factory to use monitored handler
Environment.SetEnvironmentVariable("STELLAOPS_OFFLINE_MODE", "true");
// Block DNS resolution
_monitor.BlockDns();
}
public virtual async Task DisposeAsync()
{
await _monitor.StopMonitoringAsync();
// Fail test if any network calls were attempted
if (_blockedAttempts.Count > 0)
{
var attempts = string.Join("\n", _blockedAttempts.Select(a =>
$" - {a.Host}:{a.Port} at {a.Timestamp:O}\n{a.StackTrace}"));
throw new NetworkIsolationViolationException(
$"Test attempted {_blockedAttempts.Count} network call(s):\n{attempts}");
}
}
private void OnNetworkAttempt(NetworkAttempt attempt)
{
_blockedAttempts.Add(attempt);
}
/// <summary>
/// Asserts that no network calls were made during the test.
/// </summary>
protected void AssertNoNetworkCalls()
{
if (_blockedAttempts.Count > 0)
{
throw new NetworkIsolationViolationException(
$"Network isolation violated: {_blockedAttempts.Count} attempts blocked");
}
}
/// <summary>
/// Gets the offline bundle path for this test.
/// </summary>
protected string GetOfflineBundlePath() =>
Environment.GetEnvironmentVariable("STELLAOPS_OFFLINE_BUNDLE")
?? Path.Combine(AppContext.BaseDirectory, "fixtures", "offline-bundle");
}
public sealed class NetworkMonitor : IAsyncDisposable
{
private readonly Action<NetworkAttempt> _onAttempt;
private bool _isMonitoring;
private EventHandler<FirstChanceExceptionEventArgs>? _exceptionHandler;
public NetworkMonitor(Action<NetworkAttempt> onAttempt)
{
_onAttempt = onAttempt;
}
public Task StartMonitoringAsync()
{
_isMonitoring = true;
// Hook into socket creation
_exceptionHandler = OnException;
AppDomain.CurrentDomain.FirstChanceException += _exceptionHandler;
return Task.CompletedTask;
}
public Task StopMonitoringAsync()
{
_isMonitoring = false;
if (_exceptionHandler != null)
{
AppDomain.CurrentDomain.FirstChanceException -= _exceptionHandler;
}
return Task.CompletedTask;
}
public void BlockDns()
{
// Set environment to prevent DNS lookups
Environment.SetEnvironmentVariable("RES_OPTIONS", "timeout:0 attempts:0");
}
private void OnException(object? sender, FirstChanceExceptionEventArgs e)
{
if (!_isMonitoring) return;
if (e.Exception is SocketException se)
{
_onAttempt(new NetworkAttempt(
Host: "unknown",
Port: 0,
StackTrace: se.StackTrace ?? Environment.StackTrace,
Timestamp: DateTimeOffset.UtcNow));
}
else if (e.Exception is HttpRequestException hre)
{
_onAttempt(new NetworkAttempt(
Host: hre.Message,
Port: 0,
StackTrace: hre.StackTrace ?? Environment.StackTrace,
Timestamp: DateTimeOffset.UtcNow));
}
}
public ValueTask DisposeAsync()
{
_isMonitoring = false;
return ValueTask.CompletedTask;
}
}
public sealed record NetworkAttempt(
string Host,
int Port,
string StackTrace,
DateTimeOffset Timestamp);
public sealed class NetworkIsolationViolationException : Exception
{
public NetworkIsolationViolationException(string message) : base(message) { }
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.core" Version="2.6.6" />
</ItemGroup>
</Project>