File: HandleRegistryTests.cs
Web Access
Project: src\tests\Aspire.Hosting.RemoteHost.Tests\Aspire.Hosting.RemoteHost.Tests.csproj (Aspire.Hosting.RemoteHost.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.Json.Nodes;
using Aspire.Hosting.RemoteHost.Ats;
using Xunit;
 
namespace Aspire.Hosting.RemoteHost.Tests;
 
public class HandleRegistryTests
{
    [Fact]
    public void Register_ReturnsNumericHandleId()
    {
        var registry = new HandleRegistry();
        var obj = new object();
 
        var handleId = registry.Register(obj, "aspire/TestType");
 
        // Handle ID is now just a numeric instance ID
        Assert.True(long.TryParse(handleId, out var instanceId));
        Assert.True(instanceId > 0);
        // Type ID is retrieved separately
        Assert.Equal("aspire/TestType", registry.GetTypeId(handleId));
    }
 
    [Fact]
    public void Register_IncrementsInstanceIdForEachRegistration()
    {
        var registry = new HandleRegistry();
 
        var handle1 = registry.Register(new object(), "aspire/Test");
        var handle2 = registry.Register(new object(), "aspire/Test");
        var handle3 = registry.Register(new object(), "aspire/Test");
 
        // Parse as simple integers
        var id1 = long.Parse(handle1);
        var id2 = long.Parse(handle2);
        var id3 = long.Parse(handle3);
 
        Assert.Equal(id1 + 1, id2);
        Assert.Equal(id2 + 1, id3);
    }
 
    [Fact]
    public void TryGet_ReturnsTrueAndObjectForRegisteredHandle()
    {
        var registry = new HandleRegistry();
        var obj = new TestObject { Value = 42 };
        var handleId = registry.Register(obj, "aspire/TestObject");
 
        var found = registry.TryGet(handleId, out var retrieved, out var typeId);
 
        Assert.True(found);
        Assert.Same(obj, retrieved);
        Assert.Equal("aspire/TestObject", typeId);
    }
 
    [Fact]
    public void TryGet_ReturnsFalseForUnregisteredHandle()
    {
        var registry = new HandleRegistry();
 
        var found = registry.TryGet("999", out var obj, out var typeId);
 
        Assert.False(found);
        Assert.Null(obj);
        Assert.Null(typeId);
    }
 
    [Fact]
    public void GetObject_ReturnsObjectForRegisteredHandle()
    {
        var registry = new HandleRegistry();
        var obj = new TestObject { Value = 42 };
        var handleId = registry.Register(obj, "aspire/TestObject");
 
        var retrieved = registry.GetObject(handleId);
 
        Assert.Same(obj, retrieved);
    }
 
    [Fact]
    public void GetObject_ThrowsForUnregisteredHandle()
    {
        var registry = new HandleRegistry();
 
        Assert.Throws<InvalidOperationException>(() => registry.GetObject("999"));
    }
 
    [Fact]
    public void GetObjectGeneric_ReturnsTypedObjectForRegisteredHandle()
    {
        var registry = new HandleRegistry();
        var obj = new TestObject { Value = 42 };
        var handleId = registry.Register(obj, "aspire/TestObject");
 
        var retrieved = registry.GetObject<TestObject>(handleId);
 
        Assert.Same(obj, retrieved);
        Assert.Equal(42, retrieved.Value);
    }
 
    [Fact]
    public void GetObjectGeneric_ThrowsForWrongType()
    {
        var registry = new HandleRegistry();
        var obj = new TestObject { Value = 42 };
        var handleId = registry.Register(obj, "aspire/TestObject");
 
        Assert.Throws<InvalidOperationException>(() => registry.GetObject<string>(handleId));
    }
 
    [Fact]
    public void GetTypeId_ReturnsTypeIdForRegisteredHandle()
    {
        var registry = new HandleRegistry();
        var handleId = registry.Register(new object(), "aspire.redis/RedisBuilder");
 
        var typeId = registry.GetTypeId(handleId);
 
        Assert.Equal("aspire.redis/RedisBuilder", typeId);
    }
 
    [Fact]
    public void Contains_ReturnsTrueForRegisteredHandle()
    {
        var registry = new HandleRegistry();
        var handleId = registry.Register(new object(), "aspire/Test");
 
        Assert.True(registry.Contains(handleId));
    }
 
    [Fact]
    public void Contains_ReturnsFalseForUnregisteredHandle()
    {
        var registry = new HandleRegistry();
 
        Assert.False(registry.Contains("999"));
    }
 
    [Fact]
    public void Unregister_RemovesHandle()
    {
        var registry = new HandleRegistry();
        var handleId = registry.Register(new object(), "aspire/Test");
 
        var removed = registry.Unregister(handleId);
 
        Assert.True(removed);
        Assert.False(registry.Contains(handleId));
    }
 
    [Fact]
    public void Unregister_ReturnsFalseForUnregisteredHandle()
    {
        var registry = new HandleRegistry();
 
        var removed = registry.Unregister("999");
 
        Assert.False(removed);
    }
 
    [Fact]
    public void Marshal_RegistersObjectAndReturnsJsonHandle()
    {
        var registry = new HandleRegistry();
        var obj = new TestObject { Value = 42 };
 
        var json = registry.Marshal(obj, "aspire/TestObject");
 
        Assert.NotNull(json["$handle"]);
        Assert.NotNull(json["$type"]);
        Assert.Equal("aspire/TestObject", json["$type"]!.GetValue<string>());
 
        var handleId = json["$handle"]!.GetValue<string>();
        Assert.True(registry.Contains(handleId));
    }
 
    [Fact]
    public void Count_ReturnsNumberOfRegisteredHandles()
    {
        var registry = new HandleRegistry();
 
        Assert.Equal(0, registry.Count);
 
        registry.Register(new object(), "aspire/Test1");
        Assert.Equal(1, registry.Count);
 
        registry.Register(new object(), "aspire/Test2");
        Assert.Equal(2, registry.Count);
    }
 
    [Fact]
    public async Task DisposeAsync_DisposesDisposableObjects()
    {
        var registry = new HandleRegistry();
        var disposable = new TestDisposable();
        registry.Register(disposable, "aspire/Disposable");
 
        await registry.DisposeAsync();
 
        Assert.True(disposable.Disposed);
        Assert.Equal(0, registry.Count);
    }
 
    [Fact]
    public async Task DisposeAsync_DisposesAsyncDisposableObjects()
    {
        var registry = new HandleRegistry();
        var asyncDisposable = new TestAsyncDisposable();
        registry.Register(asyncDisposable, "aspire/AsyncDisposable");
 
        await registry.DisposeAsync();
 
        Assert.True(asyncDisposable.Disposed);
    }
 
    private sealed class TestObject
    {
        public int Value { get; set; }
    }
 
    private sealed class TestDisposable : IDisposable
    {
        public bool Disposed { get; private set; }
        public void Dispose() => Disposed = true;
    }
 
    private sealed class TestAsyncDisposable : IAsyncDisposable
    {
        public bool Disposed { get; private set; }
        public ValueTask DisposeAsync()
        {
            Disposed = true;
            return ValueTask.CompletedTask;
        }
    }
}
 
public class HandleRefTests
{
    [Fact]
    public void FromJsonNode_ReturnsHandleRefForValidHandle()
    {
        var json = new JsonObject { ["$handle"] = "42" };
 
        var result = HandleRef.FromJsonNode(json);
 
        Assert.NotNull(result);
        Assert.Equal("42", result.HandleId);
    }
 
    [Fact]
    public void FromJsonNode_ReturnsNullForNullNode()
    {
        var result = HandleRef.FromJsonNode(null);
 
        Assert.Null(result);
    }
 
    [Fact]
    public void FromJsonNode_ReturnsNullForNonObjectNode()
    {
        var json = JsonValue.Create("not an object");
 
        var result = HandleRef.FromJsonNode(json);
 
        Assert.Null(result);
    }
 
    [Fact]
    public void FromJsonNode_ReturnsNullForObjectWithoutHandle()
    {
        var json = new JsonObject { ["name"] = "test", ["value"] = 42 };
 
        var result = HandleRef.FromJsonNode(json);
 
        Assert.Null(result);
    }
 
    [Fact]
    public void FromJsonNode_ThrowsForNonStringHandle()
    {
        var json = new JsonObject { ["$handle"] = 42 };
 
        // The implementation throws when trying to get a non-string as string
        Assert.Throws<InvalidOperationException>(() => HandleRef.FromJsonNode(json));
    }
 
    [Fact]
    public void FromJsonNode_WorksWithAdditionalProperties()
    {
        var json = new JsonObject
        {
            ["$handle"] = "1",
            ["$type"] = "aspire/Test",
            ["extra"] = "ignored"
        };
 
        var result = HandleRef.FromJsonNode(json);
 
        Assert.NotNull(result);
        Assert.Equal("1", result.HandleId);
    }
 
    [Fact]
    public void IsHandleRef_ReturnsTrueForHandleObject()
    {
        var json = new JsonObject { ["$handle"] = "1" };
 
        Assert.True(HandleRef.IsHandleRef(json));
    }
 
    [Fact]
    public void IsHandleRef_ReturnsFalseForNull()
    {
        Assert.False(HandleRef.IsHandleRef(null));
    }
 
    [Fact]
    public void IsHandleRef_ReturnsFalseForNonObject()
    {
        var json = JsonValue.Create("string");
 
        Assert.False(HandleRef.IsHandleRef(json));
    }
 
    [Fact]
    public void IsHandleRef_ReturnsFalseForObjectWithoutHandle()
    {
        var json = new JsonObject { ["name"] = "test" };
 
        Assert.False(HandleRef.IsHandleRef(json));
    }
 
    [Fact]
    public void IsHandleRef_ReturnsTrueEvenWithNonStringHandle()
    {
        // IsHandleRef only checks for key presence, not value type
        var json = new JsonObject { ["$handle"] = 42 };
 
        Assert.True(HandleRef.IsHandleRef(json));
    }
}