Add src files

This commit is contained in:
Vibe Myass
2026-03-11 01:40:27 +00:00
parent b6617bb221
commit 05fda54da2
32 changed files with 2852 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\LibNftables\LibNftables.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,142 @@
using System.Globalization;
namespace LibNftables.Tests;
public sealed class NftContextTests
{
[Fact]
public void ContextFlagsRoundTrip_Works()
{
if (!TryCreateContext(out var ctx))
{
return;
}
using (ctx)
{
ctx.DryRun = true;
Assert.True(ctx.DryRun);
ctx.OptimizeFlags = NftOptimizeFlags.Enabled;
Assert.True(ctx.OptimizeFlags.HasFlag(NftOptimizeFlags.Enabled));
ctx.InputFlags = NftInputFlags.NoDns | NftInputFlags.Json;
Assert.True(ctx.InputFlags.HasFlag(NftInputFlags.NoDns));
Assert.True(ctx.InputFlags.HasFlag(NftInputFlags.Json));
ctx.OutputFlags = NftOutputFlags.Json | NftOutputFlags.Echo | NftOutputFlags.Terse;
Assert.True(ctx.OutputFlags.HasFlag(NftOutputFlags.Json));
Assert.True(ctx.OutputFlags.HasFlag(NftOutputFlags.Echo));
Assert.True(ctx.OutputFlags.HasFlag(NftOutputFlags.Terse));
ctx.DebugFlags = NftDebugLevel.Parser | NftDebugLevel.Scanner;
Assert.True(ctx.DebugFlags.HasFlag(NftDebugLevel.Parser));
Assert.True(ctx.DebugFlags.HasFlag(NftDebugLevel.Scanner));
}
}
[Fact]
public void InvalidCommand_ThrowsNftExceptionWithErrorBuffer()
{
if (!TryCreateContext(out var ctx))
{
return;
}
using (ctx)
{
ctx.DryRun = true;
ctx.BufferError();
var ex = Assert.ThrowsAny<NftException>(() => ctx.RunCommand("this is not valid nft syntax"));
Assert.NotEqual(0, ex.NativeErrorCode);
Assert.False(string.IsNullOrWhiteSpace(ex.NativeErrorOutput));
}
}
[Fact]
public void ValidDryRunCommand_CanExecuteAndBufferOutput()
{
if (!TryCreateContext(out var ctx))
{
return;
}
if (!HasCapNetAdmin())
{
return;
}
using (ctx)
{
ctx.DryRun = true;
ctx.BufferOutput();
ctx.BufferError();
ctx.RunCommand("add table inet swig_test");
var output = ctx.GetOutputBuffer();
var error = ctx.GetErrorBuffer();
Assert.NotNull(output);
Assert.True(string.IsNullOrEmpty(error), $"Unexpected error buffer: {error}");
}
}
private static bool HasCapNetAdmin()
{
const int capNetAdminBit = 12;
const ulong mask = 1UL << capNetAdminBit;
try
{
foreach (var line in File.ReadLines("/proc/self/status"))
{
if (!line.StartsWith("CapEff:", StringComparison.Ordinal))
{
continue;
}
var hex = line["CapEff:".Length..].Trim();
if (ulong.TryParse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var value))
{
return (value & mask) != 0;
}
}
}
catch
{
// If capability probing fails, keep test conservative.
}
return false;
}
private static bool TryCreateContext(out NftContext context)
{
try
{
context = new NftContext();
return true;
}
catch (DllNotFoundException)
{
context = null!;
return false;
}
catch (TypeInitializationException ex) when (ex.InnerException is DllNotFoundException)
{
context = null!;
return false;
}
catch (BadImageFormatException)
{
context = null!;
return false;
}
catch (EntryPointNotFoundException)
{
context = null!;
return false;
}
}
}

View File

@@ -0,0 +1,25 @@
namespace LibNftables.Tests;
public sealed class NftEnumTests
{
[Fact]
public void OutputNumericAll_ComposesIndividualNumericFlags()
{
var expected = NftOutputFlags.NumericProto |
NftOutputFlags.NumericPrio |
NftOutputFlags.NumericSymbol |
NftOutputFlags.NumericTime;
Assert.Equal(NftOutputFlags.NumericAll, expected);
}
[Fact]
public void DebugFlags_AreBitwiseDistinct()
{
var combined = NftDebugLevel.Scanner | NftDebugLevel.Parser | NftDebugLevel.Evaluation;
Assert.True(combined.HasFlag(NftDebugLevel.Scanner));
Assert.True(combined.HasFlag(NftDebugLevel.Parser));
Assert.True(combined.HasFlag(NftDebugLevel.Evaluation));
}
}

View File

@@ -0,0 +1,66 @@
namespace LibNftables.Tests;
public sealed class NftablesClientTests
{
[Fact]
public void Apply_WithBothTextAndFile_ThrowsValidationException()
{
var client = new NftablesClient();
var request = new NftApplyRequest
{
RulesetText = "flush ruleset",
RulesetFilePath = "/tmp/does-not-matter.nft",
};
Assert.Throws<NftValidationException>(() => client.Apply(request));
}
[Fact]
public void Validate_InvalidRuleset_ReturnsInvalidResult()
{
var client = new NftablesClient();
var request = NftApplyRequest.FromText("this is not valid nft syntax");
NftValidationResult result = client.Validate(request);
Assert.False(result.IsValid);
Assert.False(string.IsNullOrWhiteSpace(result.Diagnostics));
}
[Fact]
public async System.Threading.Tasks.Task ValidateAsync_InvalidRuleset_ReturnsInvalidResult()
{
var client = new NftablesClient();
var request = NftApplyRequest.FromText("this is not valid nft syntax");
NftValidationResult result = await client.ValidateAsync(request);
Assert.False(result.IsValid);
Assert.False(string.IsNullOrWhiteSpace(result.Diagnostics));
}
[Fact]
public void Apply_InvalidRuleset_ThrowsValidationException()
{
var client = new NftablesClient();
var request = NftApplyRequest.FromText("this is not valid nft syntax");
Assert.Throws<NftValidationException>(() => client.Apply(request));
}
[Fact]
public void Snapshot_WithInsufficientPrivileges_ThrowsPermissionOrReturnsRuleset()
{
var client = new NftablesClient();
try
{
NftSnapshot snapshot = client.Snapshot();
Assert.False(string.IsNullOrWhiteSpace(snapshot.RulesetText));
}
catch (NftPermissionException)
{
// Expected in unprivileged environments.
}
}
}