Fix pattern for tests that use HttpClient (#589)

This change introduces a better API for us to test the DaprClient or
other HttpClient based APIs.

This will resolve the flakiness problems that we're seeing with some of
the actors tests.

Fixes: #573
Fixes: #588

Additionally fixed an issue where DaprHttpInteractor was misuing
HttpClientHandler. This would result in a new handler being created when
it isn't needed.
This commit is contained in:
Ryan Nowak 2021-02-16 10:09:34 -08:00 committed by GitHub
parent 847cd310c2
commit 94fc926170
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1610 additions and 1294 deletions

View File

@ -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
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit
{
}
// This is a polyfill for init only properties in netcoreapp3.1
}

View File

@ -3,11 +3,16 @@
<Import Project="dapr_common.props" /> <Import Project="dapr_common.props" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<LangVersion>9.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject> <WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)\IsExternalInit.cs" />
</ItemGroup>
<!-- Cls Compliant --> <!-- Cls Compliant -->
<PropertyGroup> <PropertyGroup>
<AssemblyClsCompliant>true</AssemblyClsCompliant> <AssemblyClsCompliant>true</AssemblyClsCompliant>

View File

@ -16,7 +16,7 @@ namespace Dapr.Actors.Client
public class ActorProxyFactory : IActorProxyFactory public class ActorProxyFactory : IActorProxyFactory
{ {
private ActorProxyOptions defaultOptions; private ActorProxyOptions defaultOptions;
private readonly HttpClientHandler handler; private readonly HttpMessageHandler handler;
/// <inheritdoc/> /// <inheritdoc/>
public ActorProxyOptions DefaultOptions public ActorProxyOptions DefaultOptions
@ -32,7 +32,16 @@ namespace Dapr.Actors.Client
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ActorProxyFactory"/> class. /// Initializes a new instance of the <see cref="ActorProxyFactory"/> class.
/// </summary> /// </summary>
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)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ActorProxyFactory"/> class.
/// </summary>
public ActorProxyFactory(ActorProxyOptions options = null, HttpMessageHandler handler = null)
{ {
this.defaultOptions = options ?? new ActorProxyOptions(); this.defaultOptions = options ?? new ActorProxyOptions();
this.handler = handler; this.handler = handler;

View File

@ -28,19 +28,20 @@ namespace Dapr.Actors
private readonly JsonSerializerOptions jsonSerializerOptions = JsonSerializerDefaults.Web; private readonly JsonSerializerOptions jsonSerializerOptions = JsonSerializerDefaults.Web;
private const string DaprEndpoint = Constants.DaprDefaultEndpoint; private const string DaprEndpoint = Constants.DaprDefaultEndpoint;
private readonly string daprPort; private readonly string daprPort;
private static HttpClientHandler innerHandler = new HttpClientHandler(); private readonly static HttpMessageHandler defaultHandler = new HttpClientHandler();
private HttpClient httpClient = null; private readonly HttpMessageHandler handler;
private bool disposed = false; private HttpClient httpClient;
private bool disposed;
private string daprApiToken; private string daprApiToken;
public DaprHttpInteractor( public DaprHttpInteractor(
HttpClientHandler clientHandler = null, HttpMessageHandler clientHandler = null,
string apiToken = null) string apiToken = null)
{ {
// Get Dapr port from Environment Variable if it has been overridden. // Get Dapr port from Environment Variable if it has been overridden.
this.daprPort = Environment.GetEnvironmentVariable("DAPR_HTTP_PORT") ?? Constants.DaprDefaultPort; this.daprPort = Environment.GetEnvironmentVariable("DAPR_HTTP_PORT") ?? Constants.DaprDefaultPort;
innerHandler = clientHandler ?? new HttpClientHandler(); this.handler = clientHandler ?? defaultHandler;
this.daprApiToken = apiToken; this.daprApiToken = apiToken;
this.httpClient = this.CreateHttpClient(); this.httpClient = this.CreateHttpClient();
} }
@ -433,7 +434,7 @@ namespace Dapr.Actors
private HttpClient CreateHttpClient() private HttpClient CreateHttpClient()
{ {
return new HttpClient(innerHandler, false); return new HttpClient(this.handler, false);
} }
private void AddDaprApiTokenHeader(HttpRequestMessage request) private void AddDaprApiTokenHeader(HttpRequestMessage request)

View File

@ -48,6 +48,8 @@ namespace Dapr.Client
// property exposed for testing purposes // property exposed for testing purposes
internal string HttpEndpoint { get; private set; } internal string HttpEndpoint { get; private set; }
private Func<HttpClient> HttpClientFactory { get; set; }
// property exposed for testing purposes // property exposed for testing purposes
internal JsonSerializerOptions JsonSerializerOptions { get; private set; } internal JsonSerializerOptions JsonSerializerOptions { get; private set; }
@ -71,6 +73,13 @@ namespace Dapr.Client
return this; return this;
} }
// Internal for testing of DaprClient
internal DaprClientBuilder UseHttpClientFactory(Func<HttpClient> factory)
{
this.HttpClientFactory = factory;
return this;
}
/// <summary> /// <summary>
/// Overrides the gRPC endpoint used by <see cref="DaprClient" /> for communicating with the Dapr runtime. /// Overrides the gRPC endpoint used by <see cref="DaprClient" /> for communicating with the Dapr runtime.
/// </summary> /// </summary>
@ -153,7 +162,8 @@ namespace Dapr.Client
var client = new Autogenerated.Dapr.DaprClient(channel); var client = new Autogenerated.Dapr.DaprClient(channel);
var apiTokenHeader = DaprClient.GetDaprApiTokenHeader(this.DaprApiToken); 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);
} }
} }
} }

View File

@ -3,6 +3,7 @@
// Licensed under the MIT License. // 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.Client.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.AspNetCore.IntegrationTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.AspNetCore.IntegrationTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.AspNetCore.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Dapr.AspNetCore.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]

View File

@ -4,8 +4,6 @@
// ------------------------------------------------------------ // ------------------------------------------------------------
using System; using System;
using System.Collections.Concurrent;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapr.Actors.Client; using Dapr.Actors.Client;
@ -16,107 +14,96 @@ namespace Dapr.Actors.Test
{ {
public class ApiTokenTests public class ApiTokenTests
{ {
[Fact(Skip = "Failing due to #573")] [Fact]
public void CreateProxyWithRemoting_WithApiToken() public async Task CreateProxyWithRemoting_WithApiToken()
{ {
await using var client = TestClient.CreateForMessageHandler();
var actorId = new ActorId("abc"); var actorId = new ActorId("abc");
var handler = new TestHttpClientHandler();
var options = new ActorProxyOptions var options = new ActorProxyOptions
{ {
DaprApiToken = "test_token", DaprApiToken = "test_token",
}; };
var factory = new ActorProxyFactory(options, handler);
var proxy = factory.CreateActorProxy<ITestActor>(actorId, "TestActor");
var task = proxy.SetCountAsync(1, new CancellationToken());
handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var request = await client.CaptureHttpRequestAsync(async handler =>
var headerValues = entry.Request.Headers.GetValues("dapr-api-token"); {
var factory = new ActorProxyFactory(options, handler);
var proxy = factory.CreateActorProxy<ITestActor>(actorId, "TestActor");
await proxy.SetCountAsync(1, new CancellationToken());
});
request.Dismiss();
var headerValues = request.Request.Headers.GetValues("dapr-api-token");
headerValues.Should().Contain("test_token"); headerValues.Should().Contain("test_token");
} }
[Fact(Skip = "Failing due to #573")] [Fact]
public void CreateProxyWithRemoting_WithNoApiToken() public async Task CreateProxyWithRemoting_WithNoApiToken()
{ {
var actorId = new ActorId("abc"); await using var client = TestClient.CreateForMessageHandler();
var handler = new TestHttpClientHandler();
var factory = new ActorProxyFactory(null, handler);
var proxy = factory.CreateActorProxy<ITestActor>(actorId, "TestActor");
var task = proxy.SetCountAsync(1, new CancellationToken());
handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var actorId = new ActorId("abc");
Action action = () => entry.Request.Headers.GetValues("dapr-api-token");
action.Should().Throw<InvalidOperationException>(); var request = await client.CaptureHttpRequestAsync(async handler =>
{
var factory = new ActorProxyFactory(null, handler);
var proxy = factory.CreateActorProxy<ITestActor>(actorId, "TestActor");
await proxy.SetCountAsync(1, new CancellationToken());
});
request.Dismiss();
Assert.Throws<InvalidOperationException>(() =>
{
request.Request.Headers.GetValues("dapr-api-token");
});
} }
[Fact(Skip = "Failing due to #573")] [Fact]
public void CreateProxyWithNoRemoting_WithApiToken() public async Task CreateProxyWithNoRemoting_WithApiToken()
{ {
await using var client = TestClient.CreateForMessageHandler();
var actorId = new ActorId("abc"); var actorId = new ActorId("abc");
var handler = new TestHttpClientHandler();
var options = new ActorProxyOptions var options = new ActorProxyOptions
{ {
DaprApiToken = "test_token", 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 request = await client.CaptureHttpRequestAsync(async handler =>
var headerValues = entry.Request.Headers.GetValues("dapr-api-token"); {
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"); headerValues.Should().Contain("test_token");
} }
[Fact(Skip = "Failing due to #573")] [Fact]
public void CreateProxyWithNoRemoting_WithNoApiToken() public async Task CreateProxyWithNoRemoting_WithNoApiToken()
{ {
await using var client = TestClient.CreateForMessageHandler();
var actorId = new ActorId("abc"); 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(); var request = await client.CaptureHttpRequestAsync(async handler =>
Action action = () => entry.Request.Headers.GetValues("dapr-api-token");
action.Should().Throw<InvalidOperationException>();
}
public class Entry
{
public Entry(HttpRequestMessage request)
{ {
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<HttpResponseMessage>(TaskCreationOptions.RunContinuationsAsynchronously); request.Dismiss();
}
public TaskCompletionSource<HttpResponseMessage> Completion { get; } Assert.Throws<InvalidOperationException>(() =>
public HttpRequestMessage Request { get; }
}
private class TestHttpClientHandler : HttpClientHandler
{
public TestHttpClientHandler()
{ {
this.Requests = new ConcurrentQueue<Entry>(); request.Request.Headers.GetValues("dapr-api-token");
} });
public ConcurrentQueue<Entry> Requests { get; }
public Action<Entry> Handler { get; set; }
protected override async Task<HttpResponseMessage> 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);
}
}
} }
} }
} }

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net5</TargetFrameworks> <TargetFrameworks>netcoreapp3.1;net5</TargetFrameworks>
<RootNamespace>Dapr.Actors</RootNamespace> <RootNamespace>Dapr.Actors</RootNamespace>
<DefineConstants>$(DefineConstants);ACTORS</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -20,6 +21,11 @@
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\GrpcUtils.cs" />
<Compile Include="..\Shared\TestClient.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Dapr.Actors\Dapr.Actors.csproj" /> <ProjectReference Include="..\..\src\Dapr.Actors\Dapr.Actors.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -5,15 +5,13 @@
namespace Dapr.Actors.Test namespace Dapr.Actors.Test
{ {
using System;
using System.Collections.Concurrent;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Security;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text.Json; using System.Text.Json;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
@ -23,224 +21,226 @@ namespace Dapr.Actors.Test
/// </summary> /// </summary>
public class DaprHttpInteractorTest public class DaprHttpInteractorTest
{ {
public class Entry [Fact]
public async Task GetState_ValidateRequest()
{ {
public Entry(HttpRequestMessage request) await using var client = TestClient.CreateForDaprHttpInterator();
{
this.Request = request;
this.Completion = new TaskCompletionSource<HttpResponseMessage>(TaskCreationOptions.RunContinuationsAsynchronously);
}
public TaskCompletionSource<HttpResponseMessage> Completion { get; }
public HttpRequestMessage Request { get; }
}
private class TestHttpClientHandler : HttpClientHandler
{
public TestHttpClientHandler()
{
this.Requests = new ConcurrentQueue<Entry>();
}
public ConcurrentQueue<Entry> Requests { get; }
public Action<Entry> Handler { get; set; }
protected override async Task<HttpResponseMessage> 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 actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var keyName = "StateKey_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(); request.Dismiss();
var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/');
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateKeyRelativeUrlFormat, actorType, actorId, keyName); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateKeyRelativeUrlFormat, actorType, actorId, keyName);
actualPath.Should().Be(expectedPath); actualPath.Should().Be(expectedPath);
entry.Request.Method.Should().Be(HttpMethod.Get); request.Request.Method.Should().Be(HttpMethod.Get);
} }
[Fact] [Fact]
public void SaveStateTransactionally_ValidateRequest() public async Task SaveStateTransactionally_ValidateRequest()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var data = "StateData"; 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(); request.Dismiss();
var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/');
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateRelativeUrlFormat, actorType, actorId); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateRelativeUrlFormat, actorType, actorId);
actualPath.Should().Be(expectedPath); actualPath.Should().Be(expectedPath);
entry.Request.Method.Should().Be(HttpMethod.Put); request.Request.Method.Should().Be(HttpMethod.Put);
} }
[Fact] [Fact]
public void InvokeActorMethodWithoutRemoting_ValidateRequest() public async Task InvokeActorMethodWithoutRemoting_ValidateRequest()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var methodName = "MethodName"; var methodName = "MethodName";
var payload = "JsonData"; 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(); request.Dismiss();
var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/');
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorMethodRelativeUrlFormat, actorType, actorId, methodName); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorMethodRelativeUrlFormat, actorType, actorId, methodName);
actualPath.Should().Be(expectedPath); actualPath.Should().Be(expectedPath);
entry.Request.Method.Should().Be(HttpMethod.Put); request.Request.Method.Should().Be(HttpMethod.Put);
} }
[Fact] [Fact]
public void RegisterReminder_ValidateRequest() public async Task RegisterReminder_ValidateRequest()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var reminderName = "ReminderName"; var reminderName = "ReminderName";
var payload = "JsonData"; 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(); request.Dismiss();
var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/');
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName);
actualPath.Should().Be(expectedPath); actualPath.Should().Be(expectedPath);
entry.Request.Method.Should().Be(HttpMethod.Put); request.Request.Method.Should().Be(HttpMethod.Put);
} }
[Fact] [Fact]
public void UnregisterReminder_ValidateRequest() public async Task UnregisterReminder_ValidateRequest()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var reminderName = "ReminderName"; 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(); request.Dismiss();
var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/');
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName);
actualPath.Should().Be(expectedPath); actualPath.Should().Be(expectedPath);
entry.Request.Method.Should().Be(HttpMethod.Delete); request.Request.Method.Should().Be(HttpMethod.Delete);
} }
[Fact] [Fact]
public void RegisterTimer_ValidateRequest() public async Task RegisterTimer_ValidateRequest()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var timerName = "TimerName"; var timerName = "TimerName";
var payload = "JsonData"; 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(); request.Dismiss();
var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/');
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName);
actualPath.Should().Be(expectedPath); actualPath.Should().Be(expectedPath);
entry.Request.Method.Should().Be(HttpMethod.Put); request.Request.Method.Should().Be(HttpMethod.Put);
} }
[Fact] [Fact]
public void UnregisterTimer_ValidateRequest() public async Task UnregisterTimer_ValidateRequest()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var timerName = "TimerName"; 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(); request.Dismiss();
var actualPath = entry.Request.RequestUri.LocalPath.TrimStart('/');
var actualPath = request.Request.RequestUri.LocalPath.TrimStart('/');
var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName); var expectedPath = string.Format(CultureInfo.InvariantCulture, Constants.ActorTimerRelativeUrlFormat, actorType, actorId, timerName);
actualPath.Should().Be(expectedPath); actualPath.Should().Be(expectedPath);
entry.Request.Method.Should().Be(HttpMethod.Delete); request.Request.Method.Should().Be(HttpMethod.Delete);
} }
[Fact] [Fact]
public void Call_WithApiTokenSet() public async Task Call_WithApiTokenSet()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator(apiToken: "test_token");
var httpInteractor = new DaprHttpInteractor(handler, apiToken: "test_token");
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var timerName = "TimerName"; 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(); request.Dismiss();
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.Count().Should().Be(1);
headerValues.First().Should().Be("test_token"); headerValues.First().Should().Be("test_token");
} }
[Fact] [Fact]
public void Call_WithoutApiToken() public async Task Call_WithoutApiToken()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var timerName = "TimerName"; 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(); request.Dismiss();
entry.Request.Headers.TryGetValues("dapr-api-token", out var headerValues);
request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues);
headerValues.Should().BeNull(); headerValues.Should().BeNull();
} }
[Fact] [Fact]
public async Task Call_ValidateUnsuccessfulResponse() public async Task Call_ValidateUnsuccessfulResponse()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var timerName = "TimerName"; var timerName = "TimerName";
var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
handler.Requests.TryDequeue(out var entry).Should().BeTrue(); {
await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName);
});
request.Dismiss();
var error = new DaprError() var error = new DaprError()
{ {
@ -253,43 +253,54 @@ namespace Dapr.Actors.Test
Content = new StringContent(JsonSerializer.Serialize(error)) Content = new StringContent(JsonSerializer.Serialize(error))
}; };
entry.Completion.SetResult(message); await Assert.ThrowsAsync<DaprApiException>(async () =>
await FluentActions.Awaiting(async () => await task).Should().ThrowAsync<DaprApiException>(); {
await request.CompleteAsync(message);
});
} }
[Fact] [Fact]
public async Task Call_ValidateUnsuccessful404Response() public async Task Call_ValidateUnsuccessful404Response()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var timerName = "TimerName"; var timerName = "TimerName";
var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
handler.Requests.TryDequeue(out var entry).Should().BeTrue(); {
await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName);
});
var message = new HttpResponseMessage(HttpStatusCode.NotFound); var message = new HttpResponseMessage(HttpStatusCode.NotFound);
entry.Completion.SetResult(message); await Assert.ThrowsAsync<DaprApiException>(async () =>
await FluentActions.Awaiting(async () => await task).Should().ThrowAsync<DaprApiException>(); {
await request.CompleteAsync(message);
});
} }
[Fact] [Fact]
public async Task Call_ValidateUnauthorizedResponse() public async Task Call_ValidateUnauthorizedResponse()
{ {
var handler = new TestHttpClientHandler(); await using var client = TestClient.CreateForDaprHttpInterator();
var httpInteractor = new DaprHttpInteractor(handler);
var actorType = "ActorType_Test"; var actorType = "ActorType_Test";
var actorId = "ActorId_Test"; var actorId = "ActorId_Test";
var timerName = "TimerName"; var timerName = "TimerName";
var task = httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName); var request = await client.CaptureHttpRequestAsync(async httpInteractor =>
handler.Requests.TryDequeue(out var entry).Should().BeTrue(); {
await httpInteractor.UnregisterTimerAsync(actorType, actorId, timerName);
});
var message = new HttpResponseMessage(HttpStatusCode.Unauthorized); var message = new HttpResponseMessage(HttpStatusCode.Unauthorized);
entry.Completion.SetResult(message);
await FluentActions.Awaiting(async () => await task).Should().ThrowAsync<AuthenticationException>(); await Assert.ThrowsAsync<AuthenticationException>(async () =>
{
await request.CompleteAsync(message);
});
} }
} }
} }

View File

@ -23,8 +23,4 @@
<ProjectReference Include="..\Dapr.AspNetCore.IntegrationTest.App\Dapr.AspNetCore.IntegrationTest.App.csproj" /> <ProjectReference Include="..\Dapr.AspNetCore.IntegrationTest.App\Dapr.AspNetCore.IntegrationTest.App.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\TestHttpClient.cs" />
</ItemGroup>
</Project> </Project>

View File

@ -18,8 +18,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Shared\TestHttpClient.cs" />
<Compile Include="..\Shared\GrpcUtils.cs" /> <Compile Include="..\Shared\GrpcUtils.cs" />
<Compile Include="..\Shared\TestClient.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -37,9 +37,8 @@ namespace Dapr.AspNetCore.Test
[Fact] [Fact]
public void DaprClientBuilder_DoesNotOverrideUserGrpcChannelOptions() public void DaprClientBuilder_DoesNotOverrideUserGrpcChannelOptions()
{ {
var httpClient = new TestHttpClient();
var builder = new DaprClientBuilder(); 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); Assert.False(builder.GrpcChannelOptions.ThrowOperationCanceledOnCancellation);
} }

View File

@ -6,13 +6,11 @@
namespace Dapr.AspNetCore.Test namespace Dapr.AspNetCore.Test
{ {
using System; using System;
using System.Net;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapr.Client; using Dapr.Client;
using Dapr.Client.Autogen.Grpc.v1; using Dapr.Client.Autogen.Grpc.v1;
using FluentAssertions; using FluentAssertions;
using Grpc.Net.Client;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -25,37 +23,41 @@ namespace Dapr.AspNetCore.Test
[Fact] [Fact]
public async Task BindAsync_WithoutMatchingRouteValue_ReportsError() 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 binder = new StateEntryModelBinder("testStore", "test", isStateEntry: false, typeof(Widget));
var context = CreateContext(CreateServices(httpClient)); var context = CreateContext(CreateServices(client.InnerClient));
await binder.BindModelAsync(context); await binder.BindModelAsync(context);
context.Result.IsModelSet.Should().BeFalse(); context.Result.IsModelSet.Should().BeFalse();
context.ModelState.ErrorCount.Should().Be(1); context.ModelState.ErrorCount.Should().Be(1);
context.ModelState["testParameter"].Errors.Count.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] [Fact]
public async Task BindAsync_CanBindValue() public async Task BindAsync_CanBindValue()
{ {
await using var client = TestClient.CreateForDaprClient();
var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: false, typeof(Widget)); var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: false, typeof(Widget));
// Configure Client // Configure Client
var httpClient = new TestHttpClient(); var context = CreateContext(CreateServices(client.InnerClient));
var context = CreateContext(CreateServices(httpClient));
context.HttpContext.Request.RouteValues["id"] = "test"; context.HttpContext.Request.RouteValues["id"] = "test";
var task = binder.BindModelAsync(context);
var request = await client.CaptureGrpcRequestAsync(async _ =>
{
await binder.BindModelAsync(context);
});
// Create Response & Respond // Create Response & Respond
var state = new Widget() { Size = "small", Color = "yellow", }; var state = new Widget() { Size = "small", Color = "yellow", };
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); await SendResponseWithState(state, request);
await SendResponseWithState(state, entry);
// Get response and validate // Get response and validate
await task;
context.Result.IsModelSet.Should().BeTrue(); context.Result.IsModelSet.Should().BeTrue();
context.Result.Model.As<Widget>().Size.Should().Be("small"); context.Result.Model.As<Widget>().Size.Should().Be("small");
context.Result.Model.As<Widget>().Color.Should().Be("yellow"); context.Result.Model.As<Widget>().Color.Should().Be("yellow");
@ -67,21 +69,24 @@ namespace Dapr.AspNetCore.Test
[Fact] [Fact]
public async Task BindAsync_CanBindStateEntry() public async Task BindAsync_CanBindStateEntry()
{ {
await using var client = TestClient.CreateForDaprClient();
var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: true, typeof(Widget)); var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: true, typeof(Widget));
// Configure Client // Configure Client
var httpClient = new TestHttpClient(); var context = CreateContext(CreateServices(client.InnerClient));
var context = CreateContext(CreateServices(httpClient));
context.HttpContext.Request.RouteValues["id"] = "test"; context.HttpContext.Request.RouteValues["id"] = "test";
var task = binder.BindModelAsync(context);
var request = await client.CaptureGrpcRequestAsync(async _ =>
{
await binder.BindModelAsync(context);
});
// Create Response & Respond // Create Response & Respond
var state = new Widget() { Size = "small", Color = "yellow", }; var state = new Widget() { Size = "small", Color = "yellow", };
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); await SendResponseWithState(state, request);
await SendResponseWithState(state, entry);
// Get response and validate // Get response and validate
await task;
context.Result.IsModelSet.Should().BeTrue(); context.Result.IsModelSet.Should().BeTrue();
context.Result.Model.As<StateEntry<Widget>>().Key.Should().Be("test"); context.Result.Model.As<StateEntry<Widget>>().Key.Should().Be("test");
context.Result.Model.As<StateEntry<Widget>>().Value.Size.Should().Be("small"); context.Result.Model.As<StateEntry<Widget>>().Value.Size.Should().Be("small");
@ -94,18 +99,21 @@ namespace Dapr.AspNetCore.Test
[Fact] [Fact]
public async Task BindAsync_ReturnsNullForNonExistentStateEntry() public async Task BindAsync_ReturnsNullForNonExistentStateEntry()
{ {
await using var client = TestClient.CreateForDaprClient();
var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: false, typeof(Widget)); var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: false, typeof(Widget));
// Configure Client // Configure Client
var httpClient = new TestHttpClient(); var context = CreateContext(CreateServices(client.InnerClient));
var context = CreateContext(CreateServices(httpClient));
context.HttpContext.Request.RouteValues["id"] = "test"; context.HttpContext.Request.RouteValues["id"] = "test";
var task = binder.BindModelAsync(context);
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var request = await client.CaptureGrpcRequestAsync(async _ =>
await SendResponseWithState<string>(null, entry); {
await binder.BindModelAsync(context);
});
await SendResponseWithState<string>(null, request);
await task;
context.ModelState.IsValid.Should().BeTrue(); context.ModelState.IsValid.Should().BeTrue();
context.Result.IsModelSet.Should().BeFalse(); context.Result.IsModelSet.Should().BeFalse();
context.Result.Should().Be(ModelBindingResult.Failed()); context.Result.Should().Be(ModelBindingResult.Failed());
@ -114,18 +122,21 @@ namespace Dapr.AspNetCore.Test
[Fact] [Fact]
public async Task BindAsync_WithStateEntry_ForNonExistentStateEntry() public async Task BindAsync_WithStateEntry_ForNonExistentStateEntry()
{ {
await using var client = TestClient.CreateForDaprClient();
var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: true, typeof(Widget)); var binder = new StateEntryModelBinder("testStore", "id", isStateEntry: true, typeof(Widget));
// Configure Client // Configure Client
var httpClient = new TestHttpClient(); var context = CreateContext(CreateServices(client.InnerClient));
var context = CreateContext(CreateServices(httpClient));
context.HttpContext.Request.RouteValues["id"] = "test"; context.HttpContext.Request.RouteValues["id"] = "test";
var task = binder.BindModelAsync(context);
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var request = await client.CaptureGrpcRequestAsync(async _ =>
await SendResponseWithState<string>(null, entry); {
await binder.BindModelAsync(context);
});
await SendResponseWithState<string>(null, request);
await task;
context.ModelState.IsValid.Should().BeTrue(); context.ModelState.IsValid.Should().BeTrue();
context.Result.IsModelSet.Should().BeTrue(); context.Result.IsModelSet.Should().BeTrue();
((StateEntry<Widget>)context.Result.Model).Value.Should().BeNull(); ((StateEntry<Widget>)context.Result.Model).Value.Should().BeNull();
@ -148,27 +159,21 @@ namespace Dapr.AspNetCore.Test
}; };
} }
private async Task SendResponseWithState<T>(T state, TestHttpClient.Entry entry) private async Task SendResponseWithState<T>(T state, TestClient<DaprClient>.TestGrpcRequest request)
{ {
var stateData = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web)); var stateData = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web));
var stateResponse = new GetStateResponse var stateResponse = new GetStateResponse()
{ {
Data = stateData, Data = stateData,
Etag = "test", Etag = "test",
}; };
var streamContent = await GrpcUtils.CreateResponseContent(stateResponse); await request.CompleteWithMessageAsync(stateResponse);
var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent);
entry.Completion.SetResult(response);
} }
private static IServiceProvider CreateServices(TestHttpClient httpClient) private static IServiceProvider CreateServices(DaprClient daprClient)
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
services.AddSingleton(daprClient); services.AddSingleton(daprClient);
return services.BuildServiceProvider(); return services.BuildServiceProvider();
} }

View File

@ -30,7 +30,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="..\Shared\AppCallbackClient.cs" /> <Compile Include="..\Shared\AppCallbackClient.cs" />
<Compile Include="..\Shared\TestHttpClient.cs" /> <Compile Include="..\Shared\TestClient.cs" />
<Compile Include="..\Shared\GrpcUtils.cs" /> <Compile Include="..\Shared\GrpcUtils.cs" />
</ItemGroup> </ItemGroup>

View File

@ -5,12 +5,9 @@
namespace Dapr.Client.Test namespace Dapr.Client.Test
{ {
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Grpc.Net.Client;
using Xunit; using Xunit;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1; using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
@ -20,19 +17,19 @@ namespace Dapr.Client.Test
public async Task DaprCall_WithApiTokenSet() public async Task DaprCall_WithApiTokenSet()
{ {
// Configure Client // Configure Client
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient(c => c.UseDaprApiToken("test_token"));
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.UseDaprApiToken("test_token")
.Build();
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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetSecretRequest>(entry.Request);
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.Count().Should().Be(1);
headerValues.First().Should().Be("test_token"); headerValues.First().Should().Be("test_token");
} }
@ -41,18 +38,19 @@ namespace Dapr.Client.Test
public async Task DaprCall_WithoutApiToken() public async Task DaprCall_WithoutApiToken()
{ {
// Configure Client // Configure Client
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetSecretRequest>(entry.Request);
entry.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); request.Request.Headers.TryGetValues("dapr-api-token", out var headerValues);
headerValues.Should().BeNull(); headerValues.Should().BeNull();
} }
} }

View File

@ -13,14 +13,10 @@ namespace Dapr.Client.Test
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapr.Client; using Dapr.Client;
using FluentAssertions;
using Grpc.Core;
using Grpc.Net.Client;
using Moq;
using Xunit; using Xunit;
// Most of the InvokeMethodAsync functionality on DaprClient is non-abstract methods that // 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. // its result.
// //
// So we write basic tests for all of those that every parameter passing is correct, and then // 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] [Fact]
public async Task InvokeMethodAsync_VoidVoidNoHttpMethod_Success() public async Task InvokeMethodAsync_VoidVoidNoHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var task = client.InvokeMethodAsync("app1", "mymethod"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
{
await daprClient.InvokeMethodAsync("app1", "mymethod");
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(request.Request.Method, HttpMethod.Post);
Assert.Equal(entry.Request.Method, HttpMethod.Post); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); Assert.Null(request.Request.Content);
Assert.Null(entry.Request.Content);
entry.Respond(new HttpResponseMessage()); await request.CompleteAsync(new HttpResponseMessage());
await task;
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_VoidVoidWithHttpMethod_Success() public async Task InvokeMethodAsync_VoidVoidWithHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(request.Request.Method, HttpMethod.Put);
Assert.Equal(entry.Request.Method, HttpMethod.Put); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); Assert.Null(request.Request.Content);
Assert.Null(entry.Request.Content);
entry.Respond(new HttpResponseMessage()); await request.CompleteAsync(new HttpResponseMessage());
await task;
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_VoidResponseNoHttpMethod_Success() public async Task InvokeMethodAsync_VoidResponseNoHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var task = client.InvokeMethodAsync<Widget>("app1", "mymethod"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
{
return await daprClient.InvokeMethodAsync<Widget>("app1", "mymethod");
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(request.Request.Method, HttpMethod.Post);
Assert.Equal(entry.Request.Method, HttpMethod.Post); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri); Assert.Null(request.Request.Content);
Assert.Null(entry.Request.Content);
var expected = new Widget() var expected = new Widget()
{ {
Color = "red", Color = "red",
}; };
entry.RespondWithJson(expected, jsonSerializerOptions); var actual = await request.CompleteWithJsonAsync(expected, jsonSerializerOptions);
var actual = await task;
Assert.Equal(expected.Color, actual.Color); Assert.Equal(expected.Color, actual.Color);
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_VoidResponseWithHttpMethod_Success() public async Task InvokeMethodAsync_VoidResponseWithHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var task = client.InvokeMethodAsync<Widget>(HttpMethod.Put, "app1", "mymethod"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
{
return await daprClient.InvokeMethodAsync<Widget>(HttpMethod.Put, "app1", "mymethod");
});
// Get Request and validate Assert.Equal(request.Request.Method, HttpMethod.Put);
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(entry.Request.Method, HttpMethod.Put); Assert.Null(request.Request.Content);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri);
Assert.Null(entry.Request.Content);
var expected = new Widget() var expected = new Widget()
{ {
Color = "red", Color = "red",
}; };
entry.RespondWithJson(expected, jsonSerializerOptions); var actual = await request.CompleteWithJsonAsync(expected, jsonSerializerOptions);
var actual = await task;
Assert.Equal(expected.Color, actual.Color); Assert.Equal(expected.Color, actual.Color);
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_RequestVoidNoHttpMethod_Success() public async Task InvokeMethodAsync_RequestVoidNoHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var data = new Widget() var data = new Widget()
{ {
Color = "red", Color = "red",
}; };
var task = client.InvokeMethodAsync<Widget>("app1", "mymethod", data); var request = await client.CaptureHttpRequestAsync(async daprClient =>
{
await daprClient.InvokeMethodAsync<Widget>("app1", "mymethod", data);
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(request.Request.Method, HttpMethod.Post);
Assert.Equal(entry.Request.Method, HttpMethod.Post); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri);
var content = Assert.IsType<JsonContent>(entry.Request.Content); var content = Assert.IsType<JsonContent>(request.Request.Content);
Assert.Equal(data.GetType(), content.ObjectType); Assert.Equal(data.GetType(), content.ObjectType);
Assert.Same(data, content.Value); Assert.Same(data, content.Value);
entry.Respond(new HttpResponseMessage()); await request.CompleteAsync(new HttpResponseMessage());
await task;
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_RequestVoidWithHttpMethod_Success() public async Task InvokeMethodAsync_RequestVoidWithHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var data = new Widget() var data = new Widget()
{ {
Color = "red", Color = "red",
}; };
var task = client.InvokeMethodAsync<Widget>(HttpMethod.Put, "app1", "mymethod", data); var request = await client.CaptureHttpRequestAsync(async daprClient =>
{
await daprClient.InvokeMethodAsync<Widget>(HttpMethod.Put, "app1", "mymethod", data);
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(request.Request.Method, HttpMethod.Put);
Assert.Equal(entry.Request.Method, HttpMethod.Put); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri);
var content = Assert.IsType<JsonContent>(entry.Request.Content); var content = Assert.IsType<JsonContent>(request.Request.Content);
Assert.Equal(data.GetType(), content.ObjectType); Assert.Equal(data.GetType(), content.ObjectType);
Assert.Same(data, content.Value); Assert.Same(data, content.Value);
entry.Respond(new HttpResponseMessage()); await request.CompleteAsync(new HttpResponseMessage());
await task;
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_RequestResponseNoHttpMethod_Success() public async Task InvokeMethodAsync_RequestResponseNoHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var data = new Widget() var data = new Widget()
{ {
Color = "red", Color = "red",
}; };
var task = client.InvokeMethodAsync<Widget, Widget>("app1", "mymethod", data); var request = await client.CaptureHttpRequestAsync(async daprClient =>
{
return await daprClient.InvokeMethodAsync<Widget, Widget>("app1", "mymethod", data);
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(request.Request.Method, HttpMethod.Post);
Assert.Equal(entry.Request.Method, HttpMethod.Post); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri);
var content = Assert.IsType<JsonContent>(entry.Request.Content); var content = Assert.IsType<JsonContent>(request.Request.Content);
Assert.Equal(data.GetType(), content.ObjectType); Assert.Equal(data.GetType(), content.ObjectType);
Assert.Same(data, content.Value); Assert.Same(data, content.Value);
entry.RespondWithJson(data, jsonSerializerOptions); var actual = await request.CompleteWithJsonAsync(data, jsonSerializerOptions);
var actual = await task;
Assert.Equal(data.Color, actual.Color); Assert.Equal(data.Color, actual.Color);
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_RequestResponseWithHttpMethod_Success() public async Task InvokeMethodAsync_RequestResponseWithHttpMethod_Success()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var data = new Widget() var data = new Widget()
{ {
Color = "red", Color = "red",
}; };
var task = client.InvokeMethodAsync<Widget, Widget>(HttpMethod.Put, "app1", "mymethod", data); var request = await client.CaptureHttpRequestAsync(async daprClient =>
{
return await daprClient.InvokeMethodAsync<Widget, Widget>(HttpMethod.Put, "app1", "mymethod", data);
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); Assert.Equal(request.Request.Method, HttpMethod.Put);
Assert.Equal(entry.Request.Method, HttpMethod.Put); Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/invoke/app1/method/mymethod").AbsoluteUri, entry.Request.RequestUri.AbsoluteUri);
var content = Assert.IsType<JsonContent>(entry.Request.Content); var content = Assert.IsType<JsonContent>(request.Request.Content);
Assert.Equal(data.GetType(), content.ObjectType); Assert.Equal(data.GetType(), content.ObjectType);
Assert.Same(data, content.Value); Assert.Same(data, content.Value);
entry.RespondWithJson(data, jsonSerializerOptions); var actual = await request.CompleteWithJsonAsync(data, jsonSerializerOptions);
var actual = await task;
Assert.Equal(data.Color, actual.Color); Assert.Equal(data.Color, actual.Color);
} }
[Fact] [Fact]
public async Task InvokeMethodAsync_WrapsHttpRequestException() public async Task InvokeMethodAsync_WrapsHttpRequestException()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", "test"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
var task = client.InvokeMethodAsync(request); {
var request = daprClient.CreateInvokeMethodRequest("test-app", "test");
await daprClient.InvokeMethodAsync(request);
});
var exception = new HttpRequestException(); var exception = new HttpRequestException();
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await request.CompleteWithExceptionAsync(exception));
entry.Throw(exception);
var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await task);
Assert.Equal("test-app", thrown.AppId); Assert.Equal("test-app", thrown.AppId);
Assert.Equal("test", thrown.MethodName); Assert.Equal("test", thrown.MethodName);
Assert.Same(exception, thrown.InnerException); Assert.Same(exception, thrown.InnerException);
@ -310,24 +268,19 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodAsync_WrapsHttpRequestException_FromEnsureSuccessStatus() public async Task InvokeMethodAsync_WrapsHttpRequestException_FromEnsureSuccessStatus()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", "test"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
var task = client.InvokeMethodAsync(request); {
var request = daprClient.CreateInvokeMethodRequest("test-app", "test");
await daprClient.InvokeMethodAsync(request);
});
var response = new HttpResponseMessage(HttpStatusCode.NotFound); var response = new HttpResponseMessage(HttpStatusCode.NotFound);
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await request.CompleteAsync(response));
entry.Respond(response);
var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await task);
Assert.Equal("test-app", thrown.AppId); Assert.Equal("test-app", thrown.AppId);
Assert.Equal("test", thrown.MethodName); Assert.Equal("test", thrown.MethodName);
Assert.IsType<HttpRequestException>(thrown.InnerException); Assert.IsType<HttpRequestException>(thrown.InnerException);
@ -337,24 +290,19 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodAsync_WithBody_WrapsHttpRequestException() public async Task InvokeMethodAsync_WithBody_WrapsHttpRequestException()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", "test"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
var task = client.InvokeMethodAsync<Widget>(request); {
var request = daprClient.CreateInvokeMethodRequest("test-app", "test");
return await daprClient.InvokeMethodAsync<Widget>(request);
});
var exception = new HttpRequestException(); var exception = new HttpRequestException();
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await request.CompleteWithExceptionAsync(exception));
entry.Throw(exception);
var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await task);
Assert.Equal("test-app", thrown.AppId); Assert.Equal("test-app", thrown.AppId);
Assert.Equal("test", thrown.MethodName); Assert.Equal("test", thrown.MethodName);
Assert.Same(exception, thrown.InnerException); Assert.Same(exception, thrown.InnerException);
@ -364,24 +312,21 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodAsync_WithBody_WrapsHttpRequestException_FromEnsureSuccessStatus() public async Task InvokeMethodAsync_WithBody_WrapsHttpRequestException_FromEnsureSuccessStatus()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", "test"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
var task = client.InvokeMethodAsync<Widget>(request); {
var request = daprClient.CreateInvokeMethodRequest("test-app", "test");
return await daprClient.InvokeMethodAsync<Widget>(request);
});
var response = new HttpResponseMessage(HttpStatusCode.NotFound); var response = new HttpResponseMessage(HttpStatusCode.NotFound);
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await request.CompleteAsync(response));
entry.Respond(response);
var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await task);
Assert.Equal("test-app", thrown.AppId); Assert.Equal("test-app", thrown.AppId);
Assert.Equal("test", thrown.MethodName); Assert.Equal("test", thrown.MethodName);
Assert.IsType<HttpRequestException>(thrown.InnerException); Assert.IsType<HttpRequestException>(thrown.InnerException);
@ -391,28 +336,22 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodAsync_WrapsHttpRequestException_FromSerialization() public async Task InvokeMethodAsync_WrapsHttpRequestException_FromSerialization()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", "test"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
var task = client.InvokeMethodAsync<Widget>(request); {
var request = daprClient.CreateInvokeMethodRequest("test-app", "test");
return await daprClient.InvokeMethodAsync<Widget>(request);
});
var response = new HttpResponseMessage(HttpStatusCode.OK) var response = new HttpResponseMessage(HttpStatusCode.OK)
{ {
Content = new StringContent("{ \"invalid\": true", Encoding.UTF8, "application/json") Content = new StringContent("{ \"invalid\": true", Encoding.UTF8, "application/json")
}; };
var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await request.CompleteAsync(response));
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue();
entry.Respond(response);
var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await task);
Assert.Equal("test-app", thrown.AppId); Assert.Equal("test-app", thrown.AppId);
Assert.Equal("test", thrown.MethodName); Assert.Equal("test", thrown.MethodName);
Assert.IsType<JsonException>(thrown.InnerException); Assert.IsType<JsonException>(thrown.InnerException);
@ -428,41 +367,31 @@ namespace Dapr.Client.Test
// garbage in -> garbage out - we don't deeply inspect what you pass. // 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")] [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 await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", method); var request = client.InnerClient.CreateInvokeMethodRequest("test-app", method);
Assert.Equal(new Uri(expected).AbsoluteUri, request.RequestUri.AbsoluteUri); Assert.Equal(new Uri(expected).AbsoluteUri, request.RequestUri.AbsoluteUri);
} }
[Fact] [Fact]
public async Task CreateInvokeMethodRequest_WithData_CreatesJsonContent() public async Task CreateInvokeMethodRequest_WithData_CreatesJsonContent()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var data = new Widget var data = new Widget
{ {
Color = "red", Color = "red",
}; };
var request = client.CreateInvokeMethodRequest("test-app", "test", data); var request = client.InnerClient.CreateInvokeMethodRequest("test-app", "test", data);
var content = Assert.IsType<JsonContent>(request.Content); var content = Assert.IsType<JsonContent>(request.Content);
Assert.Equal(typeof(Widget), content.ObjectType); Assert.Equal(typeof(Widget), content.ObjectType);
Assert.Same(data, content.Value); Assert.Same(data, content.Value);
@ -475,46 +404,37 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodWithResponseAsync_ReturnsMessageWithoutCheckingStatus() public async Task InvokeMethodWithResponseAsync_ReturnsMessageWithoutCheckingStatus()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", "test"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
var task = client.InvokeMethodWithResponseAsync(request); {
var request = daprClient.CreateInvokeMethodRequest("test-app", "test");
return await daprClient.InvokeMethodWithResponseAsync(request);
});
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var response = await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); // Non-2xx response
entry.Respond(new HttpResponseMessage(HttpStatusCode.BadRequest)); // Non-2xx response Assert.NotNull(response);
var response = await task;
} }
[Fact] [Fact]
public async Task InvokeMethodWithResponseAsync_WrapsHttpRequestException() public async Task InvokeMethodWithResponseAsync_WrapsHttpRequestException()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = client.CreateInvokeMethodRequest("test-app", "test"); var request = await client.CaptureHttpRequestAsync(async daprClient =>
var task = client.InvokeMethodWithResponseAsync(request); {
var request = daprClient.CreateInvokeMethodRequest("test-app", "test");
return await daprClient.InvokeMethodWithResponseAsync(request);
});
var exception = new HttpRequestException(); var exception = new HttpRequestException();
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await request.CompleteWithExceptionAsync(exception));
entry.Throw(exception);
var thrown = await Assert.ThrowsAsync<InvocationException>(async () => await task);
Assert.Equal("test-app", thrown.AppId); Assert.Equal("test-app", thrown.AppId);
Assert.Equal("test", thrown.MethodName); Assert.Equal("test", thrown.MethodName);
Assert.Same(exception, thrown.InnerException); Assert.Same(exception, thrown.InnerException);
@ -524,20 +444,15 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodWithResponseAsync_PreventsNonDaprRequest() public async Task InvokeMethodWithResponseAsync_PreventsNonDaprRequest()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var client = new DaprClientGrpc( c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
GrpcChannel.ForAddress("http://localhost"), });
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
jsonSerializerOptions,
default);
var request = new HttpRequestMessage(HttpMethod.Get, "https://example.com"); var request = new HttpRequestMessage(HttpMethod.Get, "https://example.com");
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => var ex = await Assert.ThrowsAsync<InvalidOperationException>(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); Assert.Equal("The provided request URI is not a Dapr service invocation URI.", ex.Message);

View File

@ -4,15 +4,11 @@
// ------------------------------------------------------------ // ------------------------------------------------------------
using System; using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapr.Client.Autogen.Grpc.v1; using Dapr.Client.Autogen.Grpc.v1;
using Dapr.Client.Autogen.Test.Grpc.v1; using Dapr.Client.Autogen.Test.Grpc.v1;
using FluentAssertions; using FluentAssertions;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using Grpc.Core; using Grpc.Core;
using Grpc.Net.Client; using Grpc.Net.Client;
@ -26,57 +22,51 @@ namespace Dapr.Client.Test
{ {
public partial class DaprClientTest public partial class DaprClientTest
{ {
private DaprClient CreateTestClientGrpc(HttpClient httpClient)
{
return new DaprClientBuilder()
.UseJsonSerializationOptions(this.jsonSerializerOptions)
.UseGrpcChannelOptions(new GrpcChannelOptions
{
HttpClient = httpClient,
ThrowOperationCanceledOnCancellation = true,
})
.Build();
}
[Fact] [Fact]
public async Task InvokeMethodGrpcAsync_WithCancelledToken() public async Task InvokeMethodGrpcAsync_WithCancelledToken()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient();
var daprClient = CreateTestClientGrpc(httpClient);
var ctSource = new CancellationTokenSource();
CancellationToken ct = ctSource.Token;
ctSource.Cancel();
await FluentActions.Awaiting(async () =>
{ {
await daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", new Request() { RequestParameter = "Hello " }, cancellationToken: ct); c.UseJsonSerializationOptions(this.jsonSerializerOptions);
}).Should().ThrowAsync<OperationCanceledException>(); });
var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await client.InnerClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", new Request() { RequestParameter = "Hello " }, cancellationToken: cts.Token);
});
} }
[Fact] [Fact]
public async Task InvokeMethodGrpcAsync_CanInvokeMethodWithReturnTypeAndData() public async Task InvokeMethodGrpcAsync_CanInvokeMethodWithReturnTypeAndData()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var daprClient = CreateTestClientGrpc(httpClient); c.UseJsonSerializationOptions(this.jsonSerializerOptions);
});
var task = daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", new Request() { RequestParameter = "Hello " }); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", new Request() { RequestParameter = "Hello " });
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeServiceRequest>(entry.Request);
envelope.Id.Should().Be("test"); envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test"); envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc); envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc);
// Create Response & Respond // Create Response & Respond
var data = new Response() { Name = "Look, I was invoked!" }; 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 // Validate Response
var invokedResponse = await task; var invokedResponse = await request.CompleteWithMessageAsync(response);
invokedResponse.Name.Should().Be("Look, I was invoked!"); invokedResponse.Name.Should().Be("Look, I was invoked!");
} }
@ -117,25 +107,31 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodGrpcAsync_CanInvokeMethodWithReturnTypeNoData() public async Task InvokeMethodGrpcAsync_CanInvokeMethodWithReturnTypeNoData()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var daprClient = CreateTestClientGrpc(httpClient); c.UseJsonSerializationOptions(this.jsonSerializerOptions);
});
var task = daprClient.InvokeMethodGrpcAsync<Response>("test", "test"); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.InvokeMethodGrpcAsync<Response>("test", "test");
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeServiceRequest>(entry.Request);
envelope.Id.Should().Be("test"); envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test"); envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(string.Empty); envelope.Message.ContentType.Should().Be(string.Empty);
// Create Response & Respond // Create Response & Respond
var data = new Response() { Name = "Look, I was invoked!" }; 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 // Validate Response
var invokedResponse = await task; var invokedResponse = await request.CompleteWithMessageAsync(response);
invokedResponse.Name.Should().Be("Look, I was invoked!"); invokedResponse.Name.Should().Be("Look, I was invoked!");
} }
@ -234,16 +230,21 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodGrpcAsync_WithNoReturnTypeAndData() public async Task InvokeMethodGrpcAsync_WithNoReturnTypeAndData()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var daprClient = CreateTestClientGrpc(httpClient); c.UseJsonSerializationOptions(this.jsonSerializerOptions);
});
var invokeRequest = new Request() { RequestParameter = "Hello" }; var invokeRequest = new Request() { RequestParameter = "Hello" };
var task = daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", invokeRequest); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", invokeRequest);
});
request.Dismiss();
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeServiceRequest>(entry.Request);
envelope.Id.Should().Be("test"); envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test"); envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc); envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc);
@ -255,18 +256,20 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeMethodGrpcAsync_WithReturnTypeAndData() public async Task InvokeMethodGrpcAsync_WithReturnTypeAndData()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient(c =>
var httpClient = new TestHttpClient(); {
var daprClient = CreateTestClientGrpc(httpClient); c.UseJsonSerializationOptions(this.jsonSerializerOptions);
});
var invokeRequest = new Request() { RequestParameter = "Hello " }; var invokeRequest = new Request() { RequestParameter = "Hello " };
var invokedResponse = new Response { Name = "Look, I was invoked!" }; var invokeResponse = new Response { Name = "Look, I was invoked!" };
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
var task = daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", invokeRequest); {
return await daprClient.InvokeMethodGrpcAsync<Request, Response>("test", "test", invokeRequest);
});
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<InvokeServiceRequest>();
var envelope = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeServiceRequest>(entry.Request);
envelope.Id.Should().Be("test"); envelope.Id.Should().Be("test");
envelope.Message.Method.Should().Be("test"); envelope.Message.Method.Should().Be("test");
envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc); envelope.Message.ContentType.Should().Be(Constants.ContentTypeApplicationGrpc);
@ -274,10 +277,16 @@ namespace Dapr.Client.Test
var actual = envelope.Message.Data.Unpack<Request>(); var actual = envelope.Message.Data.Unpack<Request>();
Assert.Equal(invokeRequest.RequestParameter, actual.RequestParameter); Assert.Equal(invokeRequest.RequestParameter, actual.RequestParameter);
await SendResponse(invokedResponse, entry); // Create Response & Respond
var response = await task; 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] [Fact]
@ -285,7 +294,10 @@ namespace Dapr.Client.Test
{ {
// Configure Client // Configure Client
var httpClient = new AppCallbackClient(new DaprAppCallbackService()); 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!" }; var request = new Request() { RequestParameter = "Look, I was invoked!" };
@ -299,7 +311,10 @@ namespace Dapr.Client.Test
{ {
// Configure Client // Configure Client
var httpClient = new AppCallbackClient(new DaprAppCallbackService()); 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(); var testRun = new TestRun();
testRun.Tests.Add(new TestCase() { Name = "test1" }); testRun.Tests.Add(new TestCase() { Name = "test1" });
@ -319,7 +334,10 @@ namespace Dapr.Client.Test
{ {
// Configure Client // Configure Client
var httpClient = new AppCallbackClient(new DaprAppCallbackService()); 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!" }; var request = new Request() { RequestParameter = "Look, I was invoked!" };
@ -328,19 +346,6 @@ namespace Dapr.Client.Test
response.Name.Should().Be("unexpected"); response.Name.Should().Be("unexpected");
} }
private async Task SendResponse<T>(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 // Test implementation of the AppCallback.AppCallbackBase service
private class DaprAppCallbackService : AppCallback.Autogen.Grpc.v1.AppCallback.AppCallbackBase private class DaprAppCallbackService : AppCallback.Autogen.Grpc.v1.AppCallback.AppCallbackBase
{ {

View File

@ -8,7 +8,6 @@ namespace Dapr.Client.Test
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,7 +15,6 @@ namespace Dapr.Client.Test
using FluentAssertions; using FluentAssertions;
using Google.Protobuf; using Google.Protobuf;
using Grpc.Core; using Grpc.Core;
using Grpc.Net.Client;
using Moq; using Moq;
using Xunit; using Xunit;
@ -25,33 +23,29 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeBindingAsync_ValidateRequest() public async Task InvokeBindingAsync_ValidateRequest()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " };
var task = daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", invokeRequest); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", invokeRequest);
});
// Get Request and validate request.Dismiss();
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeBindingRequest>(entry.Request); // Get Request and validate
request.Name.Should().Be("test"); var envelope = await request.GetRequestEnvelopeAsync<InvokeBindingRequest>();
request.Metadata.Count.Should().Be(0); envelope.Name.Should().Be("test");
var json = request.Data.ToStringUtf8(); envelope.Metadata.Count.Should().Be(0);
var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, daprClient.JsonSerializerOptions); var json = envelope.Data.ToStringUtf8();
var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, client.InnerClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Hello "); typeFromRequest.RequestParameter.Should().Be("Hello ");
} }
[Fact] [Fact]
public async Task InvokeBindingAsync_ValidateRequest_WithMetadata() public async Task InvokeBindingAsync_ValidateRequest_WithMetadata()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
@ -59,55 +53,55 @@ namespace Dapr.Client.Test
{ "key2", "value2" } { "key2", "value2" }
}; };
var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " };
var task = daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", invokeRequest, metadata); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", invokeRequest, metadata);
});
request.Dismiss();
// Get Request and validate // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<InvokeBindingRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeBindingRequest>(entry.Request); envelope.Name.Should().Be("test");
request.Name.Should().Be("test"); envelope.Metadata.Count.Should().Be(2);
request.Metadata.Count.Should().Be(2); envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.Metadata.Keys.Contains("key2").Should().BeTrue();
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Metadata["key1"].Should().Be("value1");
request.Metadata["key1"].Should().Be("value1"); envelope.Metadata["key2"].Should().Be("value2");
request.Metadata["key2"].Should().Be("value2"); var json = envelope.Data.ToStringUtf8();
var json = request.Data.ToStringUtf8(); var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, client.InnerClient.JsonSerializerOptions);
var typeFromRequest = JsonSerializer.Deserialize<InvokeRequest>(json, daprClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Hello "); typeFromRequest.RequestParameter.Should().Be("Hello ");
} }
[Fact] [Fact]
public async Task InvokeBindingAsync_WithNullPayload_ValidateRequest() public async Task InvokeBindingAsync_WithNullPayload_ValidateRequest()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var task = daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", null); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", null);
});
// Get Request and validate request.Dismiss();
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeBindingRequest>(entry.Request); // Get Request and validate
request.Name.Should().Be("test"); var envelope = await request.GetRequestEnvelopeAsync<InvokeBindingRequest>();
request.Metadata.Count.Should().Be(0); envelope.Name.Should().Be("test");
var json = request.Data.ToStringUtf8(); envelope.Metadata.Count.Should().Be(0);
var json = envelope.Data.ToStringUtf8();
Assert.Equal("null", json); Assert.Equal("null", json);
} }
[Fact] [Fact]
public async Task InvokeBindingAsync_WithRequest_ValidateRequest() public async Task InvokeBindingAsync_WithRequest_ValidateRequest()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var payload = new InvokeRequest() { RequestParameter = "Hello " }; 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 = Metadata =
{ {
{ "key1", "value1" }, { "key1", "value1" },
@ -115,37 +109,35 @@ namespace Dapr.Client.Test
} }
}; };
var task = daprClient.InvokeBindingAsync(request); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
// Get Request and validate return await daprClient.InvokeBindingAsync(bindingRequest);
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); });
var gRpcRequest = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeBindingRequest>(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<InvokeRequest>(json, daprClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Hello ");
var gRpcResponse = new Autogen.Grpc.v1.InvokeBindingResponse() 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 = Metadata =
{ {
{ "anotherkey", "anothervalue" }, { "anotherkey", "anothervalue" },
} }
}; };
var streamContent = await GrpcUtils.CreateResponseContent(gRpcResponse); var response = await request.CompleteWithMessageAsync(gRpcResponse);
entry.Completion.SetResult(GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent));
var response = await task; var envelope = await request.GetRequestEnvelopeAsync<InvokeBindingRequest>();
Assert.Same(request, response.Request); envelope.Name.Should().Be("test");
Assert.Equal("red", JsonSerializer.Deserialize<Widget>(response.Data.Span, daprClient.JsonSerializerOptions).Color); 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<InvokeRequest>(json, client.InnerClient.JsonSerializerOptions);
typeFromRequest.RequestParameter.Should().Be("Hello ");
Assert.Same(bindingRequest, response.Request);
Assert.Equal("red", JsonSerializer.Deserialize<Widget>(response.Data.Span, client.InnerClient.JsonSerializerOptions).Color);
Assert.Collection( Assert.Collection(
response.Metadata, response.Metadata,
kvp => kvp =>
@ -159,15 +151,10 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeBindingAsync_WithCancelledToken() public async Task InvokeBindingAsync_WithCancelledToken()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true })
.Build();
var ctSource = new CancellationTokenSource(); var cts = new CancellationTokenSource();
CancellationToken ct = ctSource.Token; cts.Cancel();
ctSource.Cancel();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
@ -175,10 +162,10 @@ namespace Dapr.Client.Test
{ "key2", "value2" } { "key2", "value2" }
}; };
var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " };
var task = daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", invokeRequest, metadata, ct); await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await FluentActions.Awaiting(async () => await task) await client.InnerClient.InvokeBindingAsync<InvokeRequest>("test", "create", invokeRequest, metadata, cts.Token);
.Should().ThrowAsync<OperationCanceledException>(); });
} }
[Fact] [Fact]
@ -203,26 +190,21 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task InvokeBindingAsync_WrapsJsonException() public async Task InvokeBindingAsync_WrapsJsonException()
{ {
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var response = new Autogen.Grpc.v1.InvokeBindingResponse(); var response = new Autogen.Grpc.v1.InvokeBindingResponse();
var bytes = JsonSerializer.SerializeToUtf8Bytes<Widget>(new Widget(){ Color = "red", }, new JsonSerializerOptions(JsonSerializerDefaults.Web)); var bytes = JsonSerializer.SerializeToUtf8Bytes<Widget>(new Widget(){ Color = "red", }, client.InnerClient.JsonSerializerOptions);
response.Data = ByteString.CopyFrom(bytes.Take(10).ToArray()); // trim it to make invalid JSON blog response.Data = ByteString.CopyFrom(bytes.Take(10).ToArray()); // trim it to make invalid JSON blob
var task = daprClient.InvokeBindingAsync<InvokeRequest, Widget>("test", "test", new InvokeRequest() { RequestParameter = "Hello " }); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); return await daprClient.InvokeBindingAsync<InvokeRequest, Widget>("test", "test", new InvokeRequest() { RequestParameter = "Hello " });
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<InvokeBindingRequest>(entry.Request); });
var streamContent = await GrpcUtils.CreateResponseContent(response);
entry.Completion.SetResult(GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent));
var envelope = await request.GetRequestEnvelopeAsync<InvokeBindingRequest>();
var ex = await Assert.ThrowsAsync<DaprException>(async () => var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{ {
await task; await request.CompleteWithMessageAsync(response);
}); });
Assert.IsType<JsonException>(ex.InnerException); Assert.IsType<JsonException>(ex.InnerException);
} }

View File

@ -24,32 +24,30 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task PublishEventAsync_CanPublishTopicWithData() public async Task PublishEventAsync_CanPublishTopicWithData()
{ {
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions{ HttpClient = httpClient })
.Build();
var publishData = new PublishData() { PublishObjectParameter = "testparam" }; var publishData = new PublishData() { PublishObjectParameter = "testparam" };
var task = daprClient.PublishEventAsync<PublishData>(TestPubsubName, "test", publishData); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.PublishEventAsync<PublishData>(TestPubsubName, "test", publishData);
});
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); request.Dismiss();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<PublishEventRequest>(entry.Request);
var jsonFromRequest = request.Data.ToStringUtf8();
request.DataContentType.Should().Be("application/json"); var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
request.PubsubName.Should().Be(TestPubsubName); var jsonFromRequest = envelope.Data.ToStringUtf8();
request.Topic.Should().Be("test");
jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData, daprClient.JsonSerializerOptions)); envelope.DataContentType.Should().Be("application/json");
request.Metadata.Count.Should().Be(0); 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] [Fact]
public async Task PublishEventAsync_CanPublishTopicWithData_WithMetadata() public async Task PublishEventAsync_CanPublishTopicWithData_WithMetadata()
{ {
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions{ HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
@ -58,51 +56,53 @@ namespace Dapr.Client.Test
}; };
var publishData = new PublishData() { PublishObjectParameter = "testparam" }; var publishData = new PublishData() { PublishObjectParameter = "testparam" };
var task = daprClient.PublishEventAsync<PublishData>(TestPubsubName, "test", publishData, metadata); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.PublishEventAsync<PublishData>(TestPubsubName, "test", publishData, metadata);
});
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); request.Dismiss();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<PublishEventRequest>(entry.Request);
var jsonFromRequest = request.Data.ToStringUtf8();
request.DataContentType.Should().Be("application/json"); var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
request.PubsubName.Should().Be(TestPubsubName); var jsonFromRequest = envelope.Data.ToStringUtf8();
request.Topic.Should().Be("test");
jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData, daprClient.JsonSerializerOptions));
request.Metadata.Count.Should().Be(2); envelope.DataContentType.Should().Be("application/json");
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.PubsubName.Should().Be(TestPubsubName);
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Topic.Should().Be("test");
request.Metadata["key1"].Should().Be("value1"); jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData, client.InnerClient.JsonSerializerOptions));
request.Metadata["key2"].Should().Be("value2");
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] [Fact]
public async Task PublishEventAsync_CanPublishTopicWithNoContent() public async Task PublishEventAsync_CanPublishTopicWithNoContent()
{ {
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var task = daprClient.PublishEventAsync(TestPubsubName, "test"); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); {
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<PublishEventRequest>(entry.Request); await daprClient.PublishEventAsync(TestPubsubName, "test");
var jsonFromRequest = request.Data.ToStringUtf8(); });
request.PubsubName.Should().Be(TestPubsubName); request.Dismiss();
request.Topic.Should().Be("test");
request.Data.Length.Should().Be(0);
request.Metadata.Count.Should().Be(0); var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
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] [Fact]
public async Task PublishEventAsync_CanPublishTopicWithNoContent_WithMetadata() public async Task PublishEventAsync_CanPublishTopicWithNoContent_WithMetadata()
{ {
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
@ -110,35 +110,37 @@ namespace Dapr.Client.Test
{ "key2", "value2" } { "key2", "value2" }
}; };
var task = daprClient.PublishEventAsync(TestPubsubName, "test", metadata); var request = await client.CaptureGrpcRequestAsync(async daprClient =>
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); {
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<PublishEventRequest>(entry.Request); await daprClient.PublishEventAsync(TestPubsubName, "test", metadata);
});
request.PubsubName.Should().Be(TestPubsubName); request.Dismiss();
request.Topic.Should().Be("test");
request.Data.Length.Should().Be(0);
request.Metadata.Count.Should().Be(2); var envelope = await request.GetRequestEnvelopeAsync<PublishEventRequest>();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.PubsubName.Should().Be(TestPubsubName);
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Topic.Should().Be("test");
request.Metadata["key1"].Should().Be("value1"); envelope.Data.Length.Should().Be(0);
request.Metadata["key2"].Should().Be("value2");
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] [Fact]
public async Task PublishEventAsync_WithCancelledToken() public async Task PublishEventAsync_WithCancelledToken()
{ {
var httpClient = new TestHttpClient(); await using var client = TestClient.CreateForDaprClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true })
.Build();
var ctSource = new CancellationTokenSource(); var cts = new CancellationTokenSource();
CancellationToken ct = ctSource.Token; cts.Cancel();
ctSource.Cancel();
await FluentActions.Awaiting(async () => await daprClient.PublishEventAsync(TestPubsubName, "test", cancellationToken: ct)) await Assert.ThrowsAsync<OperationCanceledException>(async () =>
.Should().ThrowAsync<OperationCanceledException>(); {
await client.InnerClient.PublishEventAsync(TestPubsubName, "test", cancellationToken: cts.Token);
});
} }
// All overloads call through a common path that does exception handling. // All overloads call through a common path that does exception handling.

View File

@ -7,13 +7,10 @@ namespace Dapr.Client.Test
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Google.Rpc;
using Grpc.Core; using Grpc.Core;
using Grpc.Net.Client;
using Moq; using Moq;
using Xunit; using Xunit;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1; using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
@ -23,67 +20,66 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task GetSecretAsync_ValidateRequest() public async Task GetSecretAsync_ValidateRequest()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
{ "key1", "value1" }, { "key1", "value1" },
{ "key2", "value2" } { "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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetSecretRequest>(entry.Request); envelope.StoreName.Should().Be("testStore");
request.StoreName.Should().Be("testStore"); envelope.Key.Should().Be("test_key");
request.Key.Should().Be("test_key"); envelope.Metadata.Count.Should().Be(2);
request.Metadata.Count.Should().Be(2); envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.Metadata.Keys.Contains("key2").Should().BeTrue();
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Metadata["key1"].Should().Be("value1");
request.Metadata["key1"].Should().Be("value1"); envelope.Metadata["key2"].Should().Be("value2");
request.Metadata["key2"].Should().Be("value2");
} }
[Fact] [Fact]
public async Task GetSecretAsync_ReturnSingleSecret() public async Task GetSecretAsync_ReturnSingleSecret()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
{ "key1", "value1" }, { "key1", "value1" },
{ "key2", "value2" } { "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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetSecretRequest>(entry.Request); envelope.StoreName.Should().Be("testStore");
request.StoreName.Should().Be("testStore"); envelope.Key.Should().Be("test_key");
request.Key.Should().Be("test_key"); envelope.Metadata.Count.Should().Be(2);
request.Metadata.Count.Should().Be(2); envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.Metadata.Keys.Contains("key2").Should().BeTrue();
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Metadata["key1"].Should().Be("value1");
request.Metadata["key1"].Should().Be("value1"); envelope.Metadata["key2"].Should().Be("value2");
request.Metadata["key2"].Should().Be("value2");
// Create Response & Respond // Create Response & Respond
var secrets = new Dictionary<string, string> var secrets = new Dictionary<string, string>
{ {
{ "redis_secret", "Guess_Redis" } { "redis_secret", "Guess_Redis" }
}; };
await SendResponseWithSecrets(secrets, entry); var secretsResponse = await SendResponseWithSecrets(secrets, request);
// Get response and validate // Get response and validate
var secretsResponse = await task;
secretsResponse.Count.Should().Be(1); secretsResponse.Count.Should().Be(1);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"].Should().Be("Guess_Redis"); secretsResponse["redis_secret"].Should().Be("Guess_Redis");
@ -92,29 +88,29 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task GetSecretAsync_ReturnMultipleSecrets() public async Task GetSecretAsync_ReturnMultipleSecrets()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
{ "key1", "value1" }, { "key1", "value1" },
{ "key2", "value2" } { "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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetSecretRequest>(entry.Request); envelope.StoreName.Should().Be("testStore");
request.StoreName.Should().Be("testStore"); envelope.Key.Should().Be("test_key");
request.Key.Should().Be("test_key"); envelope.Metadata.Count.Should().Be(2);
request.Metadata.Count.Should().Be(2); envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.Metadata.Keys.Contains("key2").Should().BeTrue();
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Metadata["key1"].Should().Be("value1");
request.Metadata["key1"].Should().Be("value1"); envelope.Metadata["key2"].Should().Be("value2");
request.Metadata["key2"].Should().Be("value2");
// Create Response & Respond // Create Response & Respond
var secrets = new Dictionary<string, string> var secrets = new Dictionary<string, string>
@ -122,10 +118,9 @@ namespace Dapr.Client.Test
{ "redis_secret", "Guess_Redis" }, { "redis_secret", "Guess_Redis" },
{ "kafka_secret", "Guess_Kafka" } { "kafka_secret", "Guess_Kafka" }
}; };
await SendResponseWithSecrets(secrets, entry); var secretsResponse = await SendResponseWithSecrets(secrets, request);
// Get response and validate // Get response and validate
var secretsResponse = await task;
secretsResponse.Count.Should().Be(2); secretsResponse.Count.Should().Be(2);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"].Should().Be("Guess_Redis"); secretsResponse["redis_secret"].Should().Be("Guess_Redis");
@ -136,11 +131,7 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task GetSecretAsync_WithCancelledToken() public async Task GetSecretAsync_WithCancelledToken()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true })
.Build();
var metadata = new Dictionary<string, string> var metadata = new Dictionary<string, string>
{ {
@ -148,11 +139,13 @@ namespace Dapr.Client.Test
{ "key2", "value2" } { "key2", "value2" }
}; };
var ctSource = new CancellationTokenSource(); var cts = new CancellationTokenSource();
CancellationToken ct = ctSource.Token; cts.Cancel();
ctSource.Cancel();
await FluentActions.Awaiting(async () => await daprClient.GetSecretAsync("testStore", "test_key", metadata, cancellationToken: ct)) await Assert.ThrowsAsync<OperationCanceledException>(async () =>
.Should().ThrowAsync<OperationCanceledException>(); {
await client.InnerClient.GetSecretAsync("testStore", "test_key", metadata, cancellationToken: cts.Token);
});
} }
[Fact] [Fact]
@ -178,59 +171,58 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task GetBulkSecretAsync_ValidateRequest() public async Task GetBulkSecretAsync_ValidateRequest()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string>(); var metadata = new Dictionary<string, string>();
metadata.Add("key1", "value1"); metadata.Add("key1", "value1");
metadata.Add("key2", "value2"); 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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetBulkSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetBulkSecretRequest>(entry.Request); envelope.StoreName.Should().Be("testStore");
request.StoreName.Should().Be("testStore"); envelope.Metadata.Count.Should().Be(2);
request.Metadata.Count.Should().Be(2); envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.Metadata.Keys.Contains("key2").Should().BeTrue();
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Metadata["key1"].Should().Be("value1");
request.Metadata["key1"].Should().Be("value1"); envelope.Metadata["key2"].Should().Be("value2");
request.Metadata["key2"].Should().Be("value2");
} }
[Fact] [Fact]
public async Task GetBulkSecretAsync_ReturnSingleSecret() public async Task GetBulkSecretAsync_ReturnSingleSecret()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string>(); var metadata = new Dictionary<string, string>();
metadata.Add("key1", "value1"); metadata.Add("key1", "value1");
metadata.Add("key2", "value2"); 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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetBulkSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetBulkSecretRequest>(entry.Request); envelope.StoreName.Should().Be("testStore");
request.StoreName.Should().Be("testStore"); envelope.Metadata.Count.Should().Be(2);
request.Metadata.Count.Should().Be(2); envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.Metadata.Keys.Contains("key2").Should().BeTrue();
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Metadata["key1"].Should().Be("value1");
request.Metadata["key1"].Should().Be("value1"); envelope.Metadata["key2"].Should().Be("value2");
request.Metadata["key2"].Should().Be("value2");
// Create Response & Respond // Create Response & Respond
var secrets = new Dictionary<string, string>(); var secrets = new Dictionary<string, string>();
secrets.Add("redis_secret", "Guess_Redis"); secrets.Add("redis_secret", "Guess_Redis");
await SendBulkResponseWithSecrets(secrets, entry); var secretsResponse = await SendBulkResponseWithSecrets(secrets, request);
// Get response and validate // Get response and validate
var secretsResponse = await task;
secretsResponse.Count.Should().Be(1); secretsResponse.Count.Should().Be(1);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis"); secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis");
@ -239,35 +231,34 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task GetBulkSecretAsync_ReturnMultipleSecrets() public async Task GetBulkSecretAsync_ReturnMultipleSecrets()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
var metadata = new Dictionary<string, string>(); var metadata = new Dictionary<string, string>();
metadata.Add("key1", "value1"); metadata.Add("key1", "value1");
metadata.Add("key2", "value2"); 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 // Get Request and validate
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetBulkSecretRequest>();
var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.GetBulkSecretRequest>(entry.Request); envelope.StoreName.Should().Be("testStore");
request.StoreName.Should().Be("testStore"); envelope.Metadata.Count.Should().Be(2);
request.Metadata.Count.Should().Be(2); envelope.Metadata.Keys.Contains("key1").Should().BeTrue();
request.Metadata.Keys.Contains("key1").Should().BeTrue(); envelope.Metadata.Keys.Contains("key2").Should().BeTrue();
request.Metadata.Keys.Contains("key2").Should().BeTrue(); envelope.Metadata["key1"].Should().Be("value1");
request.Metadata["key1"].Should().Be("value1"); envelope.Metadata["key2"].Should().Be("value2");
request.Metadata["key2"].Should().Be("value2");
// Create Response & Respond // Create Response & Respond
var secrets = new Dictionary<string, string>(); var secrets = new Dictionary<string, string>();
secrets.Add("redis_secret", "Guess_Redis"); secrets.Add("redis_secret", "Guess_Redis");
secrets.Add("kafka_secret", "Guess_Kafka"); secrets.Add("kafka_secret", "Guess_Kafka");
await SendBulkResponseWithSecrets(secrets, entry); var secretsResponse = await SendBulkResponseWithSecrets(secrets, request);
// Get response and validate // Get response and validate
var secretsResponse = await task;
secretsResponse.Count.Should().Be(2); secretsResponse.Count.Should().Be(2);
secretsResponse.ContainsKey("redis_secret").Should().BeTrue(); secretsResponse.ContainsKey("redis_secret").Should().BeTrue();
secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis"); secretsResponse["redis_secret"]["redis_secret"].Should().Be("Guess_Redis");
@ -278,21 +269,19 @@ namespace Dapr.Client.Test
[Fact] [Fact]
public async Task GetBulkSecretAsync_WithCancelledToken() public async Task GetBulkSecretAsync_WithCancelledToken()
{ {
// Configure Client await using var client = TestClient.CreateForDaprClient();
var httpClient = new TestHttpClient();
var daprClient = new DaprClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true })
.Build();
var metadata = new Dictionary<string, string>(); var metadata = new Dictionary<string, string>();
metadata.Add("key1", "value1"); metadata.Add("key1", "value1");
metadata.Add("key2", "value2"); metadata.Add("key2", "value2");
var ctSource = new CancellationTokenSource(); var cts = new CancellationTokenSource();
CancellationToken ct = ctSource.Token; cts.Cancel();
ctSource.Cancel();
await FluentActions.Awaiting(async () => await daprClient.GetBulkSecretAsync("testStore", metadata, cancellationToken: ct)) await Assert.ThrowsAsync<OperationCanceledException>(async () =>
.Should().ThrowAsync<OperationCanceledException>(); {
await client.InnerClient.GetBulkSecretAsync("testStore", metadata, cancellationToken: cts.Token);
});
} }
[Fact] [Fact]
@ -315,17 +304,15 @@ namespace Dapr.Client.Test
Assert.Same(rpcException, ex.InnerException); Assert.Same(rpcException, ex.InnerException);
} }
private async Task SendResponseWithSecrets(Dictionary<string, string> secrets, TestHttpClient.Entry entry) private async Task<TResponse> SendResponseWithSecrets<TResponse>(Dictionary<string, string> secrets, TestClient<DaprClient>.TestGrpcRequest<TResponse> request)
{ {
var secretResponse = new Autogenerated.GetSecretResponse(); var secretResponse = new Autogenerated.GetSecretResponse();
secretResponse.Data.Add(secrets); secretResponse.Data.Add(secrets);
var streamContent = await GrpcUtils.CreateResponseContent(secretResponse); return await request.CompleteWithMessageAsync(secretResponse);
var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent);
entry.Completion.SetResult(response);
} }
private async Task SendBulkResponseWithSecrets(Dictionary<string, string> secrets, TestHttpClient.Entry entry) private async Task<TResponse> SendBulkResponseWithSecrets<TResponse>(Dictionary<string, string> secrets, TestClient<DaprClient>.TestGrpcRequest<TResponse> request)
{ {
var getBulkSecretResponse = new Autogenerated.GetBulkSecretResponse(); var getBulkSecretResponse = new Autogenerated.GetBulkSecretResponse();
foreach (var secret in secrets) foreach (var secret in secrets)
@ -335,9 +322,7 @@ namespace Dapr.Client.Test
getBulkSecretResponse.Data.Add(secret.Key, secretsResponse); getBulkSecretResponse.Data.Add(secret.Key, secretsResponse);
} }
var streamContent = await GrpcUtils.CreateResponseContent(getBulkSecretResponse); return await request.CompleteWithMessageAsync(getBulkSecretResponse);
var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent);
entry.Completion.SetResult(response);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Shared\TestHttpClient.cs" />
<Compile Include="..\Shared\GrpcUtils.cs" /> <Compile Include="..\Shared\GrpcUtils.cs" />
<Compile Include="..\Shared\TestClient.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -16,6 +16,10 @@ using System.Threading.Tasks;
namespace Dapr.Extensions.Configuration.Test 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 public class DaprSecretStoreConfigurationProviderTest
{ {

View File

@ -14,7 +14,7 @@ namespace Dapr
using System.Threading; using System.Threading;
using System.Threading.Tasks; 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 public class TestHttpClient : HttpClient
{ {
private readonly TestHttpClientHandler handler; private readonly TestHttpClientHandler handler;

View File

@ -21,20 +21,15 @@ namespace Dapr
public class AppCallbackClient : HttpClient public class AppCallbackClient : HttpClient
{ {
public AppCallbackClient(AppCallbackBase callbackService) public AppCallbackClient(AppCallbackBase callbackService)
: this(new TestHttpClientHandler(callbackService)) : base(new Handler(callbackService))
{ {
} }
private AppCallbackClient(TestHttpClientHandler handler) private class Handler : HttpMessageHandler
: base(handler)
{
}
private class TestHttpClientHandler : HttpMessageHandler
{ {
private readonly AppCallbackBase callbackService; private readonly AppCallbackBase callbackService;
public TestHttpClientHandler(AppCallbackBase callbackService) public Handler(AppCallbackBase callbackService)
{ {
this.callbackService = callbackService; this.callbackService = callbackService;
} }

420
test/Shared/TestClient.cs Normal file
View File

@ -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<DaprHttpInteractor> CreateForDaprHttpInterator(string? apiToken = null)
{
var handler = new CapturingHandler();
return new TestClient<DaprHttpInteractor>(new DaprHttpInteractor(handler, apiToken), handler);
}
#endif
public static TestClient<HttpMessageHandler> CreateForMessageHandler()
{
var handler = new CapturingHandler();
return new TestClient<HttpMessageHandler>(handler, handler);
}
public static TestClient<DaprClient> CreateForDaprClient(Action<DaprClientBuilder>? 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<DaprClient>(builder.Build(), handler);
}
private static async Task WithTimeout(Task task, TimeSpan timeout, string message)
{
var tcs = new TaskCompletionSource<object>();
using var cts = new CancellationTokenSource(timeout);
using (cts.Token.Register((obj) =>
{
var tcs = (TaskCompletionSource<object>)obj!;
tcs.SetException(new TimeoutException());
}, tcs))
{
await (await Task.WhenAny(task, tcs.Task));
}
}
private static async Task<T> WithTimeout<T>(Task<T> task, TimeSpan timeout, string message)
{
var tcs = new TaskCompletionSource<T>();
using var cts = new CancellationTokenSource(timeout);
using (cts.Token.Register((obj) =>
{
var tcs = (TaskCompletionSource<T>)obj!;
tcs.SetException(new TimeoutException());
}, tcs))
{
return await (await Task.WhenAny<T>(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<T>
{
public TestHttpRequest(HttpRequestMessage request, CaptureToken capture, Task<T> task)
{
this.Request = request;
this.Capture = capture;
this.Task = task;
}
public HttpRequestMessage Request { get; }
private CaptureToken Capture { get; }
private Task<T> Task { get; }
public async Task<T> CompleteWithJsonAsync<TData>(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<T> CompleteAsync(HttpResponseMessage response)
{
this.Capture.Response.SetResult(response);
return await WithTimeout<T>(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<TRequestEnvelope> GetRequestEnvelopeAsync<TRequestEnvelope>()
where TRequestEnvelope : IMessage<TRequestEnvelope>, new()
{
return await GrpcUtils.GetRequestFromRequestMessageAsync<TRequestEnvelope>(this.Request);
}
public async Task CompleteWithMessageAsync<TResponseEnvelope>(TResponseEnvelope value)
where TResponseEnvelope : IMessage<TResponseEnvelope>
{
var content = await GrpcUtils.CreateResponseContent<TResponseEnvelope>(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<TResponse>
{
public TestGrpcRequest(HttpRequestMessage request, CaptureToken capture, Task<TResponse> task)
{
this.Request = request;
this.Capture = capture;
this.Task = task;
}
public HttpRequestMessage Request { get; }
private CaptureToken Capture { get; }
private Task<TResponse> Task { get; }
public async Task<TRequestEnvelope> GetRequestEnvelopeAsync<TRequestEnvelope>()
where TRequestEnvelope : IMessage<TRequestEnvelope>, new()
{
return await GrpcUtils.GetRequestFromRequestMessageAsync<TRequestEnvelope>(this.Request);
}
public async Task<TResponse> CompleteWithMessageAsync<TResponseEnvelope>(TResponseEnvelope value)
where TResponseEnvelope : IMessage<TResponseEnvelope>
{
var content = await GrpcUtils.CreateResponseContent<TResponseEnvelope>(value);
var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, content);
return await CompleteAsync(response);
}
public async Task<TResponse> CompleteAsync(HttpResponseMessage response)
{
this.Capture.Response.SetResult(response);
return await WithTimeout<TResponse>(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<CaptureToken> requests = new ConcurrentQueue<CaptureToken>();
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<CaptureToken> GetOutstandingRequests()
{
foreach (var request in this.requests)
{
if (request.IsComplete)
{
continue;
}
yield return request;
}
}
protected override Task<HttpResponseMessage> 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<HttpRequestMessage> Request { get; } = new TaskCompletionSource<HttpRequestMessage>();
public TaskCompletionSource<HttpResponseMessage> Response { get; } = new TaskCompletionSource<HttpResponseMessage>();
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<HttpRequestMessage> GetRequestAsync(TimeSpan timeout)
{
return WithTimeout(Request.Task, timeout, "Waiting for request to be queued timed out.");
}
}
}
public class TestClient<TClient> : 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<TestHttpRequest> CaptureHttpRequestAsync(Func<TClient, Task> operation)
{
var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation);
return new TestHttpRequest(request, capture, task);
}
public async Task<TestHttpRequest<T>> CaptureHttpRequestAsync<T>(Func<TClient, Task<T>> operation)
{
var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation);
return new TestHttpRequest<T>(request, capture, (Task<T>)task);
}
public async Task<TestGrpcRequest> CaptureGrpcRequestAsync(Func<TClient, Task> operation)
{
var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation);
return new TestGrpcRequest(request, capture, task);
}
public async Task<TestGrpcRequest<T>> CaptureGrpcRequestAsync<T>(Func<TClient, Task<T>> operation)
{
var (request, capture, task) = await CaptureHttpRequestMessageAsync(operation);
return new TestGrpcRequest<T>(request, capture, (Task<T>)task);
}
private async Task<(HttpRequestMessage, CaptureToken, Task)> CaptureHttpRequestMessageAsync(Func<TClient, Task> 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();
}
}
}