File: ReferenceExpressionRefTests.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.ApplicationModel;
using Aspire.Hosting.Ats;
using Aspire.Hosting.RemoteHost.Ats;
using Xunit;
 
namespace Aspire.Hosting.RemoteHost.Tests;
 
public class ReferenceExpressionRefTests
{
    [Fact]
    public void FromJsonNode_WithValidExpression_ReturnsRef()
    {
        // Arrange
        var json = new JsonObject
        {
            ["$expr"] = new JsonObject
            {
                ["format"] = "redis://localhost:6379"
            }
        };
 
        // Act
        var exprRef = ReferenceExpressionRef.FromJsonNode(json);
 
        // Assert
        Assert.NotNull(exprRef);
        Assert.Equal("redis://localhost:6379", exprRef.Format);
        Assert.Null(exprRef.ValueProviders);
    }
 
    [Fact]
    public void FromJsonNode_WithValueProviders_ReturnsRef()
    {
        // Arrange
        var json = new JsonObject
        {
            ["$expr"] = new JsonObject
            {
                ["format"] = "redis://{0}:{1}",
                ["valueProviders"] = new JsonArray
                {
                    new JsonObject { ["$handle"] = "aspire/EndpointReference:1" },
                    new JsonObject { ["$handle"] = "aspire/EndpointReference:2" }
                }
            }
        };
 
        // Act
        var exprRef = ReferenceExpressionRef.FromJsonNode(json);
 
        // Assert
        Assert.NotNull(exprRef);
        Assert.Equal("redis://{0}:{1}", exprRef.Format);
        Assert.NotNull(exprRef.ValueProviders);
        Assert.Equal(2, exprRef.ValueProviders.Length);
    }
 
    [Fact]
    public void FromJsonNode_WithoutExpr_ReturnsNull()
    {
        // Arrange
        var json = new JsonObject
        {
            ["format"] = "redis://localhost:6379"
        };
 
        // Act
        var exprRef = ReferenceExpressionRef.FromJsonNode(json);
 
        // Assert
        Assert.Null(exprRef);
    }
 
    [Fact]
    public void FromJsonNode_WithNullNode_ReturnsNull()
    {
        // Act
        var exprRef = ReferenceExpressionRef.FromJsonNode(null);
 
        // Assert
        Assert.Null(exprRef);
    }
 
    [Fact]
    public void IsReferenceExpressionRef_WithExprProperty_ReturnsTrue()
    {
        // Arrange
        var json = new JsonObject
        {
            ["$expr"] = new JsonObject { ["format"] = "test" }
        };
 
        // Act & Assert
        Assert.True(ReferenceExpressionRef.IsReferenceExpressionRef(json));
    }
 
    [Fact]
    public void IsReferenceExpressionRef_WithoutExprProperty_ReturnsFalse()
    {
        // Arrange
        var json = new JsonObject { ["format"] = "test" };
 
        // Act & Assert
        Assert.False(ReferenceExpressionRef.IsReferenceExpressionRef(json));
    }
 
    [Fact]
    public void ToReferenceExpression_WithLiteralOnly_CreatesExpression()
    {
        // Arrange
        var exprRef = new ReferenceExpressionRef_Accessor("redis://localhost:6379", null);
        var handles = new HandleRegistry();
 
        // Act
        var expr = exprRef.ToReferenceExpression(handles, "test/cap", "param");
 
        // Assert
        Assert.NotNull(expr);
        Assert.Equal("redis://localhost:6379", expr.ValueExpression);
    }
 
    [Fact]
    public async Task ToReferenceExpression_WithValueProviders_CreatesExpression()
    {
        // Arrange
        var handles = new HandleRegistry();
        var endpoint1 = new TestValueProvider("localhost", "{host}");
        var endpoint2 = new TestValueProvider("6379", "{port}");
        var handle1 = handles.Register(endpoint1, "aspire/EndpointReference");
        var handle2 = handles.Register(endpoint2, "aspire/EndpointReference");
 
        var valueProviders = new JsonNode?[]
        {
            new JsonObject { ["$handle"] = handle1 },
            new JsonObject { ["$handle"] = handle2 }
        };
        var exprRef = new ReferenceExpressionRef_Accessor("redis://{0}:{1}", valueProviders);
 
        // Act
        var expr = exprRef.ToReferenceExpression(handles, "test/cap", "param");
 
        // Assert
        Assert.NotNull(expr);
        Assert.Equal("redis://{host}:{port}", expr.ValueExpression);
        Assert.Equal("redis://localhost:6379", await expr.GetValueAsync(default));
    }
 
    [Fact]
    public void ToReferenceExpression_WithMissingHandle_ThrowsCapabilityException()
    {
        // Arrange
        var handles = new HandleRegistry();
        var valueProviders = new JsonNode?[]
        {
            new JsonObject { ["$handle"] = "aspire/NonExistent:999" }
        };
        var exprRef = new ReferenceExpressionRef_Accessor("redis://{0}", valueProviders);
 
        // Act & Assert
        var ex = Assert.Throws<CapabilityException>(() =>
            exprRef.ToReferenceExpression(handles, "test/cap", "param"));
        Assert.Equal(AtsErrorCodes.HandleNotFound, ex.Error.Code);
    }
 
    [Fact]
    public void ToReferenceExpression_WithStringLiteral_CreatesExpression()
    {
        // Arrange
        var handles = new HandleRegistry();
        var valueProviders = new JsonNode?[]
        {
            JsonValue.Create("6379")
        };
        var exprRef = new ReferenceExpressionRef_Accessor("redis://localhost:{0}", valueProviders);
 
        // Act
        var expr = exprRef.ToReferenceExpression(handles, "test/cap", "param");
 
        // Assert
        Assert.NotNull(expr);
        Assert.Equal("redis://localhost:6379", expr.ValueExpression);
    }
 
    [Fact]
    public async Task ToReferenceExpression_WithMixedValueProviders_CreatesExpression()
    {
        // Arrange
        var handles = new HandleRegistry();
        var endpoint = new TestValueProvider("myhost", "{host}");
        var handle = handles.Register(endpoint, "aspire/EndpointReference");
 
        var valueProviders = new JsonNode?[]
        {
            new JsonObject { ["$handle"] = handle },
            JsonValue.Create("6379")
        };
        var exprRef = new ReferenceExpressionRef_Accessor("redis://{0}:{1}", valueProviders);
 
        // Act
        var expr = exprRef.ToReferenceExpression(handles, "test/cap", "param");
 
        // Assert
        Assert.NotNull(expr);
        Assert.Equal("redis://{host}:6379", expr.ValueExpression);
        Assert.Equal("redis://myhost:6379", await expr.GetValueAsync(default));
    }
 
    [Fact]
    public void ToReferenceExpression_WithInvalidValueProvider_ThrowsCapabilityException()
    {
        // Arrange
        var handles = new HandleRegistry();
        var valueProviders = new JsonNode?[]
        {
            new JsonObject { ["invalid"] = "not-a-handle" }  // Invalid format (not a handle or string)
        };
        var exprRef = new ReferenceExpressionRef_Accessor("redis://{0}", valueProviders);
 
        // Act & Assert
        var ex = Assert.Throws<CapabilityException>(() =>
            exprRef.ToReferenceExpression(handles, "test/cap", "param"));
        Assert.Equal(AtsErrorCodes.InvalidArgument, ex.Error.Code);
    }
 
    /// <summary>
    /// Test accessor for creating ReferenceExpressionRef instances directly.
    /// </summary>
    private sealed class ReferenceExpressionRef_Accessor(string format, JsonNode?[]? valueProviders)
    {
        public ReferenceExpression ToReferenceExpression(HandleRegistry handles, string capabilityId, string paramName)
        {
            // Use reflection or parse from JSON to create the ref
            var json = new JsonObject
            {
                ["$expr"] = new JsonObject
                {
                    ["format"] = format
                }
            };
 
            if (valueProviders != null && valueProviders.Length > 0)
            {
                var providersArray = new JsonArray();
                foreach (var p in valueProviders)
                {
                    providersArray.Add(p?.DeepClone());
                }
                ((JsonObject)json["$expr"]!)["valueProviders"] = providersArray;
            }
 
            var exprRef = ReferenceExpressionRef.FromJsonNode(json);
            return exprRef!.ToReferenceExpression(handles, capabilityId, paramName);
        }
    }
 
    /// <summary>
    /// Test value provider for unit tests.
    /// </summary>
    private sealed class TestValueProvider(string value, string expression) : IValueProvider, IManifestExpressionProvider
    {
        public string ValueExpression => expression;
 
        public ValueTask<string?> GetValueAsync(CancellationToken cancellationToken)
        {
            return new ValueTask<string?>(value);
        }
    }
}