Files
libnftables-dotnet/tests/LibNftables.Tests/NftablesClientUnitTests.cs
2026-03-16 03:45:00 +00:00

253 lines
7.3 KiB
C#

namespace LibNftables.Tests;
public sealed class NftablesClientUnitTests
{
[Fact]
public void Apply_WithBothTextAndFile_ThrowsValidationException()
{
var client = CreateClient();
var request = new NftApplyRequest
{
RulesetText = "flush ruleset",
RulesetFilePath = "/tmp/does-not-matter.nft",
};
Assert.Throws<NftValidationException>(() => client.Apply(request));
}
[Fact]
public void Apply_WithMissingFile_ThrowsValidationException()
{
var client = CreateClient();
var request = NftApplyRequest.FromFile("/tmp/does-not-exist.nft");
Assert.Throws<NftValidationException>(() => client.Apply(request));
}
[Fact]
public void Apply_NullRequest_ThrowsValidationException()
{
var client = CreateClient();
Assert.Throws<NftValidationException>(() => client.Apply(null!));
}
[Fact]
public void Validate_WhenCommandThrowsValidation_ReturnsInvalidResult()
{
var context = new FakeContext
{
CommandException = new NftValidationException("invalid ruleset", 7, "syntax error"),
};
var client = CreateClient(() => context);
NftValidationResult result = client.Validate(NftApplyRequest.FromText("invalid"));
Assert.False(result.IsValid);
Assert.Equal("syntax error", result.Diagnostics);
}
[Fact]
public async Task ValidateAsync_UsesSynchronousValidationFlow()
{
var context = new FakeContext
{
OutputBuffer = "ok",
ErrorBuffer = string.Empty,
};
var client = CreateClient(() => context);
NftValidationResult result = await client.ValidateAsync(NftApplyRequest.FromText("flush ruleset", dryRun: false));
Assert.True(result.IsValid);
Assert.True(context.DryRun);
Assert.Equal("ok", result.Output);
}
[Fact]
public void Apply_WithFileRequest_UsesFileExecutionPath()
{
string path = Path.GetTempFileName();
try
{
var context = new FakeContext();
var client = CreateClient(() => context);
client.Apply(NftApplyRequest.FromFile(path));
Assert.Equal(path, context.LastFilePath);
Assert.Null(context.LastCommandText);
}
finally
{
File.Delete(path);
}
}
[Fact]
public void Apply_PropagatesOptionsAndRequestFlags()
{
var context = new FakeContext();
var options = new NftablesClientOptions
{
DefaultInputFlags = NftInputFlags.NoDns,
DefaultOutputFlags = NftOutputFlags.Json,
DefaultDebugFlags = NftDebugLevel.Parser,
DefaultOptimizeFlags = NftOptimizeFlags.Enabled,
};
var request = NftApplyRequest.FromText("flush ruleset", dryRun: true);
request.InputFlags = NftInputFlags.Json;
request.OutputFlags = NftOutputFlags.Echo;
request.DebugFlags = NftDebugLevel.Scanner;
var client = CreateClient(() => context, options);
client.Apply(request);
Assert.True(context.DryRun);
Assert.Equal(NftInputFlags.NoDns | NftInputFlags.Json, context.InputFlags);
Assert.Equal(NftOutputFlags.Json | NftOutputFlags.Echo, context.OutputFlags);
Assert.Equal(NftDebugLevel.Parser | NftDebugLevel.Scanner, context.DebugFlags);
Assert.Equal(NftOptimizeFlags.Enabled, context.OptimizeFlags);
Assert.True(context.BufferOutputCalled);
Assert.True(context.BufferErrorCalled);
}
[Fact]
public void Snapshot_WithOutput_ReturnsSnapshot()
{
var context = new FakeContext
{
OutputBuffer = "table inet filter { }",
ErrorBuffer = string.Empty,
};
var client = CreateClient(() => context);
NftSnapshot snapshot = client.Snapshot();
Assert.Equal("table inet filter { }", snapshot.RulesetText);
Assert.Equal("list ruleset", context.LastCommandText);
}
[Fact]
public void Snapshot_WithPermissionError_ThrowsPermissionException()
{
var context = new FakeContext
{
OutputBuffer = string.Empty,
ErrorBuffer = "Operation not permitted",
};
var client = CreateClient(() => context);
Assert.Throws<NftPermissionException>(() => client.Snapshot());
}
[Fact]
public void Snapshot_WithEmptyOutputAndNoError_ReturnsFlushRulesetFallback()
{
var context = new FakeContext
{
OutputBuffer = string.Empty,
ErrorBuffer = string.Empty,
};
var client = CreateClient(() => context);
NftSnapshot snapshot = client.Snapshot();
Assert.Equal("flush ruleset", snapshot.RulesetText);
}
[Fact]
public void Restore_WithNullSnapshot_ThrowsValidationException()
{
var client = CreateClient();
Assert.Throws<NftValidationException>(() => client.Restore(null!));
}
[Fact]
public void Restore_WithEmptyRuleset_ThrowsValidationException()
{
var client = CreateClient();
Assert.Throws<NftValidationException>(() => client.Restore(new NftSnapshot(" ", DateTimeOffset.UtcNow)));
}
[Fact]
public async Task RestoreAsync_UsesSnapshotRulesetText()
{
var context = new FakeContext();
var client = CreateClient(() => context);
var snapshot = new NftSnapshot("flush ruleset", DateTimeOffset.UtcNow);
await client.RestoreAsync(snapshot);
Assert.Equal("flush ruleset", context.LastCommandText);
}
private static NftablesClient CreateClient(
Func<INftContextHandle>? contextFactory = null,
NftablesClientOptions? options = null)
=> new(options, contextFactory ?? (() => new FakeContext()), skipRuntimeGuard: true);
private sealed class FakeContext : INftContextHandle
{
public bool DryRun { get; set; }
public NftOptimizeFlags OptimizeFlags { get; set; }
public NftInputFlags InputFlags { get; set; }
public NftOutputFlags OutputFlags { get; set; }
public NftDebugLevel DebugFlags { get; set; }
public bool BufferOutputCalled { get; private set; }
public bool BufferErrorCalled { get; private set; }
public string? OutputBuffer { get; set; }
public string? ErrorBuffer { get; set; }
public string? LastCommandText { get; private set; }
public string? LastFilePath { get; private set; }
public Exception? CommandException { get; set; }
public void BufferOutput() => BufferOutputCalled = true;
public void BufferError() => BufferErrorCalled = true;
public string? GetOutputBuffer() => OutputBuffer;
public string? GetErrorBuffer() => ErrorBuffer;
public void RunCommand(string commandText)
{
LastCommandText = commandText;
ThrowIfNeeded();
}
public void RunCommandFromFile(string path)
{
LastFilePath = path;
ThrowIfNeeded();
}
public void Dispose()
{
}
private void ThrowIfNeeded()
{
if (CommandException is not null)
{
throw CommandException;
}
}
}
}