// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ namespace Dapr.Client.Test { using System; using System.Net; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; using Dapr.Client.Autogen.Grpc; using FluentAssertions; using Grpc.Core; using Grpc.Net.Client; using Xunit; public class StateHttpClientTest { private const string DaprDefaultEndpoint = "127.0.0.1"; [Fact] public async Task GetStateAsync_CanReadState() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.GetStateAsync("testStore", "test"); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); SendResponseWithState(data, entry); // Get response and validate var state = await task; state.Size.Should().Be("small"); state.Color.Should().Be("yellow"); } [Fact] public async Task GetStateAsync_CanReadEmptyState_ReturnsDefault() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.GetStateAsync("testStore", "test"); // Create Response & Respond httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); SendResponseWithState(null, entry); // Get response and validate var state = await task; state.Should().BeNull(); } [Fact] public async Task GetStateAsync_ThrowsForNonSuccess() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); // Create Response & Respond var task = daprClient.GetStateAsync("testStore", "test"); httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); entry.Completion.SetResult(response); await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); } [Fact] public async Task SaveStateAsync_CanSaveState() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var widget = new Widget() { Size = "small", Color = "yellow", }; var task = daprClient.SaveStateAsync("testStore", "test", widget); // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.StoreName.Should().Be("testStore"); envelope.Requests.Count.Should().Be(1); var request = envelope.Requests[0]; request.Key.Should().Be("test"); var stateJson = request.Value.Value.ToStringUtf8(); var stateFromRequest = JsonSerializer.Deserialize(stateJson); stateFromRequest.Size.Should().Be(widget.Size); stateFromRequest.Color.Should().Be(widget.Color); } [Fact] public async Task SaveStateAsync_CanClearState() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.SaveStateAsync("testStore", "test", null); // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.StoreName.Should().Be("testStore"); envelope.Requests.Count.Should().Be(1); var request = envelope.Requests[0]; request.Key.Should().Be("test"); request.Value.Should().BeNull(); } [Fact] public async Task SetStateAsync_ThrowsForNonSuccess() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var widget = new Widget() { Size = "small", Color = "yellow", }; var task = daprClient.SaveStateAsync("testStore", "test", widget); // Create Response & Respond httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); entry.Completion.SetResult(response); await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); } [Fact] public async Task DeleteStateAsync_CanDeleteState() { var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.DeleteStateAsync("testStore", "test"); httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.StoreName.Should().Be("testStore"); envelope.Key.Should().Be("test"); } [Fact] public async Task DeleteStateAsync_ThrowsForNonSuccess() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.DeleteStateAsync("testStore", "test"); // Create Response & Respond httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); entry.Completion.SetResult(response); await FluentActions.Awaiting(async () => await task).Should().ThrowAsync(); } [Fact] public async Task GetStateEntryAsync_CanReadState() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.GetStateEntryAsync("testStore", "test"); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); SendResponseWithState(data, entry); // Get response and validate var state = await task; state.Value.Size.Should().Be("small"); state.Value.Color.Should().Be("yellow"); } [Fact] public async Task GetStateEntryAsync_CanReadEmptyState_ReturnsDefault() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.GetStateEntryAsync("testStore", "test"); // Create Response & Respond httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); SendResponseWithState(null, entry); var state = await task; state.Key.Should().Be("test"); state.Value.Should().BeNull(); } [Fact] public async Task GetStateEntryAsync_CanSaveState() { // Configure Client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.GetStateEntryAsync("testStore", "test"); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); SendResponseWithState(data, entry); var state = await task; state.Key.Should().Be("test"); state.Value.Size.Should().Be("small"); state.Value.Color.Should().Be("yellow"); // Modify the state and save it state.Value.Color = "green"; var task2 = state.SaveAsync(); // Get Request and validate httpClient.Requests.TryDequeue(out entry).Should().BeTrue(); var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.StoreName.Should().Be("testStore"); envelope.Requests.Count.Should().Be(1); var request = envelope.Requests[0]; request.Key.Should().Be("test"); var stateJson = request.Value.Value.ToStringUtf8(); var stateFromRequest = JsonSerializer.Deserialize(stateJson); stateFromRequest.Size.Should().Be("small"); stateFromRequest.Color.Should().Be("green"); } [Fact] public async Task GetStateEntryAsync_CanDeleteState() { // Configure client var httpClient = new TestHttpClient(); var daprClient = new DaprClientBuilder() .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var task = daprClient.GetStateEntryAsync("testStore", "test"); // Create Response & Respond var data = new Widget() { Size = "small", Color = "yellow", }; httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); SendResponseWithState(data, entry); var state = await task; state.Key.Should().Be("test"); state.Value.Size.Should().Be("small"); state.Value.Color.Should().Be("yellow"); state.Value.Color = "green"; var task2 = state.DeleteAsync(); // Get Request and validate httpClient.Requests.TryDequeue(out entry).Should().BeTrue(); var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.StoreName.Should().Be("testStore"); envelope.Key.Should().Be("test"); } private static string GetStateUrl(int port, string storeName, string key) { return $"http://{DaprDefaultEndpoint}:{port}/v1.0/state/{storeName}/{key}"; } private static string SaveStateUrl(int port, string storeName) { return $"http://{DaprDefaultEndpoint}:{port}/v1.0/state/{storeName}"; } private static string DeleteStateUrl(int port, string storeName, string key) { return $"http://{DaprDefaultEndpoint}:{port}/v1.0/state/{storeName}/{key}"; } private async void SendResponseWithState(T state, TestHttpClient.Entry entry) { var stateAny = await ProtobufUtils.ConvertToAnyAsync(state); var stateResponse = new GetStateResponseEnvelope(); stateResponse.Data = stateAny; stateResponse.Etag = "test"; var streamContent = await GrpcUtils.CreateResponseContent(stateResponse); var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent); entry.Completion.SetResult(response); } private class Widget { public string Size { get; set; } public string Color { get; set; } } } }