consolidate the tests locations
This commit is contained in:
@@ -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>();
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user