tests/Application/ConfusableMappingsTests.cs

Unit tests for ConfusableMappings in the application layer. They verify the pinned Unicode version and SHA-256, check that the mapping table is consistent and reachable via binary search across the whole Unicode range, and assert several known spot mappings and non-source prototypes.

using Hexagon.V2.Application;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class ConfusableMappingsTests
{
	[TestMethod]
	public void TableIsPinnedToAReviewedUnicodeRelease()
	{
		// The table decides who is allowed to impersonate whom, so it is pinned by version and by
		// the SHA-256 of the upstream file. Changing either is a reviewed act: rerun
		// tools/generate-confusables.ps1, which refuses to build a table from unpinned data.
		Assert.AreEqual( "16.0.0", ConfusableMappings.UnicodeVersion );
		Assert.AreEqual(
			"95BD0AAD6DCED5EBC63436F459C06AB21A8D107CD842FB57F5C3A1E91BCA8611",
			ConfusableMappings.SourceSha256 );
		Assert.AreEqual( 6355, ConfusableMappings.EntryCount );
	}

	[TestMethod]
	public void EveryPinnedEntryIsReachableThroughTheBinarySearch()
	{
		// Sweeping the whole code space proves the parallel arrays agree in length, that the
		// source array is sorted (an unsorted entry would be unreachable), and that every
		// sequence index resolves - the ways a hand-edited generated file goes wrong.
		var found = 0;
		for ( var codePoint = 0; codePoint <= 0x10FFFF; codePoint++ )
		{
			if ( !ConfusableMappings.TryMap( codePoint, out var single, out var sequence ) ) continue;
			found++;
			if ( sequence is not null ) Assert.IsGreaterThan( 0, sequence.Length );
			else Assert.IsTrue( single is >= 0 and <= 0x10FFFF );
		}

		Assert.AreEqual( ConfusableMappings.EntryCount, found );
	}

	[TestMethod]
	public void SpotChecksMatchTheUpstreamTable()
	{
		AssertMapsTo( 0x0430, "a" );   // CYRILLIC SMALL LETTER A
		AssertMapsTo( 0x0435, "e" );   // CYRILLIC SMALL LETTER IE
		AssertMapsTo( 0x0031, "l" );   // DIGIT ONE
		AssertMapsTo( 0x0049, "l" );   // LATIN CAPITAL LETTER I
		AssertMapsTo( 0x006D, "rn" );  // LATIN SMALL LETTER M - the standard expands, not collapses
		AssertMapsTo( 0x0030, "O" );   // DIGIT ZERO

		// Prototypes are not themselves sources, or folding would not terminate.
		Assert.IsFalse( ConfusableMappings.TryMap( 'a', out _, out _ ) );
		Assert.IsFalse( ConfusableMappings.TryMap( 'l', out _, out _ ) );
		Assert.IsFalse( ConfusableMappings.TryMap( 'i', out _, out _ ) );
	}

	private static void AssertMapsTo( int codePoint, string expected )
	{
		Assert.IsTrue(
			ConfusableMappings.TryMap( codePoint, out var single, out var sequence ),
			$"U+{codePoint:X4} should be a confusable source." );
		var actual = sequence ?? char.ConvertFromUtf32( single );
		Assert.AreEqual( expected, actual, $"U+{codePoint:X4}" );
	}
}