tests/Application/AsyncOperationRegistryTests.cs

Unit tests for AsyncOperationRegistry. Exercises starting operations (ValueTask and Task), draining behavior including pruning of completed operations, aggregation of synchronous/asynchronous/cancellation failures, and idempotence of StopAdmission/DrainAsync plus rejection of new work after stop.

#nullable enable

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Hexagon.V2.Application;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class AsyncOperationRegistryTests
{
	[TestMethod]
	public async Task CompletedOperationsSelfPruneBeforeDrain()
	{
		var registry = new AsyncOperationRegistry();

		Assert.IsTrue( registry.TryStart( "value", static () => ValueTask.CompletedTask ) );
		Assert.IsTrue( registry.TryStartTask( "task", static () => Task.CompletedTask ) );
		Assert.AreEqual( 0, registry.ActiveOperationCount );

		var drained = await registry.DrainAsync();

		Assert.IsTrue( drained.Succeeded );
		Assert.AreEqual( 2, drained.AcceptedOperationCount );
		Assert.AreEqual( 2, drained.CompletedOperationCount );
		Assert.IsEmpty( drained.Failures );
	}

	[TestMethod]
	public async Task DrainAggregatesSynchronousAsynchronousAndCancellationFailures()
	{
		var registry = new AsyncOperationRegistry();
		var cancellation = new CancellationToken( canceled: true );

		Assert.IsTrue( registry.TryStartTask( "synchronous", static () =>
			throw new InvalidOperationException( "sync" ) ) );
		Assert.IsTrue( registry.TryStartTask( "asynchronous", static () =>
			Task.FromException( new ArgumentException( "async" ) ) ) );
		Assert.IsTrue( registry.TryStartTask( "canceled", () => Task.FromCanceled( cancellation ) ) );

		var drained = await registry.DrainAsync();

		Assert.IsFalse( drained.Succeeded );
		Assert.AreEqual( 3, drained.AcceptedOperationCount );
		Assert.AreEqual( 3, drained.CompletedOperationCount );
		Assert.HasCount( 3, drained.Failures );
		CollectionAssert.AreEquivalent(
			new[] { "synchronous", "asynchronous", "canceled" },
			drained.Failures.Select( value => value.Name ).ToArray() );
		Assert.IsTrue( drained.Failures.Single( value => value.Name == "canceled" ).WasCanceled );
		Assert.AreEqual( 0, registry.ActiveOperationCount );
	}

	[TestMethod]
	public async Task StopAndDrainAreIdempotentAndRejectNewWorkWhileWaiting()
	{
		var registry = new AsyncOperationRegistry();
		var release = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously );
		Assert.IsTrue( registry.TryStartTask( "pending", () => release.Task ) );

		Assert.IsTrue( registry.StopAdmission() );
		Assert.IsFalse( registry.StopAdmission() );
		var first = registry.DrainAsync().AsTask();
		var second = registry.DrainAsync().AsTask();
		Assert.AreSame( first, second );
		Assert.IsFalse( registry.IsAccepting );
		Assert.IsFalse( registry.TryStartTask( "late", static () => Task.CompletedTask ) );
		Assert.IsFalse( first.IsCompleted );

		release.SetResult();
		var drained = await first;

		Assert.IsTrue( drained.Succeeded );
		Assert.AreEqual( 1, drained.AcceptedOperationCount );
		Assert.AreEqual( 1, drained.CompletedOperationCount );
		Assert.AreEqual( 0, registry.ActiveOperationCount );
		Assert.AreSame( drained, await second );
	}
}