tests/SboxSuperuser.Tests/CommandGrammarTests.cs

Unit test file for command parsing and scalar conversions. Tests tokenization edge cases, quoting/escaping behavior, input length limits, duration parsing, integer bounds, and robustness on random malformed input.

File Access
using SboxSuperuser.Core.Commands;
using Xunit;

namespace SboxSuperuser.Tests;

public sealed class CommandGrammarTests
{
	[Theory]
	[InlineData( "\"unfinished" )]
	[InlineData( "'unfinished" )]
	[InlineData( "name\"tail\"" )]
	[InlineData( "\"name\"tail" )]
	[InlineData( "\"bad\\q\"" )]
	[InlineData( "one\ntwo" )]
	[InlineData( "one\0two" )]
	public void MalformedTextNeverReturnsPartialTokens( string text )
	{
		var result = CommandTokenizer.Tokenize( "valid " + text );
		Assert.NotNull( result.Error );
		Assert.Empty( result.Tokens );
	}

	[Fact]
	public void QuotesAndEscapesPreserveLiteralTokensIncludingEmptyValues()
	{
		var result = CommandTokenizer.Tokenize( "  ban\t\"Jane Doe\" '' \"quoted \\\"reason\\\"\" 'a\\\\b'" );
		Assert.Null( result.Error );
		Assert.Equal( new[] { "ban", "Jane Doe", "", "quoted \"reason\"", "a\\b" }, result.Tokens );
	}

	[Fact]
	public void InputLimitsRejectBeforeReturningWork()
	{
		foreach ( var input in new[] { new string( ' ', 4097 ), new string( 'x', 513 ), string.Join( ' ', Enumerable.Repeat( "x", 65 ) ) } )
		{
			Assert.NotNull( CommandTokenizer.Tokenize( input ).Error );
		}

		Assert.Null( CommandTokenizer.Tokenize( new string( 'x', 512 ) ).Error );
		Assert.Null( CommandTokenizer.Tokenize( string.Join( ' ', Enumerable.Repeat( "x", 64 ) ) ).Error );
	}

	[Theory]
	[InlineData( "0s" )]
	[InlineData( "-1h" )]
	[InlineData( "+1h" )]
	[InlineData( "01h" )]
	[InlineData( "1.5h" )]
	[InlineData( "1h30m" )]
	[InlineData( "1" )]
	[InlineData( "forever" )]
	[InlineData( "9223372036854775807w" )]
	public void InvalidDurationsNeverBecomePermanentOrOverflow( string input )
	{
		Assert.False( ScalarConversion.Duration( input, out _ ) );
	}

	[Fact]
	public void IntegerBoundsDoNotRoundLargeIntegersOrOverflowOnWideBounds()
	{
		Assert.True( ScalarConversion.Integer( "9223372036854775807", -1e300, 1e300, out var maximum ) );
		Assert.Equal( long.MaxValue, maximum );
		Assert.False( ScalarConversion.Integer( "9223372036854775808", null, null, out _ ) );
		Assert.False( ScalarConversion.Integer( "9007199254740993", null, 9007199254740992d, out _ ) );
		Assert.False( ScalarConversion.Integer( "-9223372036854775808", 0, null, out _ ) );
		Assert.False( ScalarConversion.Integer( "1", 1.1, null, out _ ) );
		Assert.True( ScalarConversion.Integer( "2", 1.1, 2.9, out _ ) );
		Assert.False( ScalarConversion.Integer( "9223372036854775807", 9223372036854775808d, null, out _ ) );
	}

	[Fact]
	public void ArbitraryMalformedTextIsBoundedAndNeverThrows()
	{
		var random = new Random( 8 );
		const string alphabet = "a '\"\\\t\r\n0";
		for ( var iteration = 0; iteration < 2000; iteration++ )
		{
			var text = new string( Enumerable.Range( 0, random.Next( 200 ) ).Select( _ => alphabet[ random.Next( alphabet.Length ) ] ).ToArray() );
			var result = CommandTokenizer.Tokenize( text );
			Assert.True( result.Tokens.Count <= 64 );
			if ( result.Error is not null )
			{
				Assert.Empty( result.Tokens );
			}
		}
	}
}