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" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<LangVersion>9.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)\IsExternalInit.cs" />
</ItemGroup>
<!-- Cls Compliant -->
<PropertyGroup>
<AssemblyClsCompliant>true</AssemblyClsCompliant>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,9 +37,8 @@ namespace Dapr.AspNetCore.Test
[Fact]
public void DaprClientBuilder_DoesNotOverrideUserGrpcChannelOptions()
{
var httpClient = new TestHttpClient();
var builder = new DaprClientBuilder();
var daprClient = builder.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }).Build();
var daprClient = builder.UseGrpcChannelOptions(new GrpcChannelOptions()).Build();
Assert.False(builder.GrpcChannelOptions.ThrowOperationCanceledOnCancellation);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -16,6 +16,10 @@ using System.Threading.Tasks;
namespace Dapr.Extensions.Configuration.Test
{
// These tests use the outdated TestHttpClient infrastructure because they need to
// support testing with synchronous HTTP requests.
//
// Don't copy this pattern elsewhere.
public class DaprSecretStoreConfigurationProviderTest
{

View File

@ -14,7 +14,7 @@ namespace Dapr
using System.Threading;
using System.Threading.Tasks;
// This client will capture all requests, and put them in .Requests for you to inspect.
// This is an old piece of infrastructure with some limitations, don't use it in new places.
public class TestHttpClient : HttpClient
{
private readonly TestHttpClientHandler handler;

View File

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

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();
}
}
}