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(() => 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; } } }