File: NativeCliControlTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Native.Core.Tests\Aspire.Hosting.Native.Core.Tests.csproj (Aspire.Hosting.Native.Core.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.IO.Hashing;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Aspire.Hosting.Native.Api;
using Aspire.Hosting.Native.Core.Tests.TestServices;
using Aspire.Hosting.Native.Rpc;
using static Aspire.Hosting.Native.Core.Tests.TestServices.RpcTestClient;
 
namespace Aspire.Hosting.Native.Core.Tests;
 
public class NativeCliControlTests
{
    [Fact]
    public async Task CliReceivesOfflineSdkAndLanguageSpecWithoutChangingAtsDiscovery()
    {
        var server = new NativeApplicationServer();
        using var client = new RpcTestClient(server, NativeLanguageCatalog.LoadEmbedded());
        var spec = Result(client.Call("getRuntimeSpec", new JsonArray("typescript/nodejs")));
        var sdk = Result(client.Call("generateCode", new JsonArray("TypeScript", null))).AsObject();
        Assert.All(sdk, file => Assert.False(string.IsNullOrWhiteSpace(file.Value!.GetValue<string>())));
        var assembly = typeof(NativeLanguageCatalog).Assembly;
        foreach (var file in sdk.Where(file => file.Key != "apphost-server.json"))
        {
            using var stream = assembly.GetManifestResourceStream("NativeSdk." + file.Key);
            Assert.NotNull(stream);
            using var reader = new StreamReader(stream);
            Assert.Equal(await reader.ReadToEndAsync(TestContext.Current.CancellationToken), file.Value!.GetValue<string>());
        }
        var identity = JsonNode.Parse(sdk["apphost-server.json"]!.GetValue<string>())!;
        using var contract = assembly.GetManifestResourceStream("Aspire.Hosting.Native.Rpc.contract.json");
        Assert.NotNull(contract);
        var contractHash = new XxHash3();
        await contractHash.AppendAsync(contract, TestContext.Current.CancellationToken);
        Assert.Equal(Convert.ToHexString(contractHash.GetCurrentHash()), identity["contractId"]!.GetValue<string>());
        Assert.Equal(assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion,
            identity["version"]!.GetValue<string>());
        // The fingerprint identifies this build's embedded bytes, not a platform-independent API shape.
        identity["contractId"] = "contract-id";
        identity["version"] = "server-version";
        // Normalize embedded file line endings before JSON escapes them for the snapshot.
        foreach (var (name, content) in spec["migrationFiles"]!.AsObject().ToArray())
        {
            spec["migrationFiles"]![name] = content!.GetValue<string>().ReplaceLineEndings("\n");
        }
        await Verify(JsonSerializer.Serialize(new
        {
            runtimeSpec = spec,
            sdkFiles = sdk.Select(file => file.Key).Order(StringComparer.Ordinal),
            identity
        }, new JsonSerializerOptions { WriteIndented = true }), "json").UseDirectory("Snapshots");
        Assert.NotEmpty(Result(client.Call("getCapabilities", null))["Capabilities"]!.AsArray());
        Assert.Empty(Result(client.Call("getAppHostServerConfiguration", null)).AsArray());
        Assert.Equal("INVALID_ARGUMENT", Error(client.Call("getAppHostServerConfiguration", new JsonArray("unexpected"))));
        Assert.Equal("INVALID_ARGUMENT", Error(client.Call("generateCode", new JsonArray("TypeScript", "Aspire.Hosting"))));
        Assert.Equal("NOT_SUPPORTED", Error(client.Call("getRuntimeSpec", new JsonArray("python"))));
    }
 
    [Theory]
    [InlineData("getRuntimeState")]
    [InlineData("requestStop")]
    [InlineData("notifyCliReady")]
    public void NativeSpecificControlMethodsAreNotPartOfTheBootstrapContract(string method)
    {
        var server = new NativeApplicationServer();
        using var client = new RpcTestClient(server, NativeLanguageCatalog.LoadEmbedded());
        Assert.Equal("METHOD_NOT_FOUND", Error(client.Call(method, null)));
    }
 
    [Fact]
    public void StreamJsonRpcTraceContextDoesNotInvalidateAuthenticationEnvelope()
    {
        using var capture = new DiagnosticCapture(sampleActivities: false);
        using var connection = new NativeRpcConnection(Token);
        var request = new JsonObject
        {
            ["jsonrpc"] = "2.0", ["id"] = 1, ["method"] = "authenticate", ["params"] = new JsonArray(Token),
            ["traceparent"] = capture.Root.Id,
            ["tracestate"] = "vendor=value"
        };
        var response = connection.Process(Encoding.UTF8.GetBytes(request.ToJsonString()));
        Assert.NotNull(response);
        Assert.True(JsonNode.Parse(response)!["result"]!.GetValue<bool>());
        Assert.All(capture.Events, entry => Assert.Equal(capture.Root.TraceId.ToString(), entry.TraceId));
        Assert.Equal(2, capture.Events.Count);
    }
 
    private static string Error(JsonObject response) => response["error"]!["data"]!["code"]!.GetValue<string>();
}