// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ namespace Dapr.Actors.Test { using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Authentication; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Dapr.Actors.Communication; using FluentAssertions; using Xunit; /// /// Contains tests for DaprHttpInteractor. /// public class DaprHttpInteractorTest { public class Entry { public Entry(HttpRequestMessage request) { this.Request = request; this.Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } public TaskCompletionSource Completion { get; } public HttpRequestMessage Request { get; } } private class TestHttpClientHandler : HttpClientHandler { public TestHttpClientHandler() { this.Requests = new ConcurrentQueue(); } public ConcurrentQueue Requests { get; } public Action Handler { get; set; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var entry = new Entry(request); this.Handler?.Invoke(entry); this.Requests.Enqueue(entry); using (cancellationToken.Register(() => entry.Completion.TrySetCanceled())) { return await entry.Completion.Task.ConfigureAwait(false); } } } [Fact] 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); handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var actualPath = entry.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); } [Fact] public void SaveStateTransactionally_ValidateRequest() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var data = "StateData"; var task = httpInteractor.SaveStateTransactionallyAsync(actorType, actorId, data); handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var actualPath = entry.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); } [Fact] public void InvokeActorMethodWithoutRemoting_ValidateRequest() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var methodName = "MethodName"; var payload = "JsonData"; var task = httpInteractor.InvokeActorMethodWithoutRemotingAsync(actorType, actorId, methodName, payload); handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var actualPath = entry.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); } [Fact] public void RegisterReminder_ValidateRequest() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var reminderName = "ReminderName"; var payload = "JsonData"; var task = httpInteractor.RegisterReminderAsync(actorType, actorId, reminderName, payload); handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var actualPath = entry.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); } [Fact] public void UnregisterReminder_ValidateRequest() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var reminderName = "ReminderName"; var task = httpInteractor.UnregisterReminderAsync(actorType, actorId, reminderName); handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var actualPath = entry.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); } [Fact] public void RegisterTimer_ValidateRequest() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); var actorType = "ActorType_Test"; var actorId = "ActorId_Test"; var timerName = "TimerName"; var payload = "JsonData"; var task = httpInteractor.RegisterTimerAsync(actorType, actorId, timerName, payload); handler.Requests.TryDequeue(out var entry).Should().BeTrue(); var actualPath = entry.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); } [Fact] public void UnregisterTimer_ValidateRequest() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); 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 actualPath = entry.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); } [Fact] public void Call_WithApiTokenEnvVar() { Environment.SetEnvironmentVariable("DAPR_API_TOKEN", "test_token"); var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); 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(); entry.Request.Headers.TryGetValues("dapr-api-token", out var headerValues); headerValues.Count().Should().Be(1); headerValues.First().Should().Be("test_token"); } [Fact] public void Call_WithoutApiTokenEnvVar() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); 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(); entry.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); 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 error = new DaprError() { ErrorCode = "ERR_STATE_STORE", Message = "State Store Error" }; var message = new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent(JsonSerializer.Serialize(error)) }; entry.Completion.SetResult(message); await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); } [Fact] public async Task Call_ValidateUnsuccessful404Response() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); 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 message = new HttpResponseMessage(HttpStatusCode.NotFound); entry.Completion.SetResult(message); await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); } [Fact] public async Task Call_ValidateUnauthorizedResponse() { var handler = new TestHttpClientHandler(); var httpInteractor = new DaprHttpInteractor(handler); 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 message = new HttpResponseMessage(HttpStatusCode.Unauthorized); entry.Completion.SetResult(message); await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); } } }