diff --git a/properties/IsExternalInit.cs b/properties/IsExternalInit.cs new file mode 100644 index 00000000..a06c2869 --- /dev/null +++ b/properties/IsExternalInit.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } + + // This is a polyfill for init only properties in netcoreapp3.1 +} diff --git a/properties/dapr_managed_netcore.props b/properties/dapr_managed_netcore.props index 294109b1..59bb68c9 100644 --- a/properties/dapr_managed_netcore.props +++ b/properties/dapr_managed_netcore.props @@ -3,11 +3,16 @@ Debug + 9.0 true 4 false + + + + true diff --git a/src/Dapr.Actors/Client/ActorProxyFactory.cs b/src/Dapr.Actors/Client/ActorProxyFactory.cs index e59632ff..4b4f50f1 100644 --- a/src/Dapr.Actors/Client/ActorProxyFactory.cs +++ b/src/Dapr.Actors/Client/ActorProxyFactory.cs @@ -16,7 +16,7 @@ namespace Dapr.Actors.Client public class ActorProxyFactory : IActorProxyFactory { private ActorProxyOptions defaultOptions; - private readonly HttpClientHandler handler; + private readonly HttpMessageHandler handler; /// public ActorProxyOptions DefaultOptions @@ -32,7 +32,16 @@ namespace Dapr.Actors.Client /// /// Initializes a new instance of the class. /// - public ActorProxyFactory(ActorProxyOptions options = null, HttpClientHandler handler = null) + [Obsolete("Use the constructor that accepts HttpMessageHandler. This will be removed in the future.")] + public ActorProxyFactory(ActorProxyOptions options, HttpClientHandler handler) + : this(options, (HttpMessageHandler)handler) + { + } + + /// + /// Initializes a new instance of the class. + /// + public ActorProxyFactory(ActorProxyOptions options = null, HttpMessageHandler handler = null) { this.defaultOptions = options ?? new ActorProxyOptions(); this.handler = handler; diff --git a/src/Dapr.Actors/DaprHttpInteractor.cs b/src/Dapr.Actors/DaprHttpInteractor.cs index f59d452f..d4c5f5df 100644 --- a/src/Dapr.Actors/DaprHttpInteractor.cs +++ b/src/Dapr.Actors/DaprHttpInteractor.cs @@ -28,19 +28,20 @@ namespace Dapr.Actors private readonly JsonSerializerOptions jsonSerializerOptions = JsonSerializerDefaults.Web; private const string DaprEndpoint = Constants.DaprDefaultEndpoint; private readonly string daprPort; - private static HttpClientHandler innerHandler = new HttpClientHandler(); - private HttpClient httpClient = null; - private bool disposed = false; + private readonly static HttpMessageHandler defaultHandler = new HttpClientHandler(); + private readonly HttpMessageHandler handler; + private HttpClient httpClient; + private bool disposed; private string daprApiToken; public DaprHttpInteractor( - HttpClientHandler clientHandler = null, + HttpMessageHandler clientHandler = null, string apiToken = null) { // Get Dapr port from Environment Variable if it has been overridden. this.daprPort = Environment.GetEnvironmentVariable("DAPR_HTTP_PORT") ?? Constants.DaprDefaultPort; - innerHandler = clientHandler ?? new HttpClientHandler(); + this.handler = clientHandler ?? defaultHandler; this.daprApiToken = apiToken; this.httpClient = this.CreateHttpClient(); } @@ -433,7 +434,7 @@ namespace Dapr.Actors private HttpClient CreateHttpClient() { - return new HttpClient(innerHandler, false); + return new HttpClient(this.handler, false); } private void AddDaprApiTokenHeader(HttpRequestMessage request) diff --git a/src/Dapr.Client/DaprClientBuilder.cs b/src/Dapr.Client/DaprClientBuilder.cs index bac7e3fe..dcfec673 100644 --- a/src/Dapr.Client/DaprClientBuilder.cs +++ b/src/Dapr.Client/DaprClientBuilder.cs @@ -48,6 +48,8 @@ namespace Dapr.Client // property exposed for testing purposes internal string HttpEndpoint { get; private set; } + private Func HttpClientFactory { get; set; } + // property exposed for testing purposes internal JsonSerializerOptions JsonSerializerOptions { get; private set; } @@ -71,6 +73,13 @@ namespace Dapr.Client return this; } + // Internal for testing of DaprClient + internal DaprClientBuilder UseHttpClientFactory(Func factory) + { + this.HttpClientFactory = factory; + return this; + } + /// /// Overrides the gRPC endpoint used by for communicating with the Dapr runtime. /// @@ -153,7 +162,8 @@ namespace Dapr.Client var client = new Autogenerated.Dapr.DaprClient(channel); var apiTokenHeader = DaprClient.GetDaprApiTokenHeader(this.DaprApiToken); - return new DaprClientGrpc(channel, client, new HttpClient(), httpEndpoint, this.JsonSerializerOptions, apiTokenHeader); + var httpClient = HttpClientFactory is object ? HttpClientFactory() : new HttpClient(); + return new DaprClientGrpc(channel, client, httpClient, httpEndpoint, this.JsonSerializerOptions, apiTokenHeader); } } } diff --git a/src/Dapr.Client/properties/AssemblyInfo.cs b/src/Dapr.Client/properties/AssemblyInfo.cs index 51e56ef6..454eb8d6 100644 --- a/src/Dapr.Client/properties/AssemblyInfo.cs +++ b/src/Dapr.Client/properties/AssemblyInfo.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License. // ------------------------------------------------------------ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.Actors.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.Client.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.AspNetCore.IntegrationTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.AspNetCore.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] diff --git a/test/Dapr.Actors.Test/ApiTokenTests.cs b/test/Dapr.Actors.Test/ApiTokenTests.cs index c3df510c..382b882e 100644 --- a/test/Dapr.Actors.Test/ApiTokenTests.cs +++ b/test/Dapr.Actors.Test/ApiTokenTests.cs @@ -4,8 +4,6 @@ // ------------------------------------------------------------ using System; -using System.Collections.Concurrent; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Dapr.Actors.Client; @@ -16,107 +14,96 @@ namespace Dapr.Actors.Test { public class ApiTokenTests { - [Fact(Skip = "Failing due to #573")] - public void CreateProxyWithRemoting_WithApiToken() + [Fact] + public async Task CreateProxyWithRemoting_WithApiToken() { + await using var client = TestClient.CreateForMessageHandler(); + var actorId = new ActorId("abc"); - var handler = new TestHttpClientHandler(); var options = new ActorProxyOptions { DaprApiToken = "test_token", }; - var factory = new ActorProxyFactory(options, handler); - var proxy = factory.CreateActorProxy(actorId, "TestActor"); - var task = proxy.SetCountAsync(1, new CancellationToken()); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var headerValues = entry.Request.Headers.GetValues("dapr-api-token"); + var request = await client.CaptureHttpRequestAsync(async handler => + { + var factory = new ActorProxyFactory(options, handler); + var proxy = factory.CreateActorProxy(actorId, "TestActor"); + await proxy.SetCountAsync(1, new CancellationToken()); + }); + + request.Dismiss(); + + var headerValues = request.Request.Headers.GetValues("dapr-api-token"); headerValues.Should().Contain("test_token"); } - [Fact(Skip = "Failing due to #573")] - public void CreateProxyWithRemoting_WithNoApiToken() + [Fact] + public async Task CreateProxyWithRemoting_WithNoApiToken() { - var actorId = new ActorId("abc"); - var handler = new TestHttpClientHandler(); - var factory = new ActorProxyFactory(null, handler); - var proxy = factory.CreateActorProxy(actorId, "TestActor"); - var task = proxy.SetCountAsync(1, new CancellationToken()); + await using var client = TestClient.CreateForMessageHandler(); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - Action action = () => entry.Request.Headers.GetValues("dapr-api-token"); - action.Should().Throw(); + var actorId = new ActorId("abc"); + + var request = await client.CaptureHttpRequestAsync(async handler => + { + var factory = new ActorProxyFactory(null, handler); + var proxy = factory.CreateActorProxy(actorId, "TestActor"); + await proxy.SetCountAsync(1, new CancellationToken()); + }); + + request.Dismiss(); + + Assert.Throws(() => + { + request.Request.Headers.GetValues("dapr-api-token"); + }); } - [Fact(Skip = "Failing due to #573")] - public void CreateProxyWithNoRemoting_WithApiToken() + [Fact] + public async Task CreateProxyWithNoRemoting_WithApiToken() { + await using var client = TestClient.CreateForMessageHandler(); + var actorId = new ActorId("abc"); - var handler = new TestHttpClientHandler(); var options = new ActorProxyOptions { DaprApiToken = "test_token", }; - var factory = new ActorProxyFactory(options, handler); - var proxy = factory.Create(actorId, "TestActor"); - var task = proxy.InvokeMethodAsync("SetCountAsync", 1, new CancellationToken()); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var headerValues = entry.Request.Headers.GetValues("dapr-api-token"); + var request = await client.CaptureHttpRequestAsync(async handler => + { + var factory = new ActorProxyFactory(options, handler); + var proxy = factory.Create(actorId, "TestActor"); + await proxy.InvokeMethodAsync("SetCountAsync", 1, new CancellationToken()); + }); + + request.Dismiss(); + + var headerValues = request.Request.Headers.GetValues("dapr-api-token"); headerValues.Should().Contain("test_token"); } - [Fact(Skip = "Failing due to #573")] - public void CreateProxyWithNoRemoting_WithNoApiToken() + [Fact] + public async Task CreateProxyWithNoRemoting_WithNoApiToken() { + await using var client = TestClient.CreateForMessageHandler(); + var actorId = new ActorId("abc"); - var handler = new TestHttpClientHandler(); - var factory = new ActorProxyFactory(null, handler); - var proxy = factory.Create(actorId, "TestActor"); - var task = proxy.InvokeMethodAsync("SetCountAsync", 1, new CancellationToken()); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - Action action = () => entry.Request.Headers.GetValues("dapr-api-token"); - action.Should().Throw(); - } - - - public class Entry - { - public Entry(HttpRequestMessage request) + var request = await client.CaptureHttpRequestAsync(async handler => { - this.Request = request; + var factory = new ActorProxyFactory(null, handler); + var proxy = factory.Create(actorId, "TestActor"); + await proxy.InvokeMethodAsync("SetCountAsync", 1, new CancellationToken()); + }); - this.Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } + request.Dismiss(); - public TaskCompletionSource Completion { get; } - - public HttpRequestMessage Request { get; } - } - - private class TestHttpClientHandler : HttpClientHandler - { - public TestHttpClientHandler() + Assert.Throws(() => { - this.Requests = new ConcurrentQueue(); - } - - public ConcurrentQueue Requests { get; } - - public Action Handler { get; set; } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var entry = new Entry(request); - this.Handler?.Invoke(entry); - this.Requests.Enqueue(entry); - - using (cancellationToken.Register(() => entry.Completion.TrySetCanceled())) - { - return await entry.Completion.Task.ConfigureAwait(false); - } - } + request.Request.Headers.GetValues("dapr-api-token"); + }); } } } diff --git a/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj b/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj index ddcaf3ca..e1f5b60f 100644 --- a/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj +++ b/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj @@ -2,6 +2,7 @@ netcoreapp3.1;net5 Dapr.Actors + $(DefineConstants);ACTORS @@ -20,6 +21,11 @@ + + + + + diff --git a/test/Dapr.Actors.Test/DaprHttpInteractorTest.cs b/test/Dapr.Actors.Test/DaprHttpInteractorTest.cs index d2f182a5..9f46c026 100644 --- a/test/Dapr.Actors.Test/DaprHttpInteractorTest.cs +++ b/test/Dapr.Actors.Test/DaprHttpInteractorTest.cs @@ -5,15 +5,13 @@ namespace Dapr.Actors.Test { - using System; - using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; + using System.Security; using System.Security.Authentication; using System.Text.Json; - using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Xunit; @@ -23,224 +21,226 @@ namespace Dapr.Actors.Test /// public class DaprHttpInteractorTest { - public class Entry + [Fact] + public async Task GetState_ValidateRequest() { - public Entry(HttpRequestMessage request) - { - this.Request = request; + await using var client = TestClient.CreateForDaprHttpInterator(); - this.Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - - public TaskCompletionSource Completion { get; } - - public HttpRequestMessage Request { get; } - } - - private class TestHttpClientHandler : HttpClientHandler - { - public TestHttpClientHandler() - { - this.Requests = new ConcurrentQueue(); - } - - public ConcurrentQueue Requests { get; } - - public Action Handler { get; set; } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var entry = new Entry(request); - this.Handler?.Invoke(entry); - this.Requests.Enqueue(entry); - - using (cancellationToken.Register(() => entry.Completion.TrySetCanceled())) - { - return await entry.Completion.Task.ConfigureAwait(false); - } - } - } - - [Fact(Skip = "Failing due to #573")] - public void GetState_ValidateRequest() - { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var keyName = "StateKey_Test"; - var task = httpInteractor.GetStateAsync(actorType, actorId, keyName); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.GetStateAsync(actorType, actorId, keyName); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/'); + request.Dismiss(); + + var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/'); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateKeyRelativeUrlFormat, actorType, actorId, keyName); actualPath.Should().Be(expectedPath); - entry.Request.Method.Should().Be(HttpMethod.Get); + request.Request.Method.Should().Be(HttpMethod.Get); } [Fact] - public void SaveStateTransactionally_ValidateRequest() + public async Task SaveStateTransactionally_ValidateRequest() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var data = "StateData"; - var task = httpInteractor.SaveStateTransactionallyAsync(actorType, actorId, data); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.SaveStateTransactionallyAsync(actorType, actorId, data); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/'); + request.Dismiss(); + + var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/'); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateRelativeUrlFormat, actorType, actorId); actualPath.Should().Be(expectedPath); - entry.Request.Method.Should().Be(HttpMethod.Put); + request.Request.Method.Should().Be(HttpMethod.Put); } [Fact] - public void InvokeActorMethodWithoutRemoting_ValidateRequest() + public async Task InvokeActorMethodWithoutRemoting_ValidateRequest() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var methodName = "MethodName"; var payload = "JsonData"; - var task = httpInteractor.InvokeActorMethodWithoutRemotingAsync(actorType, actorId, methodName, payload); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.InvokeActorMethodWithoutRemotingAsync(actorType, actorId, methodName, payload); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/'); + request.Dismiss(); + + var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/'); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorMethodRelativeUrlFormat, actorType, actorId, methodName); actualPath.Should().Be(expectedPath); - entry.Request.Method.Should().Be(HttpMethod.Put); + request.Request.Method.Should().Be(HttpMethod.Put); } [Fact] - public void RegisterReminder_ValidateRequest() + public async Task RegisterReminder_ValidateRequest() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var reminderName = "ReminderName"; var payload = "JsonData"; - var task = httpInteractor.RegisterReminderAsync(actorType, actorId, reminderName, payload); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.RegisterReminderAsync(actorType, actorId, reminderName, payload); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/'); + request.Dismiss(); + + var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/'); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName); actualPath.Should().Be(expectedPath); - entry.Request.Method.Should().Be(HttpMethod.Put); + request.Request.Method.Should().Be(HttpMethod.Put); } [Fact] - public void UnregisterReminder_ValidateRequest() + public async Task UnregisterReminder_ValidateRequest() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var reminderName = "ReminderName"; - var task = httpInteractor.UnregisterReminderAsync(actorType, actorId, reminderName); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.UnregisterReminderAsync(actorType, actorId, reminderName); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/'); + request.Dismiss(); + + var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/'); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName); actualPath.Should().Be(expectedPath); - entry.Request.Method.Should().Be(HttpMethod.Delete); + request.Request.Method.Should().Be(HttpMethod.Delete); } [Fact] - public void RegisterTimer_ValidateRequest() + public async Task RegisterTimer_ValidateRequest() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; var payload = "JsonData"; - var task = httpInteractor.RegisterTimerAsync(actorType, actorId, timerName, payload); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.RegisterTimerAsync(actorType, actorId, timerName, payload); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/'); + request.Dismiss(); + + var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/'); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName); actualPath.Should().Be(expectedPath); - entry.Request.Method.Should().Be(HttpMethod.Put); + request.Request.Method.Should().Be(HttpMethod.Put); } [Fact] - public void UnregisterTimer_ValidateRequest() + public async Task UnregisterTimer_ValidateRequest() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; - var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/'); + request.Dismiss(); + + var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/'); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName); actualPath.Should().Be(expectedPath); - entry.Request.Method.Should().Be(HttpMethod.Delete); + request.Request.Method.Should().Be(HttpMethod.Delete); } [Fact] - public void Call_WithApiTokenSet() + public async Task Call_WithApiTokenSet() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler, apiToken: "test_token"); + await using var client = TestClient.CreateForDaprHttpInterator(apiToken: "test_token"); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; - var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); + request.Dismiss(); + + request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); headerValues.Count().Should().Be(1); headerValues.First().Should().Be("test_token"); } [Fact] - public void Call_WithoutApiToken() + public async Task Call_WithoutApiToken() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; - var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + }); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); + request.Dismiss(); + + request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); headerValues.Should().BeNull(); } [Fact] public async Task Call_ValidateUnsuccessfulResponse() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; - var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + }); + + request.Dismiss(); var error = new DaprError() { @@ -253,43 +253,54 @@ namespace Dapr.Actors.Test Content = new StringContent(JsonSerializer.Serialize(error)) }; - entry.Completion.SetResult(message); - await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); + await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(message); + }); } [Fact] public async Task Call_ValidateUnsuccessful404Response() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; - var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + }); var message = new HttpResponseMessage(HttpStatusCode.NotFound); - entry.Completion.SetResult(message); - await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); + await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(message); + }); } [Fact] public async Task Call_ValidateUnauthorizedResponse() { - var handler = new TestHttpClientHandler(); - var httpInteractor = new DaprHttpInteractor(handler); + await using var client = TestClient.CreateForDaprHttpInterator(); + var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; - var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); - handler.Requests.TryDequeue(out var entry).Should().BeTrue(); + var request = await client.CaptureHttpRequestAsync(async httpInteractor => + { + await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); + }); var message = new HttpResponseMessage(HttpStatusCode.Unauthorized); - entry.Completion.SetResult(message); - await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); + + await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(message); + }); } } } diff --git a/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj b/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj index 7233a0ea..9f57dc16 100644 --- a/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj +++ b/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj @@ -23,8 +23,4 @@ - - - - \ No newline at end of file diff --git a/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj b/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj index 2c83a4a9..78139576 100644 --- a/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj +++ b/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj @@ -18,8 +18,8 @@ - + diff --git a/test/Dapr.AspNetCore.Test/DaprClientBuilderTest.cs b/test/Dapr.AspNetCore.Test/DaprClientBuilderTest.cs index 5461484f..b33d33ba 100644 --- a/test/Dapr.AspNetCore.Test/DaprClientBuilderTest.cs +++ b/test/Dapr.AspNetCore.Test/DaprClientBuilderTest.cs @@ -37,9 +37,8 @@ namespace Dapr.AspNetCore.Test [Fact] public void DaprClientBuilder_DoesNotOverrideUserGrpcChannelOptions() { - var httpClient = new TestHttpClient(); var builder = new DaprClientBuilder(); - var daprClient = builder.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }).Build(); + var daprClient = builder.UseGrpcChannelOptions(new GrpcChannelOptions()).Build(); Assert.False(builder.GrpcChannelOptions.ThrowOperationCanceledOnCancellation); } diff --git a/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs b/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs index c0540e5c..6e32faa7 100644 --- a/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs +++ b/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs @@ -6,13 +6,11 @@ namespace Dapr.AspNetCore.Test { using System; - using System.Net; using System.Text.Json; using System.Threading.Tasks; using Dapr.Client; using Dapr.Client.Autogen.Grpc.v1; using FluentAssertions; - using Grpc.Net.Client; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -25,37 +23,41 @@ namespace Dapr.AspNetCore.Test [Fact] public async Task BindAsync_WithoutMatchingRouteValue_ReportsError() { - var binder = new StateEntryModelBinder("testStore", "test", isStateEntry: false, typeof(Widget)); + await using var client = TestClient.CreateForDaprClient(); - var httpClient = new TestHttpClient(); - var context = CreateContext(CreateServices(httpClient)); + var binder = new StateEntryModelBinder("testStore", "test", isStateEntry: false, typeof(Widget)); + var context = CreateContext(CreateServices(client.InnerClient)); await binder.BindModelAsync(context); + context.Result.IsModelSet.Should().BeFalse(); context.ModelState.ErrorCount.Should().Be(1); context.ModelState["testParameter"].Errors.Count.Should().Be(1); - httpClient.Requests.Count.Should().Be(0); + // No request to state store, validated by disposing client } [Fact] public async Task BindAsync_CanBindValue() { + await using var client = TestClient.CreateForDaprClient(); + var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: false, typeof(Widget)); // Configure Client - var httpClient = new TestHttpClient(); - var context = CreateContext(CreateServices(httpClient)); + var context = CreateContext(CreateServices(client.InnerClient)); context.HttpContext.Request.RouteValues["id"] = "test"; - var task = binder.BindModelAsync(context); + + var request = await client.CaptureGrpcRequestAsync(async _ => + { + await binder.BindModelAsync(context); + }); // Create Response & Respond var state = new Widget() { Size = "small", Color = "yellow", }; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(state, entry); + await SendResponseWithState(state, request); // Get response and validate - await task; context.Result.IsModelSet.Should().BeTrue(); context.Result.Model.As().Size.Should().Be("small"); context.Result.Model.As().Color.Should().Be("yellow"); @@ -67,21 +69,24 @@ namespace Dapr.AspNetCore.Test [Fact] public async Task BindAsync_CanBindStateEntry() { + await using var client = TestClient.CreateForDaprClient(); + var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: true, typeof(Widget)); // Configure Client - var httpClient = new TestHttpClient(); - var context = CreateContext(CreateServices(httpClient)); + var context = CreateContext(CreateServices(client.InnerClient)); context.HttpContext.Request.RouteValues["id"] = "test"; - var task = binder.BindModelAsync(context); + + var request = await client.CaptureGrpcRequestAsync(async _ => + { + await binder.BindModelAsync(context); + }); // Create Response & Respond var state = new Widget() { Size = "small", Color = "yellow", }; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(state, entry); + await SendResponseWithState(state, request); // Get response and validate - await task; context.Result.IsModelSet.Should().BeTrue(); context.Result.Model.As>().Key.Should().Be("test"); context.Result.Model.As>().Value.Size.Should().Be("small"); @@ -94,18 +99,21 @@ namespace Dapr.AspNetCore.Test [Fact] public async Task BindAsync_ReturnsNullForNonExistentStateEntry() { + await using var client = TestClient.CreateForDaprClient(); + var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: false, typeof(Widget)); // Configure Client - var httpClient = new TestHttpClient(); - var context = CreateContext(CreateServices(httpClient)); + var context = CreateContext(CreateServices(client.InnerClient)); context.HttpContext.Request.RouteValues["id"] = "test"; - var task = binder.BindModelAsync(context); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(null, entry); + var request = await client.CaptureGrpcRequestAsync(async _ => + { + await binder.BindModelAsync(context); + }); + + await SendResponseWithState(null, request); - await task; context.ModelState.IsValid.Should().BeTrue(); context.Result.IsModelSet.Should().BeFalse(); context.Result.Should().Be(ModelBindingResult.Failed()); @@ -114,18 +122,21 @@ namespace Dapr.AspNetCore.Test [Fact] public async Task BindAsync_WithStateEntry_ForNonExistentStateEntry() { + await using var client = TestClient.CreateForDaprClient(); + var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: true, typeof(Widget)); // Configure Client - var httpClient = new TestHttpClient(); - var context = CreateContext(CreateServices(httpClient)); + var context = CreateContext(CreateServices(client.InnerClient)); context.HttpContext.Request.RouteValues["id"] = "test"; - var task = binder.BindModelAsync(context); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(null, entry); + var request = await client.CaptureGrpcRequestAsync(async _ => + { + await binder.BindModelAsync(context); + }); + + await SendResponseWithState(null, request); - await task; context.ModelState.IsValid.Should().BeTrue(); context.Result.IsModelSet.Should().BeTrue(); ((StateEntry)context.Result.Model).Value.Should().BeNull(); @@ -148,27 +159,21 @@ namespace Dapr.AspNetCore.Test }; } - private async Task SendResponseWithState(T state, TestHttpClient.Entry entry) + private async Task SendResponseWithState(T state, TestClient.TestGrpcRequest request) { var stateData = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web)); - var stateResponse = new GetStateResponse + var stateResponse = new GetStateResponse() { Data = stateData, Etag = "test", }; - var streamContent = await GrpcUtils.CreateResponseContent(stateResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); + await request.CompleteWithMessageAsync(stateResponse); } - private static IServiceProvider CreateServices(TestHttpClient httpClient) + private static IServiceProvider CreateServices(DaprClient daprClient) { var services = new ServiceCollection(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); - services.AddSingleton(daprClient); return services.BuildServiceProvider(); } diff --git a/test/Dapr.Client.Test/Dapr.Client.Test.csproj b/test/Dapr.Client.Test/Dapr.Client.Test.csproj index 7264f972..91a93d75 100644 --- a/test/Dapr.Client.Test/Dapr.Client.Test.csproj +++ b/test/Dapr.Client.Test/Dapr.Client.Test.csproj @@ -30,7 +30,7 @@ - + diff --git a/test/Dapr.Client.Test/DaprApiTokenTest.cs b/test/Dapr.Client.Test/DaprApiTokenTest.cs index e9ffd50b..775290cf 100644 --- a/test/Dapr.Client.Test/DaprApiTokenTest.cs +++ b/test/Dapr.Client.Test/DaprApiTokenTest.cs @@ -5,12 +5,9 @@ namespace Dapr.Client.Test { - using System; - using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; - using Grpc.Net.Client; using Xunit; using Autogenerated = Dapr.Client.Autogen.Grpc.v1; @@ -20,19 +17,19 @@ namespace Dapr.Client.Test public async Task DaprCall_WithApiTokenSet() { // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .UseDaprApiToken("test_token") - .Build(); + await using var client = TestClient.CreateForDaprClient(c => c.UseDaprApiToken("test_token")); - var task = daprClient.GetSecretAsync("testStore", "test_key"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetSecretAsync("testStore", "test_key"); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); - entry.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); + request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); headerValues.Count().Should().Be(1); headerValues.First().Should().Be("test_token"); } @@ -41,18 +38,19 @@ namespace Dapr.Client.Test public async Task DaprCall_WithoutApiToken() { // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetSecretAsync("testStore", "test_key"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetSecretAsync("testStore", "test_key"); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); - entry.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); + request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); headerValues.Should().BeNull(); } } diff --git a/test/Dapr.Client.Test/DaprClientTest.InvokeMethodAsync.cs b/test/Dapr.Client.Test/DaprClientTest.InvokeMethodAsync.cs index 7915dc4b..b0606aaa 100644 --- a/test/Dapr.Client.Test/DaprClientTest.InvokeMethodAsync.cs +++ b/test/Dapr.Client.Test/DaprClientTest.InvokeMethodAsync.cs @@ -13,14 +13,10 @@ namespace Dapr.Client.Test using System.Text.Json; using System.Threading.Tasks; using Dapr.Client; - using FluentAssertions; - using Grpc.Core; - using Grpc.Net.Client; - using Moq; using Xunit; // Most of the InvokeMethodAsync functionality on DaprClient is non-abstract methods that - // forward to a few different entry points to create a message, or send a message and process + // forward to a few different request points to create a message, or send a message and process // its result. // // So we write basic tests for all of those that every parameter passing is correct, and then @@ -37,270 +33,232 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodAsync_VoidVoidNoHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var task = client.InvokeMethodAsync("app1", "mymethod"); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + await daprClient.InvokeMethodAsync("app1", "mymethod"); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Post); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); - Assert.Null(entry.Request.Content); + Assert.Equal(request.Request.Method, HttpMethod.Post); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); + Assert.Null(request.Request.Content); - entry.Respond(new HttpResponseMessage()); - await task; + await request.CompleteAsync(new HttpResponseMessage()); } [Fact] public async Task InvokeMethodAsync_VoidVoidWithHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var task = client.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod"); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + await daprClient.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod"); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Put); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); - Assert.Null(entry.Request.Content); + Assert.Equal(request.Request.Method, HttpMethod.Put); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); + Assert.Null(request.Request.Content); - entry.Respond(new HttpResponseMessage()); - await task; + await request.CompleteAsync(new HttpResponseMessage()); } [Fact] public async Task InvokeMethodAsync_VoidResponseNoHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var task = client.InvokeMethodAsync("app1", "mymethod"); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodAsync("app1", "mymethod"); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Post); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); - Assert.Null(entry.Request.Content); + Assert.Equal(request.Request.Method, HttpMethod.Post); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); + Assert.Null(request.Request.Content); var expected = new Widget() { Color = "red", }; - entry.RespondWithJson(expected, jsonSerializerOptions); - var actual = await task; + var actual = await request.CompleteWithJsonAsync(expected, jsonSerializerOptions); Assert.Equal(expected.Color, actual.Color); } [Fact] public async Task InvokeMethodAsync_VoidResponseWithHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var task = client.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod"); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod"); + }); - // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Put); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); - Assert.Null(entry.Request.Content); + Assert.Equal(request.Request.Method, HttpMethod.Put); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); + Assert.Null(request.Request.Content); var expected = new Widget() { Color = "red", }; - entry.RespondWithJson(expected, jsonSerializerOptions); - var actual = await task; + var actual = await request.CompleteWithJsonAsync(expected, jsonSerializerOptions); Assert.Equal(expected.Color, actual.Color); } [Fact] public async Task InvokeMethodAsync_RequestVoidNoHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var data = new Widget() { Color = "red", }; - var task = client.InvokeMethodAsync("app1", "mymethod", data); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + await daprClient.InvokeMethodAsync("app1", "mymethod", data); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Post); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); + Assert.Equal(request.Request.Method, HttpMethod.Post); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); - var content = Assert.IsType(entry.Request.Content); + var content = Assert.IsType(request.Request.Content); Assert.Equal(data.GetType(), content.ObjectType); Assert.Same(data, content.Value); - entry.Respond(new HttpResponseMessage()); - await task; + await request.CompleteAsync(new HttpResponseMessage()); } [Fact] public async Task InvokeMethodAsync_RequestVoidWithHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var data = new Widget() { Color = "red", }; - var task = client.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod", data); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + await daprClient.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod", data); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Put); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); + Assert.Equal(request.Request.Method, HttpMethod.Put); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); - var content = Assert.IsType(entry.Request.Content); + var content = Assert.IsType(request.Request.Content); Assert.Equal(data.GetType(), content.ObjectType); Assert.Same(data, content.Value); - entry.Respond(new HttpResponseMessage()); - await task; + await request.CompleteAsync(new HttpResponseMessage()); } [Fact] public async Task InvokeMethodAsync_RequestResponseNoHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var data = new Widget() { Color = "red", }; - var task = client.InvokeMethodAsync("app1", "mymethod", data); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodAsync("app1", "mymethod", data); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Post); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); + Assert.Equal(request.Request.Method, HttpMethod.Post); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); - var content = Assert.IsType(entry.Request.Content); + var content = Assert.IsType(request.Request.Content); Assert.Equal(data.GetType(), content.ObjectType); Assert.Same(data, content.Value); - entry.RespondWithJson(data, jsonSerializerOptions); - var actual = await task; + var actual = await request.CompleteWithJsonAsync(data, jsonSerializerOptions); Assert.Equal(data.Color, actual.Color); } [Fact] public async Task InvokeMethodAsync_RequestResponseWithHttpMethod_Success() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var data = new Widget() { Color = "red", }; - var task = client.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod", data); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodAsync(HttpMethod.Put, "app1", "mymethod", data); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - Assert.Equal(entry.Request.Method, HttpMethod.Put); - Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); + Assert.Equal(request.Request.Method, HttpMethod.Put); + Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri); - var content = Assert.IsType(entry.Request.Content); + var content = Assert.IsType(request.Request.Content); Assert.Equal(data.GetType(), content.ObjectType); Assert.Same(data, content.Value); - entry.RespondWithJson(data, jsonSerializerOptions); - var actual = await task; + var actual = await request.CompleteWithJsonAsync(data, jsonSerializerOptions); Assert.Equal(data.Color, actual.Color); } [Fact] public async Task InvokeMethodAsync_WrapsHttpRequestException() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", "test"); - var task = client.InvokeMethodAsync(request); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + var request = daprClient.CreateInvokeMethodRequest("test-app", "test"); + await daprClient.InvokeMethodAsync(request); + }); var exception = new HttpRequestException(); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Throw(exception); - - var thrown = await Assert.ThrowsAsync(async () => await task); + var thrown = await Assert.ThrowsAsync(async () => await request.CompleteWithExceptionAsync(exception)); Assert.Equal("test-app", thrown.AppId); Assert.Equal("test", thrown.MethodName); Assert.Same(exception, thrown.InnerException); @@ -310,24 +268,19 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodAsync_WrapsHttpRequestException_FromEnsureSuccessStatus() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", "test"); - var task = client.InvokeMethodAsync(request); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + var request = daprClient.CreateInvokeMethodRequest("test-app", "test"); + await daprClient.InvokeMethodAsync(request); + }); var response = new HttpResponseMessage(HttpStatusCode.NotFound); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Respond(response); - - var thrown = await Assert.ThrowsAsync(async () => await task); + var thrown = await Assert.ThrowsAsync(async () => await request.CompleteAsync(response)); Assert.Equal("test-app", thrown.AppId); Assert.Equal("test", thrown.MethodName); Assert.IsType(thrown.InnerException); @@ -337,24 +290,19 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodAsync_WithBody_WrapsHttpRequestException() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", "test"); - var task = client.InvokeMethodAsync(request); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + var request = daprClient.CreateInvokeMethodRequest("test-app", "test"); + return await daprClient.InvokeMethodAsync(request); + }); var exception = new HttpRequestException(); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Throw(exception); - - var thrown = await Assert.ThrowsAsync(async () => await task); + var thrown = await Assert.ThrowsAsync(async () => await request.CompleteWithExceptionAsync(exception)); Assert.Equal("test-app", thrown.AppId); Assert.Equal("test", thrown.MethodName); Assert.Same(exception, thrown.InnerException); @@ -364,24 +312,21 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodAsync_WithBody_WrapsHttpRequestException_FromEnsureSuccessStatus() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", "test"); - var task = client.InvokeMethodAsync(request); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + var request = daprClient.CreateInvokeMethodRequest("test-app", "test"); + return await daprClient.InvokeMethodAsync(request); + }); + + var response = new HttpResponseMessage(HttpStatusCode.NotFound); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Respond(response); - - var thrown = await Assert.ThrowsAsync(async () => await task); + var thrown = await Assert.ThrowsAsync(async () => await request.CompleteAsync(response)); Assert.Equal("test-app", thrown.AppId); Assert.Equal("test", thrown.MethodName); Assert.IsType(thrown.InnerException); @@ -391,28 +336,22 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodAsync_WrapsHttpRequestException_FromSerialization() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", "test"); - var task = client.InvokeMethodAsync(request); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + var request = daprClient.CreateInvokeMethodRequest("test-app", "test"); + return await daprClient.InvokeMethodAsync(request); + }); var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{ \"invalid\": true", Encoding.UTF8, "application/json") }; - - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Respond(response); - - var thrown = await Assert.ThrowsAsync(async () => await task); + var thrown = await Assert.ThrowsAsync(async () => await request.CompleteAsync(response)); Assert.Equal("test-app", thrown.AppId); Assert.Equal("test", thrown.MethodName); Assert.IsType(thrown.InnerException); @@ -428,41 +367,31 @@ namespace Dapr.Client.Test // garbage in -> garbage out - we don't deeply inspect what you pass. [InlineData("http://example.com", "https://test-endpoint:3501/v1.0/invoke/test-app/method/http://example.com")] - public void CreateInvokeMethodRequest_TransformsUrlCorrectly(string method, string expected) + public async Task CreateInvokeMethodRequest_TransformsUrlCorrectly(string method, string expected) { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", method); + var request = client.InnerClient.CreateInvokeMethodRequest("test-app", method); Assert.Equal(new Uri(expected).AbsoluteUri, request.RequestUri.AbsoluteUri); } [Fact] public async Task CreateInvokeMethodRequest_WithData_CreatesJsonContent() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var data = new Widget { Color = "red", }; - var request = client.CreateInvokeMethodRequest("test-app", "test", data); + var request = client.InnerClient.CreateInvokeMethodRequest("test-app", "test", data); var content = Assert.IsType(request.Content); Assert.Equal(typeof(Widget), content.ObjectType); Assert.Same(data, content.Value); @@ -475,46 +404,37 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodWithResponseAsync_ReturnsMessageWithoutCheckingStatus() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", "test"); - var task = client.InvokeMethodWithResponseAsync(request); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + var request = daprClient.CreateInvokeMethodRequest("test-app", "test"); + return await daprClient.InvokeMethodWithResponseAsync(request); + }); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Respond(new HttpResponseMessage(HttpStatusCode.BadRequest)); // Non-2xx response - - var response = await task; + var response = await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); // Non-2xx response + Assert.NotNull(response); } [Fact] public async Task InvokeMethodWithResponseAsync_WrapsHttpRequestException() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var request = client.CreateInvokeMethodRequest("test-app", "test"); - var task = client.InvokeMethodWithResponseAsync(request); + var request = await client.CaptureHttpRequestAsync(async daprClient => + { + var request = daprClient.CreateInvokeMethodRequest("test-app", "test"); + return await daprClient.InvokeMethodWithResponseAsync(request); + }); var exception = new HttpRequestException(); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Throw(exception); - - var thrown = await Assert.ThrowsAsync(async () => await task); + var thrown = await Assert.ThrowsAsync(async () => await request.CompleteWithExceptionAsync(exception)); Assert.Equal("test-app", thrown.AppId); Assert.Equal("test", thrown.MethodName); Assert.Same(exception, thrown.InnerException); @@ -524,20 +444,15 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodWithResponseAsync_PreventsNonDaprRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var client = new DaprClientGrpc( - GrpcChannel.ForAddress("http://localhost"), - Mock.Of(), - httpClient, - new Uri("https://test-endpoint:3501"), - jsonSerializerOptions, - default); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var request = new HttpRequestMessage(HttpMethod.Get, "https://example.com"); var ex = await Assert.ThrowsAsync(async () => { - await client.InvokeMethodWithResponseAsync(request); + await client.InnerClient.InvokeMethodWithResponseAsync(request); }); Assert.Equal("The provided request URI is not a Dapr service invocation URI.", ex.Message); diff --git a/test/Dapr.Client.Test/DaprClientTest.InvokeMethodGrpcAsync.cs b/test/Dapr.Client.Test/DaprClientTest.InvokeMethodGrpcAsync.cs index 06759891..a0531147 100644 --- a/test/Dapr.Client.Test/DaprClientTest.InvokeMethodGrpcAsync.cs +++ b/test/Dapr.Client.Test/DaprClientTest.InvokeMethodGrpcAsync.cs @@ -4,15 +4,11 @@ // ------------------------------------------------------------ using System; -using System.Net; -using System.Net.Http; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Dapr.Client.Autogen.Grpc.v1; using Dapr.Client.Autogen.Test.Grpc.v1; using FluentAssertions; -using Google.Protobuf; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using Grpc.Net.Client; @@ -26,57 +22,51 @@ namespace Dapr.Client.Test { public partial class DaprClientTest { - private DaprClient CreateTestClientGrpc(HttpClient httpClient) - { - return new DaprClientBuilder() - .UseJsonSerializationOptions(this.jsonSerializerOptions) - .UseGrpcChannelOptions(new GrpcChannelOptions - { - HttpClient = httpClient, - ThrowOperationCanceledOnCancellation = true, - }) - .Build(); - } - [Fact] public async Task InvokeMethodGrpcAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = CreateTestClientGrpc(httpClient); - - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); - - await FluentActions.Awaiting(async () => + await using var client = TestClient.CreateForDaprClient(c => { - await daprClient.InvokeMethodGrpcAsync("test", "test", new Request() { RequestParameter = "Hello " }, cancellationToken: ct); - }).Should().ThrowAsync(); + c.UseJsonSerializationOptions(this.jsonSerializerOptions); + }); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.InvokeMethodGrpcAsync("test", "test", new Request() { RequestParameter = "Hello " }, cancellationToken: cts.Token); + }); } [Fact] public async Task InvokeMethodGrpcAsync_CanInvokeMethodWithReturnTypeAndData() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = CreateTestClientGrpc(httpClient); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var task = daprClient.InvokeMethodGrpcAsync("test", "test", new Request() { RequestParameter = "Hello " }); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodGrpcAsync("test", "test", new Request() { RequestParameter = "Hello " }); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); envelope.Id.Should().Be("test"); envelope.Message.Method.Should().Be("test"); envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc); // Create Response & Respond var data = new Response() { Name = "Look, I was invoked!" }; - await SendResponse(data, entry); + var response = new Autogen.Grpc.v1.InvokeResponse() + { + Data = Any.Pack(data), + }; // Validate Response - var invokedResponse = await task; + var invokedResponse = await request.CompleteWithMessageAsync(response); invokedResponse.Name.Should().Be("Look, I was invoked!"); } @@ -117,25 +107,31 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodGrpcAsync_CanInvokeMethodWithReturnTypeNoData() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = CreateTestClientGrpc(httpClient); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseJsonSerializationOptions(this.jsonSerializerOptions); + }); - var task = daprClient.InvokeMethodGrpcAsync("test", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodGrpcAsync("test", "test"); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); envelope.Id.Should().Be("test"); envelope.Message.Method.Should().Be("test"); envelope.Message.ContentType.Should().Be(string.Empty); // Create Response & Respond var data = new Response() { Name = "Look, I was invoked!" }; - await SendResponse(data, entry); + var response = new Autogen.Grpc.v1.InvokeResponse() + { + Data = Any.Pack(data), + }; // Validate Response - var invokedResponse = await task; + var invokedResponse = await request.CompleteWithMessageAsync(response); invokedResponse.Name.Should().Be("Look, I was invoked!"); } @@ -234,16 +230,21 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodGrpcAsync_WithNoReturnTypeAndData() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = CreateTestClientGrpc(httpClient); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var invokeRequest = new Request() { RequestParameter = "Hello" }; - var task = daprClient.InvokeMethodGrpcAsync("test", "test", invokeRequest); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodGrpcAsync("test", "test", invokeRequest); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); envelope.Id.Should().Be("test"); envelope.Message.Method.Should().Be("test"); envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc); @@ -255,18 +256,20 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeMethodGrpcAsync_WithReturnTypeAndData() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = CreateTestClientGrpc(httpClient); + await using var client = TestClient.CreateForDaprClient(c => + { + c.UseJsonSerializationOptions(this.jsonSerializerOptions); + }); var invokeRequest = new Request() { RequestParameter = "Hello " }; - var invokedResponse = new Response { Name = "Look, I was invoked!" }; - - var task = daprClient.InvokeMethodGrpcAsync("test", "test", invokeRequest); + var invokeResponse = new Response { Name = "Look, I was invoked!" }; + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.InvokeMethodGrpcAsync("test", "test", invokeRequest); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); envelope.Id.Should().Be("test"); envelope.Message.Method.Should().Be("test"); envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc); @@ -274,10 +277,16 @@ namespace Dapr.Client.Test var actual = envelope.Message.Data.Unpack(); Assert.Equal(invokeRequest.RequestParameter, actual.RequestParameter); - await SendResponse(invokedResponse, entry); - var response = await task; + // Create Response & Respond + var data = new Response() { Name = "Look, I was invoked!" }; + var response = new Autogen.Grpc.v1.InvokeResponse() + { + Data = Any.Pack(data), + }; - response.Name.Should().Be(invokedResponse.Name); + // Validate Response + var invokedResponse = await request.CompleteWithMessageAsync(response); + invokeResponse.Name.Should().Be(invokeResponse.Name); } [Fact] @@ -285,7 +294,10 @@ namespace Dapr.Client.Test { // Configure Client var httpClient = new AppCallbackClient(new DaprAppCallbackService()); - var daprClient = CreateTestClientGrpc(httpClient); + var daprClient = new DaprClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions(){ HttpClient = httpClient, }) + .UseJsonSerializationOptions(this.jsonSerializerOptions) + .Build(); var request = new Request() { RequestParameter = "Look, I was invoked!" }; @@ -299,7 +311,10 @@ namespace Dapr.Client.Test { // Configure Client var httpClient = new AppCallbackClient(new DaprAppCallbackService()); - var daprClient = CreateTestClientGrpc(httpClient); + var daprClient = new DaprClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions(){ HttpClient = httpClient, }) + .UseJsonSerializationOptions(this.jsonSerializerOptions) + .Build(); var testRun = new TestRun(); testRun.Tests.Add(new TestCase() { Name = "test1" }); @@ -319,7 +334,10 @@ namespace Dapr.Client.Test { // Configure Client var httpClient = new AppCallbackClient(new DaprAppCallbackService()); - var daprClient = CreateTestClientGrpc(httpClient); + var daprClient = new DaprClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions(){ HttpClient = httpClient, }) + .UseJsonSerializationOptions(this.jsonSerializerOptions) + .Build(); var request = new Request() { RequestParameter = "Look, I was invoked!" }; @@ -328,19 +346,6 @@ namespace Dapr.Client.Test response.Name.Should().Be("unexpected"); } - - private async Task SendResponse(T data, TestHttpClient.Entry entry) where T : IMessage - { - var dataResponse = new InvokeResponse - { - Data = Any.Pack(data), - }; - - var streamContent = await GrpcUtils.CreateResponseContent(dataResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); - } - // Test implementation of the AppCallback.AppCallbackBase service private class DaprAppCallbackService : AppCallback.Autogen.Grpc.v1.AppCallback.AppCallbackBase { diff --git a/test/Dapr.Client.Test/InvokeBindingApiTest.cs b/test/Dapr.Client.Test/InvokeBindingApiTest.cs index 4f8e7091..17a8c3a2 100644 --- a/test/Dapr.Client.Test/InvokeBindingApiTest.cs +++ b/test/Dapr.Client.Test/InvokeBindingApiTest.cs @@ -8,7 +8,6 @@ namespace Dapr.Client.Test using System; using System.Collections.Generic; using System.Linq; - using System.Net; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -16,7 +15,6 @@ namespace Dapr.Client.Test using FluentAssertions; using Google.Protobuf; using Grpc.Core; - using Grpc.Net.Client; using Moq; using Xunit; @@ -25,33 +23,29 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeBindingAsync_ValidateRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; - var task = daprClient.InvokeBindingAsync("test", "create", invokeRequest); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.InvokeBindingAsync("test", "create", invokeRequest); + }); - // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.Name.Should().Be("test"); - request.Metadata.Count.Should().Be(0); - var json = request.Data.ToStringUtf8(); - var typeFromRequest = JsonSerializer.Deserialize(json, daprClient.JsonSerializerOptions); + request.Dismiss(); + + // Get Request and validate + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.Name.Should().Be("test"); + envelope.Metadata.Count.Should().Be(0); + var json = envelope.Data.ToStringUtf8(); + var typeFromRequest = JsonSerializer.Deserialize(json, client.InnerClient.JsonSerializerOptions); typeFromRequest.RequestParameter.Should().Be("Hello "); } [Fact] public async Task InvokeBindingAsync_ValidateRequest_WithMetadata() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary { @@ -59,55 +53,55 @@ namespace Dapr.Client.Test { "key2", "value2" } }; var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; - var task = daprClient.InvokeBindingAsync("test", "create", invokeRequest, metadata); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.InvokeBindingAsync("test", "create", invokeRequest, metadata); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.Name.Should().Be("test"); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); - var json = request.Data.ToStringUtf8(); - var typeFromRequest = JsonSerializer.Deserialize(json, daprClient.JsonSerializerOptions); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.Name.Should().Be("test"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); + var json = envelope.Data.ToStringUtf8(); + var typeFromRequest = JsonSerializer.Deserialize(json, client.InnerClient.JsonSerializerOptions); typeFromRequest.RequestParameter.Should().Be("Hello "); } [Fact] public async Task InvokeBindingAsync_WithNullPayload_ValidateRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.InvokeBindingAsync("test", "create", null); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.InvokeBindingAsync("test", "create", null); + }); - // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.Name.Should().Be("test"); - request.Metadata.Count.Should().Be(0); - var json = request.Data.ToStringUtf8(); + request.Dismiss(); + + // Get Request and validate + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.Name.Should().Be("test"); + envelope.Metadata.Count.Should().Be(0); + var json = envelope.Data.ToStringUtf8(); Assert.Equal("null", json); } [Fact] public async Task InvokeBindingAsync_WithRequest_ValidateRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var payload = new InvokeRequest() { RequestParameter = "Hello " }; - var request = new BindingRequest("test", "create") + var bindingRequest = new BindingRequest("test", "create") { - Data = JsonSerializer.SerializeToUtf8Bytes(payload, daprClient.JsonSerializerOptions), + Data = JsonSerializer.SerializeToUtf8Bytes(payload, client.InnerClient.JsonSerializerOptions), Metadata = { { "key1", "value1" }, @@ -115,37 +109,35 @@ namespace Dapr.Client.Test } }; - var task = daprClient.InvokeBindingAsync(request); - - // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - - var gRpcRequest = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - gRpcRequest.Name.Should().Be("test"); - gRpcRequest.Metadata.Count.Should().Be(2); - gRpcRequest.Metadata.Keys.Contains("key1").Should().BeTrue(); - gRpcRequest.Metadata.Keys.Contains("key2").Should().BeTrue(); - gRpcRequest.Metadata["key1"].Should().Be("value1"); - gRpcRequest.Metadata["key2"].Should().Be("value2"); - - var json = gRpcRequest.Data.ToStringUtf8(); - var typeFromRequest = JsonSerializer.Deserialize(json, daprClient.JsonSerializerOptions); - typeFromRequest.RequestParameter.Should().Be("Hello "); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.InvokeBindingAsync(bindingRequest); + }); var gRpcResponse = new Autogen.Grpc.v1.InvokeBindingResponse() { - Data = ByteString.CopyFrom(JsonSerializer.SerializeToUtf8Bytes(new Widget() { Color = "red", }, daprClient.JsonSerializerOptions)), + Data = ByteString.CopyFrom(JsonSerializer.SerializeToUtf8Bytes(new Widget() { Color = "red", }, client.InnerClient.JsonSerializerOptions)), Metadata = { { "anotherkey", "anothervalue" }, } }; - var streamContent = await GrpcUtils.CreateResponseContent(gRpcResponse); - entry.Completion.SetResult(GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent)); + var response = await request.CompleteWithMessageAsync(gRpcResponse); - var response = await task; - Assert.Same(request, response.Request); - Assert.Equal("red", JsonSerializer.Deserialize(response.Data.Span, daprClient.JsonSerializerOptions).Color); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.Name.Should().Be("test"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); + + var json = envelope.Data.ToStringUtf8(); + var typeFromRequest = JsonSerializer.Deserialize(json, client.InnerClient.JsonSerializerOptions); + typeFromRequest.RequestParameter.Should().Be("Hello "); + + Assert.Same(bindingRequest, response.Request); + Assert.Equal("red", JsonSerializer.Deserialize(response.Data.Span, client.InnerClient.JsonSerializerOptions).Color); Assert.Collection( response.Metadata, kvp => @@ -159,15 +151,10 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeBindingAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); + var cts = new CancellationTokenSource(); + cts.Cancel(); var metadata = new Dictionary { @@ -175,10 +162,10 @@ namespace Dapr.Client.Test { "key2", "value2" } }; var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; - var task = daprClient.InvokeBindingAsync("test", "create", invokeRequest, metadata, ct); - - await FluentActions.Awaiting(async () => await task) - .Should().ThrowAsync(); + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.InvokeBindingAsync("test", "create", invokeRequest, metadata, cts.Token); + }); } [Fact] @@ -203,26 +190,21 @@ namespace Dapr.Client.Test [Fact] public async Task InvokeBindingAsync_WrapsJsonException() { - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var response = new Autogen.Grpc.v1.InvokeBindingResponse(); - var bytes = JsonSerializer.SerializeToUtf8Bytes(new Widget(){ Color = "red", }, new JsonSerializerOptions(JsonSerializerDefaults.Web)); - response.Data = ByteString.CopyFrom(bytes.Take(10).ToArray()); // trim it to make invalid JSON blog + var bytes = JsonSerializer.SerializeToUtf8Bytes(new Widget(){ Color = "red", }, client.InnerClient.JsonSerializerOptions); + response.Data = ByteString.CopyFrom(bytes.Take(10).ToArray()); // trim it to make invalid JSON blob - var task = daprClient.InvokeBindingAsync("test", "test", new InvokeRequest() { RequestParameter = "Hello " }); - - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - - var streamContent = await GrpcUtils.CreateResponseContent(response); - entry.Completion.SetResult(GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent)); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.InvokeBindingAsync("test", "test", new InvokeRequest() { RequestParameter = "Hello " }); + }); + var envelope = await request.GetRequestEnvelopeAsync(); var ex = await Assert.ThrowsAsync(async () => { - await task; + await request.CompleteWithMessageAsync(response); }); Assert.IsType(ex.InnerException); } diff --git a/test/Dapr.Client.Test/PublishEventApiTest.cs b/test/Dapr.Client.Test/PublishEventApiTest.cs index 90f5b96e..44e8ac98 100644 --- a/test/Dapr.Client.Test/PublishEventApiTest.cs +++ b/test/Dapr.Client.Test/PublishEventApiTest.cs @@ -24,32 +24,30 @@ namespace Dapr.Client.Test [Fact] public async Task PublishEventAsync_CanPublishTopicWithData() { - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions{ HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var publishData = new PublishData() { PublishObjectParameter = "testparam" }; - var task = daprClient.PublishEventAsync(TestPubsubName, "test", publishData); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.PublishEventAsync(TestPubsubName, "test", publishData); + }); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - var jsonFromRequest = request.Data.ToStringUtf8(); + request.Dismiss(); - request.DataContentType.Should().Be("application/json"); - request.PubsubName.Should().Be(TestPubsubName); - request.Topic.Should().Be("test"); - jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData, daprClient.JsonSerializerOptions)); - request.Metadata.Count.Should().Be(0); + var envelope = await request.GetRequestEnvelopeAsync(); + var jsonFromRequest = envelope.Data.ToStringUtf8(); + + envelope.DataContentType.Should().Be("application/json"); + envelope.PubsubName.Should().Be(TestPubsubName); + envelope.Topic.Should().Be("test"); + jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData, client.InnerClient.JsonSerializerOptions)); + envelope.Metadata.Count.Should().Be(0); } [Fact] public async Task PublishEventAsync_CanPublishTopicWithData_WithMetadata() { - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions{ HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary { @@ -58,51 +56,53 @@ namespace Dapr.Client.Test }; var publishData = new PublishData() { PublishObjectParameter = "testparam" }; - var task = daprClient.PublishEventAsync(TestPubsubName, "test", publishData, metadata); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.PublishEventAsync(TestPubsubName, "test", publishData, metadata); + }); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - var jsonFromRequest = request.Data.ToStringUtf8(); + request.Dismiss(); - request.DataContentType.Should().Be("application/json"); - request.PubsubName.Should().Be(TestPubsubName); - request.Topic.Should().Be("test"); - jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData, daprClient.JsonSerializerOptions)); + var envelope = await request.GetRequestEnvelopeAsync(); + var jsonFromRequest = envelope.Data.ToStringUtf8(); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + envelope.DataContentType.Should().Be("application/json"); + envelope.PubsubName.Should().Be(TestPubsubName); + envelope.Topic.Should().Be("test"); + jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData, client.InnerClient.JsonSerializerOptions)); + + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); } [Fact] public async Task PublishEventAsync_CanPublishTopicWithNoContent() { - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.PublishEventAsync(TestPubsubName, "test"); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - var jsonFromRequest = request.Data.ToStringUtf8(); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.PublishEventAsync(TestPubsubName, "test"); + }); - request.PubsubName.Should().Be(TestPubsubName); - request.Topic.Should().Be("test"); - request.Data.Length.Should().Be(0); + request.Dismiss(); - request.Metadata.Count.Should().Be(0); + var envelope = await request.GetRequestEnvelopeAsync(); + var jsonFromRequest = envelope.Data.ToStringUtf8(); + + envelope.PubsubName.Should().Be(TestPubsubName); + envelope.Topic.Should().Be("test"); + envelope.Data.Length.Should().Be(0); + envelope.Metadata.Count.Should().Be(0); } [Fact] public async Task PublishEventAsync_CanPublishTopicWithNoContent_WithMetadata() { - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary { @@ -110,35 +110,37 @@ namespace Dapr.Client.Test { "key2", "value2" } }; - var task = daprClient.PublishEventAsync(TestPubsubName, "test", metadata); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.PublishEventAsync(TestPubsubName, "test", metadata); + }); - request.PubsubName.Should().Be(TestPubsubName); - request.Topic.Should().Be("test"); - request.Data.Length.Should().Be(0); + request.Dismiss(); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.PubsubName.Should().Be(TestPubsubName); + envelope.Topic.Should().Be("test"); + envelope.Data.Length.Should().Be(0); + + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); } [Fact] public async Task PublishEventAsync_WithCancelledToken() { - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); + var cts = new CancellationTokenSource(); + cts.Cancel(); - await FluentActions.Awaiting(async () => await daprClient.PublishEventAsync(TestPubsubName, "test", cancellationToken: ct)) - .Should().ThrowAsync(); + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.PublishEventAsync(TestPubsubName, "test", cancellationToken: cts.Token); + }); } // All overloads call through a common path that does exception handling. diff --git a/test/Dapr.Client.Test/SecretApiTest.cs b/test/Dapr.Client.Test/SecretApiTest.cs index 35f0c236..9051c4f2 100644 --- a/test/Dapr.Client.Test/SecretApiTest.cs +++ b/test/Dapr.Client.Test/SecretApiTest.cs @@ -7,13 +7,10 @@ namespace Dapr.Client.Test { using System; using System.Collections.Generic; - using System.Net; using System.Threading; using System.Threading.Tasks; using FluentAssertions; - using Google.Rpc; using Grpc.Core; - using Grpc.Net.Client; using Moq; using Xunit; using Autogenerated = Dapr.Client.Autogen.Grpc.v1; @@ -23,67 +20,66 @@ namespace Dapr.Client.Test [Fact] public async Task GetSecretAsync_ValidateRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }; - var task = daprClient.GetSecretAsync("testStore", "test_key", metadata); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetSecretAsync("testStore", "test_key", metadata); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test_key"); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test_key"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); } [Fact] public async Task GetSecretAsync_ReturnSingleSecret() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }; - var task = daprClient.GetSecretAsync("testStore", "test_key", metadata); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetSecretAsync("testStore", "test_key", metadata); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test_key"); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test_key"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); // Create Response & Respond var secrets = new Dictionary { { "redis_secret", "Guess_Redis" } }; - await SendResponseWithSecrets(secrets, entry); + var secretsResponse = await SendResponseWithSecrets(secrets, request); // Get response and validate - var secretsResponse = await task; secretsResponse.Count.Should().Be(1); secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse["redis_secret"].Should().Be("Guess_Redis"); @@ -92,29 +88,29 @@ namespace Dapr.Client.Test [Fact] public async Task GetSecretAsync_ReturnMultipleSecrets() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }; - var task = daprClient.GetSecretAsync("testStore", "test_key", metadata); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetSecretAsync("testStore", "test_key", metadata); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test_key"); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test_key"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); // Create Response & Respond var secrets = new Dictionary @@ -122,10 +118,9 @@ namespace Dapr.Client.Test { "redis_secret", "Guess_Redis" }, { "kafka_secret", "Guess_Kafka" } }; - await SendResponseWithSecrets(secrets, entry); + var secretsResponse = await SendResponseWithSecrets(secrets, request); // Get response and validate - var secretsResponse = await task; secretsResponse.Count.Should().Be(2); secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse["redis_secret"].Should().Be("Guess_Redis"); @@ -136,11 +131,7 @@ namespace Dapr.Client.Test [Fact] public async Task GetSecretAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary { @@ -148,11 +139,13 @@ namespace Dapr.Client.Test { "key2", "value2" } }; - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); - await FluentActions.Awaiting(async () => await daprClient.GetSecretAsync("testStore", "test_key", metadata, cancellationToken: ct)) - .Should().ThrowAsync(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.GetSecretAsync("testStore", "test_key", metadata, cancellationToken: cts.Token); + }); } [Fact] @@ -178,59 +171,58 @@ namespace Dapr.Client.Test [Fact] public async Task GetBulkSecretAsync_ValidateRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary(); metadata.Add("key1", "value1"); metadata.Add("key2", "value2"); - var task = daprClient.GetBulkSecretAsync("testStore", metadata); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetBulkSecretAsync("testStore", metadata); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); } [Fact] public async Task GetBulkSecretAsync_ReturnSingleSecret() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary(); metadata.Add("key1", "value1"); metadata.Add("key2", "value2"); - var task = daprClient.GetBulkSecretAsync("testStore", metadata); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetBulkSecretAsync("testStore", metadata); + }); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); // Create Response & Respond var secrets = new Dictionary(); secrets.Add("redis_secret", "Guess_Redis"); - await SendBulkResponseWithSecrets(secrets, entry); + var secretsResponse = await SendBulkResponseWithSecrets(secrets, request); // Get response and validate - var secretsResponse = await task; secretsResponse.Count.Should().Be(1); secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis"); @@ -239,35 +231,34 @@ namespace Dapr.Client.Test [Fact] public async Task GetBulkSecretAsync_ReturnMultipleSecrets() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary(); metadata.Add("key1", "value1"); metadata.Add("key2", "value2"); - var task = daprClient.GetBulkSecretAsync("testStore", metadata); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetBulkSecretAsync("testStore", metadata); + }); + // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Metadata.Count.Should().Be(2); - request.Metadata.Keys.Contains("key1").Should().BeTrue(); - request.Metadata.Keys.Contains("key2").Should().BeTrue(); - request.Metadata["key1"].Should().Be("value1"); - request.Metadata["key2"].Should().Be("value2"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Metadata.Count.Should().Be(2); + envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); + envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); + envelope.Metadata["key1"].Should().Be("value1"); + envelope.Metadata["key2"].Should().Be("value2"); // Create Response & Respond var secrets = new Dictionary(); secrets.Add("redis_secret", "Guess_Redis"); secrets.Add("kafka_secret", "Guess_Kafka"); - await SendBulkResponseWithSecrets(secrets, entry); + var secretsResponse = await SendBulkResponseWithSecrets(secrets, request); // Get response and validate - var secretsResponse = await task; secretsResponse.Count.Should().Be(2); secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis"); @@ -278,21 +269,19 @@ namespace Dapr.Client.Test [Fact] public async Task GetBulkSecretAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var metadata = new Dictionary(); metadata.Add("key1", "value1"); metadata.Add("key2", "value2"); - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); - await FluentActions.Awaiting(async () => await daprClient.GetBulkSecretAsync("testStore", metadata, cancellationToken: ct)) - .Should().ThrowAsync(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.GetBulkSecretAsync("testStore", metadata, cancellationToken: cts.Token); + }); } [Fact] @@ -315,17 +304,15 @@ namespace Dapr.Client.Test Assert.Same(rpcException, ex.InnerException); } - private async Task SendResponseWithSecrets(Dictionary secrets, TestHttpClient.Entry entry) + private async Task SendResponseWithSecrets(Dictionary secrets, TestClient.TestGrpcRequest request) { var secretResponse = new Autogenerated.GetSecretResponse(); secretResponse.Data.Add(secrets); - var streamContent = await GrpcUtils.CreateResponseContent(secretResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); + return await request.CompleteWithMessageAsync(secretResponse); } - private async Task SendBulkResponseWithSecrets(Dictionary secrets, TestHttpClient.Entry entry) + private async Task SendBulkResponseWithSecrets(Dictionary secrets, TestClient.TestGrpcRequest request) { var getBulkSecretResponse = new Autogenerated.GetBulkSecretResponse(); foreach (var secret in secrets) @@ -335,9 +322,7 @@ namespace Dapr.Client.Test getBulkSecretResponse.Data.Add(secret.Key, secretsResponse); } - var streamContent = await GrpcUtils.CreateResponseContent(getBulkSecretResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); + return await request.CompleteWithMessageAsync(getBulkSecretResponse); } } } diff --git a/test/Dapr.Client.Test/StateApiTest.cs b/test/Dapr.Client.Test/StateApiTest.cs index 90f31724..e1fee94f 100644 --- a/test/Dapr.Client.Test/StateApiTest.cs +++ b/test/Dapr.Client.Test/StateApiTest.cs @@ -27,21 +27,21 @@ namespace Dapr.Client.Test [Fact] public async Task GetStateAsync_CanReadState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAsync("testStore", "test"); + }); + + request.Dismiss(); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(data, entry); + var envelope = MakeGetStateResponse(data); + var state = await request.CompleteWithMessageAsync(envelope); // Get response and validate - var state = await task; state.Size.Should().Be("small"); state.Color.Should().Be("yellow"); } @@ -49,87 +49,81 @@ namespace Dapr.Client.Test [Fact] public async Task GetBulkStateAsync_CanReadState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var key = "test"; - var task = daprClient.GetBulkStateAsync("testStore", new List() { key }, null); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetBulkStateAsync("testStore", new List() { key }, null); + }); // Create Response & Respond var data = "value"; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithBulkState(key, data, entry); + var envelope = MakeGetBulkStateResponse(key, data); + var state = await request.CompleteWithMessageAsync(envelope); // Get response and validate - var state = await task; state.Should().HaveCount(1); } [Fact] public async Task GetBulkStateAsync_WrapsRpcException() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var key = "test"; - var task = daprClient.GetBulkStateAsync("testStore", new List() { key }, null); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetBulkStateAsync("testStore", new List() { key }, null); + }); // Create Response & Respond - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - entry.Respond(new HttpResponseMessage(HttpStatusCode.NotAcceptable)); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable)); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task GetBulkStateAsync_ValidateRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); + var key = "test"; var metadata = new Dictionary { { "partitionKey", "mypartition" } }; + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetBulkStateAsync("testStore", new List() { key }, null, metadata: metadata); + }); - var key = "test"; - var task = daprClient.GetBulkStateAsync("testStore", new List() { key }, null, metadata : metadata); + request.Dismiss(); // Create Response & Validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Metadata.Should().BeEquivalentTo(metadata); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Metadata.Should().BeEquivalentTo(metadata); } [Fact] public async Task GetStateAndEtagAsync_CanReadState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateAndETagAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAndETagAsync("testStore", "test"); + }); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(data, entry, "Test_Etag"); + var envelope = MakeGetStateResponse(data, "Test_Etag"); + var (state, etag) = await request.CompleteWithMessageAsync(envelope); // Get response and validate - var (state, etag) = await task; state.Size.Should().Be("small"); state.Color.Should().Be("yellow"); etag.Should().Be("Test_Etag"); @@ -138,66 +132,59 @@ namespace Dapr.Client.Test [Fact] public async Task GetStateAndETagAsync_WrapsRpcException() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAndETagAsync("testStore", "test"); + }); // Create Response & Respond - var task = daprClient.GetStateAndETagAsync("testStore", "test"); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); - entry.Completion.SetResult(response); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable)); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task GetStateAndETagAsync_WrapsJsonException() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAndETagAsync("testStore", "test"); + }); // Create Response & Respond - var task = daprClient.GetStateAndETagAsync("testStore", "test"); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - - var stateResponse = new Autogenerated.GetStateResponse() + var envelope = new Autogenerated.GetStateResponse() { // Totally NOT valid JSON Data = ByteString.CopyFrom(0x5b, 0x7b, 0x5b, 0x7b), }; - - var streamContent = await GrpcUtils.CreateResponseContent(stateResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteWithMessageAsync(envelope); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task GetStateAsync_CanReadEmptyState_ReturnsDefault() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateAsync("testStore", "test", ConsistencyMode.Eventual); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAsync("testStore", "test", ConsistencyMode.Eventual); + }); // Create Response & Respond - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(null, entry); + var envelope = MakeGetStateResponse(null); + var state = await request.CompleteWithMessageAsync(envelope); // Get response and validate - var state = await task; state.Should().BeNull(); } @@ -206,128 +193,117 @@ namespace Dapr.Client.Test [InlineData(ConsistencyMode.Strong, StateConsistency.ConsistencyStrong)] public async Task GetStateAsync_ValidateRequest(ConsistencyMode consistencyMode, StateConsistency expectedConsistencyMode) { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateAsync("testStore", "test", consistencyMode); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAsync("testStore", "test", consistencyMode); + }); // Get Request & Validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test"); - request.Consistency.Should().Be(expectedConsistencyMode); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test"); + envelope.Consistency.Should().Be(expectedConsistencyMode); // Create Response & Respond - await SendResponseWithState(null, entry); + var state = await request.CompleteWithMessageAsync(MakeGetStateResponse(null)); // Get response and validate - var state = await task; state.Should().BeNull(); } [Fact] public async Task GetStateAndEtagAsync_ValidateRequest() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); + var metadata = new Dictionary { { "partitionKey", "mypartition" } }; - - var task = daprClient.GetStateAsync("testStore", "test", metadata : metadata); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAsync("testStore", "test", metadata: metadata); + }); // Get Request & Validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test"); - request.Metadata.Should().BeEquivalentTo(metadata); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test"); + envelope.Metadata.Should().BeEquivalentTo(metadata); // Create Response & Respond - await SendResponseWithState(null, entry); + var state = await request.CompleteWithMessageAsync(MakeGetStateResponse(null)); // Get response and validate - var state = await task; state.Should().BeNull(); } [Fact] public async Task GetStateAsync_WrapsRpcException() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAsync("testStore", "test"); + }); // Create Response & Respond - var task = daprClient.GetStateAsync("testStore", "test"); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); - entry.Completion.SetResult(response); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable)); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task GetStateAsync_WrapsJsonException() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateAsync("testStore", "test"); + }); // Create Response & Respond - var task = daprClient.GetStateAsync("testStore", "test"); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var stateResponse = new Autogenerated.GetStateResponse() { // Totally NOT valid JSON Data = ByteString.CopyFrom(0x5b, 0x7b, 0x5b, 0x7b), }; - var streamContent = await GrpcUtils.CreateResponseContent(stateResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteWithMessageAsync(stateResponse); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task SaveStateAsync_CanSaveState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var widget = new Widget() { Size = "small", Color = "yellow", }; - var task = daprClient.SaveStateAsync("testStore", "test", widget); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.SaveStateAsync("testStore", "test", widget); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - - request.StoreName.Should().Be("testStore"); - request.States.Count.Should().Be(1); - var state = request.States[0]; + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.States.Count.Should().Be(1); + var state = envelope.States[0]; state.Key.Should().Be("test"); var stateJson = state.Value.ToStringUtf8(); - var stateFromRequest = JsonSerializer.Deserialize(stateJson, daprClient.JsonSerializerOptions); + var stateFromRequest = JsonSerializer.Deserialize(stateJson, client.InnerClient.JsonSerializerOptions); stateFromRequest.Size.Should().Be(widget.Size); stateFromRequest.Color.Should().Be(widget.Color); } @@ -335,91 +311,76 @@ namespace Dapr.Client.Test [Fact] public async Task GetStateAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var widget = new Widget() { Size = "small", Color = "yellow", }; - var task = daprClient.SaveStateAsync("testStore", "test", widget); + var cts = new CancellationTokenSource(); + cts.Cancel(); - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); - - await FluentActions.Awaiting(async () => await daprClient.GetStateAsync("testStore", "test", cancellationToken: ct)) - .Should().ThrowAsync(); + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.GetStateAsync("testStore", "test", cancellationToken: cts.Token); + }); } [Fact] public async Task SaveStateAsync_CanClearState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.SaveStateAsync("testStore", "test", null); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.SaveStateAsync("testStore", "test", null); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); - request.StoreName.Should().Be("testStore"); - request.States.Count.Should().Be(1); - var state = request.States[0]; + envelope.StoreName.Should().Be("testStore"); + envelope.States.Count.Should().Be(1); + var state = envelope.States[0]; state.Key.Should().Be("test"); - state.Value.Should().Equal(ByteString.Empty); } [Fact] public async Task SaveStateAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); - await FluentActions.Awaiting(async () => await daprClient.SaveStateAsync("testStore", "test", null, cancellationToken: ct)) - .Should().ThrowAsync(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.SaveStateAsync("testStore", "test", null, cancellationToken: cts.Token); + }); } [Fact] public async Task SetStateAsync_ThrowsForNonSuccess() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var widget = new Widget() { Size = "small", Color = "yellow", }; - var task = daprClient.SaveStateAsync("testStore", "test", widget); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.SaveStateAsync("testStore", "test", widget); + }); // Create Response & Respond - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); - entry.Completion.SetResult(response); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable)); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task ExecuteStateTransactionAsync_CanSaveState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var stateValue1 = new Widget() { Size = "small", Color = "yellow", }; var metadata1 = new Dictionary() @@ -445,20 +406,24 @@ namespace Dapr.Client.Test state3 }; - var task = daprClient.ExecuteStateTransactionAsync("testStore", states); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.ExecuteStateTransactionAsync("testStore", states); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var transactionRequest = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request.GetRequestEnvelopeAsync(); - transactionRequest.StoreName.Should().Be("testStore"); - transactionRequest.Operations.Count.Should().Be(3); + envelope.StoreName.Should().Be("testStore"); + envelope.Operations.Count.Should().Be(3); - var req1 = transactionRequest.Operations[0]; + var req1 = envelope.Operations[0]; req1.Request.Key.Should().Be("stateKey1"); req1.OperationType.Should().Be(StateOperationType.Upsert.ToString().ToLower()); var valueJson1 = req1.Request.Value.ToStringUtf8(); - var value1 = JsonSerializer.Deserialize(valueJson1, daprClient.JsonSerializerOptions); + var value1 = JsonSerializer.Deserialize(valueJson1, client.InnerClient.JsonSerializerOptions); value1.Size.Should().Be(stateValue1.Size); value1.Color.Should().Be(stateValue1.Color); req1.Request.Etag.Value.Should().Be("testEtag"); @@ -466,29 +431,25 @@ namespace Dapr.Client.Test req1.Request.Metadata["a"].Should().Be("b"); req1.Request.Options.Concurrency.Should().Be(2); - var req2 = transactionRequest.Operations[1]; + var req2 = envelope.Operations[1]; req2.Request.Key.Should().Be("stateKey2"); req2.OperationType.Should().Be(StateOperationType.Delete.ToString().ToLower()); var valueJson2 = req2.Request.Value.ToStringUtf8(); - var value2 = JsonSerializer.Deserialize(valueJson2, daprClient.JsonSerializerOptions); + var value2 = JsonSerializer.Deserialize(valueJson2, client.InnerClient.JsonSerializerOptions); value2.Should().Be(100); - var req3 = transactionRequest.Operations[2]; + var req3 = envelope.Operations[2]; req3.Request.Key.Should().Be("stateKey3"); req3.OperationType.Should().Be(StateOperationType.Upsert.ToString().ToLower()); var valueJson3 = req3.Request.Value.ToStringUtf8(); - var value3 = JsonSerializer.Deserialize(valueJson3, daprClient.JsonSerializerOptions); + var value3 = JsonSerializer.Deserialize(valueJson3, client.InnerClient.JsonSerializerOptions); value3.Should().Be("teststring"); } [Fact] public async Task ExecuteStateTransactionAsync_ThrowsForNonSuccess() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var widget1 = new Widget() { Size = "small", Color = "yellow", }; var state1 = new StateTransactionRequest("stateKey1", JsonSerializer.SerializeToUtf8Bytes(widget1), StateOperationType.Upsert); @@ -496,110 +457,105 @@ namespace Dapr.Client.Test { state1 }; - var task = daprClient.ExecuteStateTransactionAsync("testStore", states); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.ExecuteStateTransactionAsync("testStore", states); + }); // Create Response & Respond - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); - entry.Completion.SetResult(response); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable)); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task ExecuteStateTransactionAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); - - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); + await using var client = TestClient.CreateForDaprClient(); var operation = new StateTransactionRequest("test", null, StateOperationType.Delete); var operations = new List { - operation + operation, }; - await FluentActions.Awaiting(async () => await daprClient.ExecuteStateTransactionAsync("testStore", operations, new Dictionary(), cancellationToken: ct)) - .Should().ThrowAsync(); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.ExecuteStateTransactionAsync("testStore", operations, new Dictionary(), cancellationToken: cts.Token); + }); } [Fact] public async Task DeleteStateAsync_CanDeleteState() { - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.DeleteStateAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.DeleteStateAsync("testStore", "test"); + }); - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test"); + request.Dismiss(); + + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test"); } [Fact] public async Task DeleteStateAsync_ThrowsForNonSuccess() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.DeleteStateAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.DeleteStateAsync("testStore", "test"); + }); // Create Response & Respond - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); - entry.Completion.SetResult(response); - - var ex = await Assert.ThrowsAsync(async () => await task); + var ex = await Assert.ThrowsAsync(async () => + { + await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable)); + }); Assert.IsType(ex.InnerException); } [Fact] public async Task DeleteStateAsync_WithCancelledToken() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var ctSource = new CancellationTokenSource(); - CancellationToken ct = ctSource.Token; - ctSource.Cancel(); + var cts = new CancellationTokenSource(); + cts.Cancel(); - await FluentActions.Awaiting(async () => await daprClient.DeleteStateAsync("testStore", "key", cancellationToken: ct)) - .Should().ThrowAsync(); + await Assert.ThrowsAsync(async () => + { + await client.InnerClient.DeleteStateAsync("testStore", "key", cancellationToken: cts.Token); + }); } [Fact] public async Task GetStateEntryAsync_CanReadState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateEntryAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateEntryAsync("testStore", "test"); + }); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(data, entry); + var envelope = MakeGetStateResponse(data); + var state = await request.CompleteWithMessageAsync(envelope); // Get response and validate - var state = await task; state.Value.Size.Should().Be("small"); state.Value.Color.Should().Be("yellow"); } @@ -607,19 +563,17 @@ namespace Dapr.Client.Test [Fact] public async Task GetStateEntryAsync_CanReadEmptyState_ReturnsDefault() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateEntryAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateEntryAsync("testStore", "test"); + }); // Create Response & Respond - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(null, entry); + var envelope = MakeGetStateResponse(null); + var state = await request.CompleteWithMessageAsync(envelope); - var state = await task; state.Key.Should().Be("test"); state.Value.Should().BeNull(); } @@ -627,39 +581,41 @@ namespace Dapr.Client.Test [Fact] public async Task GetStateEntryAsync_CanSaveState() { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateEntryAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateEntryAsync("testStore", "test"); + }); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(data, entry); + var state = await request.CompleteWithMessageAsync(MakeGetStateResponse(data)); - var state = await task; state.Key.Should().Be("test"); state.Value.Size.Should().Be("small"); state.Value.Color.Should().Be("yellow"); // Modify the state and save it state.Value.Color = "green"; - var task2 = state.SaveAsync(); + + var request2 = await client.CaptureGrpcRequestAsync(async _ => + { + await state.SaveAsync(); + }); + + request2.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var envelope = await request2.GetRequestEnvelopeAsync(); - request.StoreName.Should().Be("testStore"); - request.States.Count.Should().Be(1); - var requestState = request.States[0]; + envelope.StoreName.Should().Be("testStore"); + envelope.States.Count.Should().Be(1); + var requestState = envelope.States[0]; requestState.Key.Should().Be("test"); var stateJson = requestState.Value.ToStringUtf8(); - var stateFromRequest = JsonSerializer.Deserialize(stateJson, daprClient.JsonSerializerOptions); + var stateFromRequest = JsonSerializer.Deserialize(stateJson, client.InnerClient.JsonSerializerOptions); stateFromRequest.Size.Should().Be("small"); stateFromRequest.Color.Should().Be("green"); } @@ -667,32 +623,34 @@ namespace Dapr.Client.Test [Fact] public async Task GetStateEntryAsync_CanDeleteState() { - // Configure client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); - var task = daprClient.GetStateEntryAsync("testStore", "test"); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.GetStateEntryAsync("testStore", "test"); + }); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - await SendResponseWithState(data, entry); + var state = await request.CompleteWithMessageAsync(MakeGetStateResponse(data)); - var state = await task; state.Key.Should().Be("test"); state.Value.Size.Should().Be("small"); state.Value.Color.Should().Be("yellow"); state.Value.Color = "green"; - var task2 = state.DeleteAsync(); + var request2 = await client.CaptureGrpcRequestAsync(async daprClient => + { + await state.DeleteAsync(); + }); + + request2.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test"); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test"); + } [Theory] @@ -706,11 +664,7 @@ namespace Dapr.Client.Test StateConsistency expectedConsistency, StateConcurrency expectedConcurrency) { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var widget = new Widget() { Size = "small", Color = "yellow", }; var stateOptions = new StateOptions @@ -724,14 +678,19 @@ namespace Dapr.Client.Test { "key1", "value1" }, { "key2", "value2" } }; - var task = daprClient.SaveStateAsync("testStore", "test", widget, stateOptions, metadata); + + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.SaveStateAsync("testStore", "test", widget, stateOptions, metadata); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.States.Count.Should().Be(1); - var state = request.States[0]; + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.States.Count.Should().Be(1); + var state = envelope.States[0]; state.Key.Should().Be("test"); state.Metadata.Count.Should().Be(2); state.Metadata.Keys.Contains("key1").Should().BeTrue(); @@ -742,7 +701,7 @@ namespace Dapr.Client.Test state.Options.Consistency.Should().Be(expectedConsistency); var stateJson = state.Value.ToStringUtf8(); - var stateFromRequest = JsonSerializer.Deserialize(stateJson, daprClient.JsonSerializerOptions); + var stateFromRequest = JsonSerializer.Deserialize(stateJson, client.InnerClient.JsonSerializerOptions); stateFromRequest.Size.Should().Be(widget.Size); stateFromRequest.Color.Should().Be(widget.Color); } @@ -758,11 +717,7 @@ namespace Dapr.Client.Test StateConsistency expectedConsistency, StateConcurrency expectedConcurrency) { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var widget = new Widget() { Size = "small", Color = "yellow", }; var stateOptions = new StateOptions @@ -776,14 +731,18 @@ namespace Dapr.Client.Test { "key1", "value1" }, { "key2", "value2" } }; - var task = daprClient.TrySaveStateAsync("testStore", "test", widget, "Test_Etag", stateOptions, metadata); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.TrySaveStateAsync("testStore", "test", widget, "Test_Etag", stateOptions, metadata); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.States.Count.Should().Be(1); - var state = request.States[0]; + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.States.Count.Should().Be(1); + var state = envelope.States[0]; state.Etag.Value.Should().Be("Test_Etag"); state.Metadata.Count.Should().Be(2); state.Metadata.Keys.Contains("key1").Should().BeTrue(); @@ -794,7 +753,7 @@ namespace Dapr.Client.Test state.Options.Consistency.Should().Be(expectedConsistency); var stateJson = state.Value.ToStringUtf8(); - var stateFromRequest = JsonSerializer.Deserialize(stateJson, daprClient.JsonSerializerOptions); + var stateFromRequest = JsonSerializer.Deserialize(stateJson, client.InnerClient.JsonSerializerOptions); stateFromRequest.Size.Should().Be(widget.Size); stateFromRequest.Color.Should().Be(widget.Color); } @@ -814,7 +773,7 @@ namespace Dapr.Client.Test .Setup(m => m.SaveStateAsync(It.IsAny(), It.IsAny())) .Throws(rpcException); - var ex = await Assert.ThrowsAsync(async () => + var ex = await Assert.ThrowsAsync(async () => { await client.DaprClient.TrySaveStateAsync("test", "test", "testValue", "someETag"); }); @@ -946,11 +905,7 @@ namespace Dapr.Client.Test StateConsistency expectedConsistency, StateConcurrency expectedConcurrency) { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var stateOptions = new StateOptions { @@ -958,15 +913,20 @@ namespace Dapr.Client.Test Consistency = consistencyMode }; - var task = daprClient.DeleteStateAsync("testStore", "test", stateOptions); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.DeleteStateAsync("testStore", "test", stateOptions); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test"); - request.Options.Concurrency.Should().Be(expectedConcurrency); - request.Options.Consistency.Should().Be(expectedConsistency); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test"); + envelope.Options.Concurrency.Should().Be(expectedConcurrency); + envelope.Options.Consistency.Should().Be(expectedConsistency); + } [Theory] @@ -980,11 +940,7 @@ namespace Dapr.Client.Test StateConsistency expectedConsistency, StateConcurrency expectedConcurrency) { - // Configure Client - var httpClient = new TestHttpClient(); - var daprClient = new DaprClientBuilder() - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + await using var client = TestClient.CreateForDaprClient(); var stateOptions = new StateOptions { @@ -992,49 +948,54 @@ namespace Dapr.Client.Test Consistency = consistencyMode }; - var task = daprClient.TryDeleteStateAsync("testStore", "test", "Test_Etag", stateOptions); + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + return await daprClient.TryDeleteStateAsync("testStore", "test", "Test_Etag", stateOptions); + }); + + request.Dismiss(); // Get Request and validate - httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); - request.StoreName.Should().Be("testStore"); - request.Key.Should().Be("test"); - request.Etag.Value.Should().Be("Test_Etag"); - request.Options.Concurrency.Should().Be(expectedConcurrency); - request.Options.Consistency.Should().Be(expectedConsistency); + var envelope = await request.GetRequestEnvelopeAsync(); + envelope.StoreName.Should().Be("testStore"); + envelope.Key.Should().Be("test"); + envelope.Etag.Value.Should().Be("Test_Etag"); + envelope.Options.Concurrency.Should().Be(expectedConcurrency); + envelope.Options.Consistency.Should().Be(expectedConsistency); } - private async Task SendResponseWithState(T state, TestHttpClient.Entry entry, string etag = null) + private Autogenerated.GetStateResponse MakeGetStateResponse(T state, string etag = null) { - var stateDate = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web)); - var stateResponse = new Autogenerated.GetStateResponse + var data = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web)); + var response = new Autogenerated.GetStateResponse { - Data = stateDate + Data = data }; if (etag != null) { - stateResponse.Etag = etag; + response.Etag = etag; } - var streamContent = await GrpcUtils.CreateResponseContent(stateResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); + return response; } - private async Task SendResponseWithBulkState(string key, string state, TestHttpClient.Entry entry) + private Autogenerated.GetBulkStateResponse MakeGetBulkStateResponse(string key, T state) { - var stateDate = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web)); - var bulkResponse = new Autogenerated.GetBulkStateResponse(); - bulkResponse.Items.Add(new Autogenerated.BulkStateItem() + var data = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web)); + var response = new Autogenerated.GetBulkStateResponse { - Key = key, - Data = ByteString.CopyFromUtf8(state) - }); + Items = + { + new Autogenerated.BulkStateItem() + { + Key = key, + Data = data, + } + } + }; - var streamContent = await GrpcUtils.CreateResponseContent(bulkResponse); - var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); - entry.Completion.SetResult(response); + return response; } private class Widget @@ -1045,3 +1006,4 @@ namespace Dapr.Client.Test } } } + diff --git a/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj b/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj index fc64791d..11034521 100644 --- a/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj +++ b/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj @@ -14,8 +14,8 @@ - + diff --git a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs index a73d566f..7b47fc2a 100644 --- a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs +++ b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs @@ -16,6 +16,10 @@ using System.Threading.Tasks; namespace Dapr.Extensions.Configuration.Test { + // These tests use the outdated TestHttpClient infrastructure because they need to + // support testing with synchronous HTTP requests. + // + // Don't copy this pattern elsewhere. public class DaprSecretStoreConfigurationProviderTest { diff --git a/test/Shared/TestHttpClient.cs b/test/Dapr.Extensions.Configuration.Test/TestHttpClient.cs similarity index 97% rename from test/Shared/TestHttpClient.cs rename to test/Dapr.Extensions.Configuration.Test/TestHttpClient.cs index bb7709e0..35f88c8d 100644 --- a/test/Shared/TestHttpClient.cs +++ b/test/Dapr.Extensions.Configuration.Test/TestHttpClient.cs @@ -14,7 +14,7 @@ namespace Dapr using System.Threading; using System.Threading.Tasks; - // This client will capture all requests, and put them in .Requests for you to inspect. + // This is an old piece of infrastructure with some limitations, don't use it in new places. public class TestHttpClient : HttpClient { private readonly TestHttpClientHandler handler; diff --git a/test/Shared/AppCallbackClient.cs b/test/Shared/AppCallbackClient.cs index a3cfabd0..67098c21 100644 --- a/test/Shared/AppCallbackClient.cs +++ b/test/Shared/AppCallbackClient.cs @@ -21,20 +21,15 @@ namespace Dapr public class AppCallbackClient : HttpClient { public AppCallbackClient(AppCallbackBase callbackService) - : this(new TestHttpClientHandler(callbackService)) + : base(new Handler(callbackService)) { } - private AppCallbackClient(TestHttpClientHandler handler) - : base(handler) - { - } - - private class TestHttpClientHandler : HttpMessageHandler + private class Handler : HttpMessageHandler { private readonly AppCallbackBase callbackService; - public TestHttpClientHandler(AppCallbackBase callbackService) + public Handler(AppCallbackBase callbackService) { this.callbackService = callbackService; } diff --git a/test/Shared/TestClient.cs b/test/Shared/TestClient.cs new file mode 100644 index 00000000..a31958e4 --- /dev/null +++ b/test/Shared/TestClient.cs @@ -0,0 +1,420 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +#if ACTORS +using Dapr.Actors; +#endif +using Dapr.Client; +using Google.Protobuf; +using Grpc.Net.Client; + +namespace Dapr +{ + public abstract class TestClient + { + #if ACTORS + internal static TestClient CreateForDaprHttpInterator(string? apiToken = null) + { + var handler = new CapturingHandler(); + return new TestClient(new DaprHttpInteractor(handler, apiToken), handler); + } + #endif + + public static TestClient CreateForMessageHandler() + { + var handler = new CapturingHandler(); + return new TestClient(handler, handler); + } + + public static TestClient CreateForDaprClient(Action? configure = default) + { + var handler = new CapturingHandler(); + var httpClient = new HttpClient(handler); + + var builder = new DaprClientBuilder(); + configure?.Invoke(builder); + + builder.UseHttpClientFactory(() => httpClient); + builder.UseGrpcChannelOptions(new GrpcChannelOptions() + { + HttpClient = httpClient, + ThrowOperationCanceledOnCancellation = true, + }); + + return new TestClient(builder.Build(), handler); + } + + private static async Task WithTimeout(Task task, TimeSpan timeout, string message) + { + var tcs = new TaskCompletionSource(); + + using var cts = new CancellationTokenSource(timeout); + using (cts.Token.Register((obj) => + { + var tcs = (TaskCompletionSource)obj!; + tcs.SetException(new TimeoutException()); + }, tcs)) + { + await (await Task.WhenAny(task, tcs.Task)); + } + } + + private static async Task WithTimeout(Task task, TimeSpan timeout, string message) + { + var tcs = new TaskCompletionSource(); + + using var cts = new CancellationTokenSource(timeout); + using (cts.Token.Register((obj) => + { + var tcs = (TaskCompletionSource)obj!; + tcs.SetException(new TimeoutException()); + }, tcs)) + { + return await (await Task.WhenAny(task, tcs.Task)); + } + } + + public class TestHttpRequest + { + public TestHttpRequest(HttpRequestMessage request, CaptureToken capture, Task task) + { + this.Request = request; + this.Capture = capture; + this.Task = task; + } + + public HttpRequestMessage Request { get; } + + private CaptureToken Capture { get; } + + private Task Task { get; } + + public async Task CompleteAsync(HttpResponseMessage response) + { + this.Capture.Response.SetResult(response); + await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public async Task CompleteWithExceptionAsync(Exception ex) + { + this.Capture.Response.SetException(ex); + await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public void Dismiss() + { + this.Capture.IsDismissed = true; + } + } + + public class TestHttpRequest + { + public TestHttpRequest(HttpRequestMessage request, CaptureToken capture, Task task) + { + this.Request = request; + this.Capture = capture; + this.Task = task; + } + + public HttpRequestMessage Request { get; } + + private CaptureToken Capture { get; } + + private Task Task { get; } + + public async Task CompleteWithJsonAsync(TData value, JsonSerializerOptions options) + { + var bytes = JsonSerializer.SerializeToUtf8Bytes(value, options); + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(bytes) + }; + response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json") { CharSet = "UTF-8", }; + return await CompleteAsync(response); + } + + public async Task CompleteAsync(HttpResponseMessage response) + { + this.Capture.Response.SetResult(response); + return await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public async Task CompleteWithExceptionAsync(Exception ex) + { + this.Capture.Response.SetException(ex); + await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public void Dismiss() + { + this.Capture.IsDismissed = true; + } + } + + public class TestGrpcRequest + { + public TestGrpcRequest(HttpRequestMessage request, CaptureToken capture, Task task) + { + this.Request = request; + this.Capture = capture; + this.Task = task; + } + + public HttpRequestMessage Request { get; } + + private CaptureToken Capture { get; } + + private Task Task { get; } + + public async Task GetRequestEnvelopeAsync() + where TRequestEnvelope : IMessage, new() + { + return await GrpcUtils.GetRequestFromRequestMessageAsync(this.Request); + } + + public async Task CompleteWithMessageAsync(TResponseEnvelope value) + where TResponseEnvelope : IMessage + { + var content = await GrpcUtils.CreateResponseContent(value); + var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, content); + await CompleteAsync(response); + } + + public async Task CompleteAsync(HttpResponseMessage response) + { + this.Capture.Response.SetResult(response); + await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public async Task CompleteWithExceptionAsync(Exception ex) + { + this.Capture.Response.SetException(ex); + await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public void Dismiss() + { + this.Capture.IsDismissed = true; + } + } + + public class TestGrpcRequest + { + public TestGrpcRequest(HttpRequestMessage request, CaptureToken capture, Task task) + { + this.Request = request; + this.Capture = capture; + this.Task = task; + } + + public HttpRequestMessage Request { get; } + + private CaptureToken Capture { get; } + + private Task Task { get; } + + public async Task GetRequestEnvelopeAsync() + where TRequestEnvelope : IMessage, new() + { + return await GrpcUtils.GetRequestFromRequestMessageAsync(this.Request); + } + + public async Task CompleteWithMessageAsync(TResponseEnvelope value) + where TResponseEnvelope : IMessage + { + var content = await GrpcUtils.CreateResponseContent(value); + var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, content); + return await CompleteAsync(response); + } + + public async Task CompleteAsync(HttpResponseMessage response) + { + this.Capture.Response.SetResult(response); + return await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public async Task CompleteWithExceptionAsync(Exception ex) + { + this.Capture.Response.SetException(ex); + await WithTimeout(this.Task, TimeSpan.FromSeconds(10), "Waiting for response to be completed timed out."); + } + + public void Dismiss() + { + this.Capture.IsDismissed = true; + } + } + + public class CapturingHandler : HttpMessageHandler + { + private readonly ConcurrentQueue requests = new ConcurrentQueue(); + private readonly object @lock = new object(); + private CaptureToken? current; + public CaptureToken Capture() + { + lock (this.@lock) + { + if (this.current is CaptureToken) + { + throw new InvalidOperationException( + "Capture operation started while already capturing. " + + "Concurrent use of the test client is not supported."); + } + + return (this.current = new CaptureToken()); + } + } + + public IEnumerable GetOutstandingRequests() + { + foreach (var request in this.requests) + { + if (request.IsComplete) + { + continue; + } + + yield return request; + } + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + CaptureToken? capture; + lock (this.@lock) + { + if ((capture = this.current) is CaptureToken) + { + this.current = default; + } + } + + capture ??= new CaptureToken(); + this.requests.Enqueue(capture); + capture.Request.SetResult(request); + + return capture.Response.Task; + } + } + + public class CaptureToken + { + public TaskCompletionSource Request { get; } = new TaskCompletionSource(); + + public TaskCompletionSource Response { get; } = new TaskCompletionSource(); + + public bool IsDismissed { get; set; } + + public bool IsComplete + { + get + { + return + IsDismissed || + // We assume that whomever started the work observed exceptions making the request. + !Request.Task.IsCompletedSuccessfully || + Response.Task.IsCompleted; + } + } + + public Task GetRequestAsync(TimeSpan timeout) + { + return WithTimeout(Request.Task, timeout, "Waiting for request to be queued timed out."); + } + } + } + + public class TestClient : TestClient, IAsyncDisposable + { + public TestClient(TClient innerClient, CapturingHandler handler) + { + this.InnerClient = innerClient; + this.Handler = handler; + } + + public TClient InnerClient { get; } + + private CapturingHandler Handler { get; } + + public async Task CaptureHttpRequestAsync(Func operation) + { + var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation); + return new TestHttpRequest(request, capture, task); + } + + public async Task> CaptureHttpRequestAsync(Func> operation) + { + var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation); + return new TestHttpRequest(request, capture, (Task)task); + } + + public async Task CaptureGrpcRequestAsync(Func operation) + { + var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation); + return new TestGrpcRequest(request, capture, task); + } + + public async Task> CaptureGrpcRequestAsync(Func> operation) + { + var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation); + return new TestGrpcRequest(request, capture, (Task)task); + } + + private async Task<(HttpRequestMessage, CaptureToken, Task)> CaptureHttpRequestMessageAsync(Func operation) + { + var capture = this.Handler.Capture(); + var task = operation(this.InnerClient); + if (task.IsFaulted) + { + // If the task throws, we want to bubble that up eaglerly. + await task; + } + + HttpRequestMessage request; + try + { + // Apply a 10 second timeout to waiting for the task to be queued. This is a very + // generous timeout so if we hit it then it's likely a bug. + request = await capture.GetRequestAsync(TimeSpan.FromSeconds(10)); + } + + // If the original operation threw, report that instead of the timeout + catch (TimeoutException) when (task.IsFaulted) + { + await task; + throw; // unreachable + } + + return (request, capture, task); + } + + public ValueTask DisposeAsync() + { + (this.InnerClient as IDisposable)?.Dispose(); + + var requests = this.Handler.GetOutstandingRequests().ToArray(); + if (requests.Length > 0) + { + throw new InvalidOperationException( + "The client has 1 or more incomplete requests. " + + "Use 'request.Dismiss()' if the test is uninterested in the response."); + } + + return new ValueTask(); + } + } +}