| File: TestServices\RpcTestClient.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.Text; using System.Text.Json.Nodes; using Aspire.Hosting.Native.Rpc; using Aspire.Hosting.Native.Api; using Aspire.Hosting.Native.Server; namespace Aspire.Hosting.Native.Core.Tests.TestServices; /// <summary>Exercises the actual JSON-RPC envelope without a transport or service-provider fake.</summary> internal sealed class RpcTestClient : IDisposable { public const string Token = "native-rpc-test-token-not-a-real-secret"; private int _requestId; public RpcTestClient(bool authenticate) : this(new NativeRpcConnection(Token), authenticate) { } public RpcTestClient(NativeApplicationServer server) : this(new NativeRpcConnection(Token, server), authenticate: true) { } public RpcTestClient(NativeApplicationServer server, NativeServerOptions options) : this(new NativeRpcConnection(Token, server, languages: null, options), authenticate: true) { } public RpcTestClient(NativeApplicationServer server, NativeLanguageCatalog languages) : this(new NativeRpcConnection(Token, server, languages), authenticate: true) { } private RpcTestClient(NativeRpcConnection connection, bool authenticate) { Connection = connection; if (authenticate && Call("authenticate", new JsonArray(Token))["result"]?.GetValue<bool>() != true) { throw new InvalidOperationException("The test RPC connection could not authenticate."); } } public NativeRpcConnection Connection { get; } public JsonObject Call(string method, JsonNode? parameters) => CallAsync(method, parameters).GetAwaiter().GetResult(); public async Task<JsonObject> CallAsync(string method, JsonNode? parameters) { var request = new JsonObject { ["jsonrpc"] = "2.0", ["id"] = Interlocked.Increment(ref _requestId), ["method"] = method, ["params"] = parameters }; var response = await Connection.ProcessAsync(Encoding.UTF8.GetBytes(request.ToJsonString())).ConfigureAwait(false) ?? throw new InvalidOperationException("A request did not receive a response."); return JsonNode.Parse(response)?.AsObject() ?? throw new InvalidOperationException("The RPC response is not a JSON object."); } public JsonObject Invoke(string capability, JsonObject arguments) => Call("invokeCapability", new JsonArray(CapabilityId(capability), arguments)); public Task<JsonObject> InvokeAsync(string capability, JsonObject arguments) => CallAsync("invokeCapability", new JsonArray(CapabilityId(capability), arguments)); // ATS static exports use the assembly name; instance exports use the type namespace. private static string CapabilityId(string capability) => (capability == "createSession" ? "Aspire.Hosting.Native.Server/" : "Aspire.Hosting.Native.Api/") + capability; public JsonNode CreateSession() => Result(Invoke("createSession", [])); public JsonNode StartGeneration(JsonNode session) => Result(Invoke("startGeneration", Arguments(("context", session)))); public JsonNode AddResource(JsonNode composition, string name) => Result(Invoke("addResource", Arguments(("context", composition), ("name", JsonValue.Create(name)), ("typeId", JsonValue.Create("example/Resource"))))); public static JsonObject Arguments(params (string Name, JsonNode? Value)[] values) => new(values.Select(value => KeyValuePair.Create(value.Name, value.Value?.DeepClone()))); public static JsonNode Result(JsonObject response) => response["result"]?.DeepClone() ?? throw new InvalidOperationException("The RPC request did not return a non-null result."); public void Dispose() => Connection.Dispose(); }