tests/Application/SceneIdentityValidatorTests.cs

Unit tests for scene identity validation and indexing. Exercises SceneIdentityProvenanceClassifier, SceneIdentityValidator, PersistentSceneIdentityIndex, and related behaviors like repairing duplicates, runtime validation, and resolution precedence between editor-authored and runtime/network identities.

NetworkingFile Access
using Hexagon.V2.Application;
using Hexagon.V2.Domain;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class SceneIdentityValidatorTests
{
	[TestMethod]
	public void ProvenanceClassifier_TrustsOnlyCapturedNonNetworkComponents()
	{
		var authored = Guid.NewGuid();
		var laterRuntime = Guid.NewGuid();
		var classifier = new SceneIdentityProvenanceClassifier();

		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( authored, hasActiveNetworkRoot: false ) );

		classifier.CaptureAuthoredSnapshot( new[] { authored } );

		Assert.IsTrue( classifier.HasAuthoredSnapshot );
		Assert.AreEqual( 1, classifier.AuthoredComponentCount );
		Assert.AreEqual(
			SceneIdentityProvenance.EditorAuthored,
			classifier.Classify( authored, hasActiveNetworkRoot: false ) );
		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( authored, hasActiveNetworkRoot: true ) );
		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( laterRuntime, hasActiveNetworkRoot: false ) );
	}

	[TestMethod]
	public void ProvenanceClassifier_NewSceneSnapshotReplacesPriorAuthority()
	{
		var firstScene = Guid.NewGuid();
		var secondScene = Guid.NewGuid();
		var classifier = new SceneIdentityProvenanceClassifier();
		classifier.CaptureAuthoredSnapshot( new[] { firstScene } );

		classifier.CaptureAuthoredSnapshot( new[] { secondScene } );

		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( firstScene, hasActiveNetworkRoot: false ) );
		Assert.AreEqual(
			SceneIdentityProvenance.EditorAuthored,
			classifier.Classify( secondScene, hasActiveNetworkRoot: false ) );
	}

	[TestMethod]
	public void EditorRepair_FillsMissingAndRepairsOnlyLaterDuplicate()
	{
		var duplicate = new SceneEntityId( Guid.Parse( "11111111-1111-1111-1111-111111111111" ) );
		var generated = new Queue<SceneEntityId>( new[]
		{
			new SceneEntityId( Guid.Parse( "22222222-2222-2222-2222-222222222222" ) ),
			new SceneEntityId( Guid.Parse( "33333333-3333-3333-3333-333333333333" ) )
		} );
		var result = SceneIdentityValidator.RepairForEditor( new[]
		{
			new SceneIdentityCandidate( "a", duplicate ),
			new SceneIdentityCandidate( "b", duplicate ),
			new SceneIdentityCandidate( "c", null )
		}, () => generated.Dequeue() );

		Assert.IsFalse( result[0].Repaired );
		Assert.IsTrue( result[1].Repaired );
		Assert.IsTrue( result[2].Repaired );
		Assert.AreNotEqual( result[0].EffectiveId, result[1].EffectiveId );
	}

	[TestMethod]
	public void RuntimeValidation_DisablesEveryAmbiguousOrBlankEntity()
	{
		var duplicate = new SceneEntityId( Guid.Parse( "11111111-1111-1111-1111-111111111111" ) );
		var result = SceneIdentityValidator.ValidateRuntime( new[]
		{
			new SceneIdentityCandidate( "a", duplicate ),
			new SceneIdentityCandidate( "b", duplicate ),
			new SceneIdentityCandidate( "c", null )
		} );

		Assert.IsTrue( result.All( value => !value.Enabled && value.FatalDiagnostic is not null ) );
	}

	[TestMethod]
	public void PersistentIndexEnumeratesTenThousandCandidatesOnceAndProvidesConstantTimeLookups()
	{
		var ids = Enumerable.Range( 0, 10_000 ).Select( _ => SceneEntityId.New() ).ToArray();
		var enumerated = 0;
		IEnumerable<SceneIdentityCandidate> Candidates()
		{
			for ( var index = 0; index < ids.Length; index++ )
			{
				enumerated++;
				yield return new SceneIdentityCandidate( $"entity/{index}", ids[index] );
			}
		}

		var index = PersistentSceneIdentityIndex.Build( Candidates() );

		Assert.AreEqual( 10_000, enumerated );
		Assert.AreEqual( 10_000, index.Count );
		for ( var candidate = 0; candidate < ids.Length; candidate++ )
		{
			Assert.IsTrue( index.TryResolveId( ids[candidate], out var byId ) );
			Assert.IsTrue( index.TryResolvePath( $"entity/{candidate}", out var byPath ) );
			Assert.AreSame( byId, byPath );
		}
	}

	[TestMethod]
	public void RuntimeNetworkDuplicateCannotPoisonEditorAuthoredIdentity()
	{
		var duplicate = SceneEntityId.New();
		var initial = PersistentSceneIdentityIndex.Build( new[]
		{
			new SceneIdentityCandidate( "first", duplicate )
		} );
		Assert.IsTrue( initial.TryResolveId( duplicate, out _ ) );

		var rebuilt = PersistentSceneIdentityIndex.Build( new[]
		{
			new SceneIdentityCandidate( "first", duplicate ),
			new SceneIdentityCandidate(
				"first", duplicate, SceneIdentityProvenance.RuntimeOrNetwork )
		} );

		Assert.IsTrue( rebuilt.TryResolveId( duplicate, out var resolved ) );
		Assert.AreEqual( "first", resolved.StablePath );
		Assert.IsTrue( rebuilt.Resolutions[0].Enabled );
		Assert.IsFalse( rebuilt.Resolutions[1].Enabled );
		Assert.AreEqual( SceneIdentityProvenance.RuntimeOrNetwork, rebuilt.Resolutions[1].Provenance );
		Assert.IsNull( rebuilt.Resolutions[1].EffectiveId );
		StringAssert.Contains( rebuilt.Resolutions[1].FatalDiagnostic, "runtime/network root" );
	}

	[TestMethod]
	public void TwoEditorAuthoredDuplicatesStillFailClosed()
	{
		var duplicate = SceneEntityId.New();
		var rebuilt = PersistentSceneIdentityIndex.Build( new[]
		{
			new SceneIdentityCandidate( "first", duplicate ),
			new SceneIdentityCandidate( "second", duplicate )
		} );

		Assert.IsFalse( rebuilt.TryResolveId( duplicate, out _ ) );
		Assert.IsTrue( rebuilt.Resolutions.All( resolution => !resolution.Enabled ) );
	}
}