dotnet-sdk/test/Dapr.Client.Test/StateApiTest.cs

1037 lines
41 KiB
C#

// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
namespace Dapr.Client.Test
{
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
using FluentAssertions;
using Google.Protobuf;
using Grpc.Core;
using Grpc.Net.Client;
using Moq;
using StateConsistency = Dapr.Client.Autogen.Grpc.v1.StateOptions.Types.StateConsistency;
using StateConcurrency = Dapr.Client.Autogen.Grpc.v1.StateOptions.Types.StateConcurrency;
using Xunit;
using System.Threading;
using System.Net.Http;
public class StateApiTest
{
[Fact]
public async Task GetStateAsync_CanReadState()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAsync<Widget>("testStore", "test");
});
request.Dismiss();
// Create Response & Respond
var data = new Widget() { Size = "small", Color = "yellow", };
var envelope = MakeGetStateResponse(data);
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Size.Should().Be("small");
state.Color.Should().Be("yellow");
}
[Fact]
public async Task GetBulkStateAsync_CanReadState()
{
await using var client = TestClient.CreateForDaprClient();
var key = "test";
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetBulkStateAsync("testStore", new List<string>() { key }, null);
});
// Create Response & Respond
var data = "value";
var envelope = MakeGetBulkStateResponse(key, data);
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Should().HaveCount(1);
}
[Fact]
public async Task GetBulkStateAsync_WrapsRpcException()
{
await using var client = TestClient.CreateForDaprClient();
var key = "test";
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetBulkStateAsync("testStore", new List<string>() { key }, null);
});
// Create Response & Respond
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable));
});
Assert.IsType<RpcException>(ex.InnerException);
}
[Fact]
public async Task GetBulkStateAsync_ValidateRequest()
{
await using var client = TestClient.CreateForDaprClient();
var key = "test";
var metadata = new Dictionary<string, string>
{
{ "partitionKey", "mypartition" }
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetBulkStateAsync("testStore", new List<string>() { key }, null, metadata: metadata);
});
request.Dismiss();
// Create Response & Validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetBulkStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Metadata.Should().BeEquivalentTo(metadata);
}
[Fact]
public async Task GetStateAndEtagAsync_CanReadState()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAndETagAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var data = new Widget() { Size = "small", Color = "yellow", };
var envelope = MakeGetStateResponse(data, "Test_Etag");
var (state, etag) = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Size.Should().Be("small");
state.Color.Should().Be("yellow");
etag.Should().Be("Test_Etag");
}
[Fact]
public async Task GetStateAndETagAsync_WrapsRpcException()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAndETagAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable));
});
Assert.IsType<RpcException>(ex.InnerException);
}
[Fact]
public async Task GetStateAndETagAsync_WrapsJsonException()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAndETagAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var envelope = new Autogenerated.GetStateResponse()
{
// Totally NOT valid JSON
Data = ByteString.CopyFrom(0x5b, 0x7b, 0x5b, 0x7b),
};
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteWithMessageAsync(envelope);
});
Assert.IsType<JsonException>(ex.InnerException);
}
[Fact]
public async Task GetStateAsync_CanReadEmptyState_ReturnsDefault()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAsync<Widget>("testStore", "test", ConsistencyMode.Eventual);
});
// Create Response & Respond
var envelope = MakeGetStateResponse<Widget>(null);
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Should().BeNull();
}
[Theory]
[InlineData(ConsistencyMode.Eventual, StateConsistency.ConsistencyEventual)]
[InlineData(ConsistencyMode.Strong, StateConsistency.ConsistencyStrong)]
public async Task GetStateAsync_ValidateRequest(ConsistencyMode consistencyMode, StateConsistency expectedConsistencyMode)
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAsync<Widget>("testStore", "test", consistencyMode);
});
// Get Request & Validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
envelope.Consistency.Should().Be(expectedConsistencyMode);
// Create Response & Respond
var state = await request.CompleteWithMessageAsync(MakeGetStateResponse<Widget>(null));
// Get response and validate
state.Should().BeNull();
}
[Fact]
public async Task GetStateAndEtagAsync_ValidateRequest()
{
await using var client = TestClient.CreateForDaprClient();
var metadata = new Dictionary<string, string>
{
{ "partitionKey", "mypartition" }
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAsync<Widget>("testStore", "test", metadata: metadata);
});
// Get Request & Validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.GetStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
envelope.Metadata.Should().BeEquivalentTo(metadata);
// Create Response & Respond
var state = await request.CompleteWithMessageAsync(MakeGetStateResponse<Widget>(null));
// Get response and validate
state.Should().BeNull();
}
[Fact]
public async Task GetStateAsync_WrapsRpcException()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable));
});
Assert.IsType<RpcException>(ex.InnerException);
}
[Fact]
public async Task GetStateAsync_WrapsJsonException()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var stateResponse = new Autogenerated.GetStateResponse()
{
// Totally NOT valid JSON
Data = ByteString.CopyFrom(0x5b, 0x7b, 0x5b, 0x7b),
};
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteWithMessageAsync(stateResponse);
});
Assert.IsType<JsonException>(ex.InnerException);
}
[Fact]
public async Task SaveStateAsync_CanSaveState()
{
await using var client = TestClient.CreateForDaprClient();
var widget = new Widget() { Size = "small", Color = "yellow", };
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.SaveStateAsync("testStore", "test", widget);
});
request.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
var state = envelope.States[0];
state.Key.Should().Be("test");
var stateJson = state.Value.ToStringUtf8();
var stateFromRequest = JsonSerializer.Deserialize<Widget>(stateJson, client.InnerClient.JsonSerializerOptions);
stateFromRequest.Size.Should().Be(widget.Size);
stateFromRequest.Color.Should().Be(widget.Color);
}
[Fact]
public async Task GetStateAsync_WithCancelledToken()
{
await using var client = TestClient.CreateForDaprClient();
var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await client.InnerClient.GetStateAsync<Widget>("testStore", "test", cancellationToken: cts.Token);
});
}
[Fact]
public async Task SaveStateAsync_CanClearState()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.SaveStateAsync<object>("testStore", "test", null);
});
request.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
var state = envelope.States[0];
state.Key.Should().Be("test");
state.Value.Should().Equal(ByteString.Empty);
}
[Fact]
public async Task SaveStateAsync_WithCancelledToken()
{
await using var client = TestClient.CreateForDaprClient();
var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await client.InnerClient.SaveStateAsync<object>("testStore", "test", null, cancellationToken: cts.Token);
});
}
[Fact]
public async Task SetStateAsync_ThrowsForNonSuccess()
{
await using var client = TestClient.CreateForDaprClient();
var widget = new Widget() { Size = "small", Color = "yellow", };
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.SaveStateAsync("testStore", "test", widget);
});
// Create Response & Respond
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable));
});
Assert.IsType<RpcException>(ex.InnerException);
}
[Fact]
public async Task ExecuteStateTransactionAsync_CanSaveState()
{
await using var client = TestClient.CreateForDaprClient();
var stateValue1 = new Widget() { Size = "small", Color = "yellow", };
var metadata1 = new Dictionary<string, string>()
{
{"a", "b" }
};
var options1 = new StateOptions
{
Concurrency = ConcurrencyMode.LastWrite
};
var state1 = new StateTransactionRequest("stateKey1", JsonSerializer.SerializeToUtf8Bytes(stateValue1), StateOperationType.Upsert, "testEtag", metadata1, options1);
var stateValue2 = 100;
var state2 = new StateTransactionRequest("stateKey2", JsonSerializer.SerializeToUtf8Bytes(stateValue2), StateOperationType.Delete);
var stateValue3 = "teststring";
var state3 = new StateTransactionRequest("stateKey3", JsonSerializer.SerializeToUtf8Bytes(stateValue3), StateOperationType.Upsert);
var states = new List<StateTransactionRequest>
{
state1,
state2,
state3
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.ExecuteStateTransactionAsync("testStore", states);
});
request.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.ExecuteStateTransactionRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Operations.Count.Should().Be(3);
var req1 = envelope.Operations[0];
req1.Request.Key.Should().Be("stateKey1");
req1.OperationType.Should().Be(StateOperationType.Upsert.ToString().ToLower());
var valueJson1 = req1.Request.Value.ToStringUtf8();
var value1 = JsonSerializer.Deserialize<Widget>(valueJson1, client.InnerClient.JsonSerializerOptions);
value1.Size.Should().Be(stateValue1.Size);
value1.Color.Should().Be(stateValue1.Color);
req1.Request.Etag.Value.Should().Be("testEtag");
req1.Request.Metadata.Count.Should().Be(1);
req1.Request.Metadata["a"].Should().Be("b");
req1.Request.Options.Concurrency.Should().Be(2);
var req2 = envelope.Operations[1];
req2.Request.Key.Should().Be("stateKey2");
req2.OperationType.Should().Be(StateOperationType.Delete.ToString().ToLower());
var valueJson2 = req2.Request.Value.ToStringUtf8();
var value2 = JsonSerializer.Deserialize<int>(valueJson2, client.InnerClient.JsonSerializerOptions);
value2.Should().Be(100);
var req3 = envelope.Operations[2];
req3.Request.Key.Should().Be("stateKey3");
req3.OperationType.Should().Be(StateOperationType.Upsert.ToString().ToLower());
var valueJson3 = req3.Request.Value.ToStringUtf8();
var value3 = JsonSerializer.Deserialize<string>(valueJson3, client.InnerClient.JsonSerializerOptions);
value3.Should().Be("teststring");
}
[Fact]
public async Task ExecuteStateTransactionAsync_ThrowsForNonSuccess()
{
await using var client = TestClient.CreateForDaprClient();
var widget1 = new Widget() { Size = "small", Color = "yellow", };
var state1 = new StateTransactionRequest("stateKey1", JsonSerializer.SerializeToUtf8Bytes(widget1), StateOperationType.Upsert);
var states = new List<StateTransactionRequest>
{
state1
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.ExecuteStateTransactionAsync("testStore", states);
});
// Create Response & Respond
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable));
});
Assert.IsType<RpcException>(ex.InnerException);
}
[Fact]
public async Task ExecuteStateTransactionAsync_WithCancelledToken()
{
await using var client = TestClient.CreateForDaprClient();
var operation = new StateTransactionRequest("test", null, StateOperationType.Delete);
var operations = new List<StateTransactionRequest>
{
operation,
};
var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await client.InnerClient.ExecuteStateTransactionAsync("testStore", operations, new Dictionary<string, string>(), cancellationToken: cts.Token);
});
}
[Fact]
public async Task DeleteStateAsync_CanDeleteState()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.DeleteStateAsync("testStore", "test");
});
request.Dismiss();
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.DeleteStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
}
[Fact]
public async Task DeleteStateAsync_ThrowsForNonSuccess()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.DeleteStateAsync("testStore", "test");
});
// Create Response & Respond
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.NotAcceptable));
});
Assert.IsType<RpcException>(ex.InnerException);
}
[Fact]
public async Task DeleteStateAsync_WithCancelledToken()
{
await using var client = TestClient.CreateForDaprClient();
var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await client.InnerClient.DeleteStateAsync("testStore", "key", cancellationToken: cts.Token);
});
}
[Fact]
public async Task GetStateEntryAsync_CanReadState()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateEntryAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var data = new Widget() { Size = "small", Color = "yellow", };
var envelope = MakeGetStateResponse(data);
var state = await request.CompleteWithMessageAsync(envelope);
// Get response and validate
state.Value.Size.Should().Be("small");
state.Value.Color.Should().Be("yellow");
}
[Fact]
public async Task GetStateEntryAsync_CanReadEmptyState_ReturnsDefault()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateEntryAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var envelope = MakeGetStateResponse<Widget>(null);
var state = await request.CompleteWithMessageAsync(envelope);
state.Key.Should().Be("test");
state.Value.Should().BeNull();
}
[Fact]
public async Task GetStateEntryAsync_CanSaveState()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateEntryAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var data = new Widget() { Size = "small", Color = "yellow", };
var state = await request.CompleteWithMessageAsync(MakeGetStateResponse(data));
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 request2 = await client.CaptureGrpcRequestAsync(async _ =>
{
await state.SaveAsync();
});
request2.Dismiss();
// Get Request and validate
var envelope = await request2.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
var requestState = envelope.States[0];
requestState.Key.Should().Be("test");
var stateJson = requestState.Value.ToStringUtf8();
var stateFromRequest = JsonSerializer.Deserialize<Widget>(stateJson, client.InnerClient.JsonSerializerOptions);
stateFromRequest.Size.Should().Be("small");
stateFromRequest.Color.Should().Be("green");
}
[Fact]
public async Task GetStateEntryAsync_CanDeleteState()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.GetStateEntryAsync<Widget>("testStore", "test");
});
// Create Response & Respond
var data = new Widget() { Size = "small", Color = "yellow", };
var state = await request.CompleteWithMessageAsync(MakeGetStateResponse(data));
state.Key.Should().Be("test");
state.Value.Size.Should().Be("small");
state.Value.Color.Should().Be("yellow");
state.Value.Color = "green";
var request2 = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await state.DeleteAsync();
});
request2.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.DeleteStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
}
[Theory]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyLastWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyLastWrite)]
public async Task SaveStateAsync_ValidateOptions(
ConsistencyMode consistencyMode,
ConcurrencyMode concurrencyMode,
StateConsistency expectedConsistency,
StateConcurrency expectedConcurrency)
{
await using var client = TestClient.CreateForDaprClient();
var widget = new Widget() { Size = "small", Color = "yellow", };
var stateOptions = new StateOptions
{
Concurrency = concurrencyMode,
Consistency = consistencyMode
};
var metadata = new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.SaveStateAsync("testStore", "test", widget, stateOptions, metadata);
});
request.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
var state = envelope.States[0];
state.Key.Should().Be("test");
state.Metadata.Count.Should().Be(2);
state.Metadata.Keys.Contains("key1").Should().BeTrue();
state.Metadata.Keys.Contains("key2").Should().BeTrue();
state.Metadata["key1"].Should().Be("value1");
state.Metadata["key2"].Should().Be("value2");
state.Options.Concurrency.Should().Be(expectedConcurrency);
state.Options.Consistency.Should().Be(expectedConsistency);
var stateJson = state.Value.ToStringUtf8();
var stateFromRequest = JsonSerializer.Deserialize<Widget>(stateJson, client.InnerClient.JsonSerializerOptions);
stateFromRequest.Size.Should().Be(widget.Size);
stateFromRequest.Color.Should().Be(widget.Color);
}
[Theory]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyLastWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyLastWrite)]
public async Task TrySaveStateAsync_ValidateOptions(
ConsistencyMode consistencyMode,
ConcurrencyMode concurrencyMode,
StateConsistency expectedConsistency,
StateConcurrency expectedConcurrency)
{
await using var client = TestClient.CreateForDaprClient();
var widget = new Widget() { Size = "small", Color = "yellow", };
var stateOptions = new StateOptions
{
Concurrency = concurrencyMode,
Consistency = consistencyMode
};
var metadata = new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.TrySaveStateAsync("testStore", "test", widget, "Test_Etag", stateOptions, metadata);
});
request.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.SaveStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
var state = envelope.States[0];
state.Etag.Value.Should().Be("Test_Etag");
state.Metadata.Count.Should().Be(2);
state.Metadata.Keys.Contains("key1").Should().BeTrue();
state.Metadata.Keys.Contains("key2").Should().BeTrue();
state.Metadata["key1"].Should().Be("value1");
state.Metadata["key2"].Should().Be("value2");
state.Options.Concurrency.Should().Be(expectedConcurrency);
state.Options.Consistency.Should().Be(expectedConsistency);
var stateJson = state.Value.ToStringUtf8();
var stateFromRequest = JsonSerializer.Deserialize<Widget>(stateJson, client.InnerClient.JsonSerializerOptions);
stateFromRequest.Size.Should().Be(widget.Size);
stateFromRequest.Color.Should().Be(widget.Color);
}
[Fact]
public async Task TrySaveStateAsync_ValidateNonETagErrorThrowsException()
{
var client = new MockClient();
var response = client.CallStateApi<string>()
.Build();
var rpcException = new RpcException(new Status(StatusCode.Internal, "Network Error"));
// Setup the mock client to throw an Rpc Exception with the expected details info
client.Mock
.Setup(m => m.SaveStateAsync(It.IsAny<Autogen.Grpc.v1.SaveStateRequest>(), It.IsAny<CallOptions>()))
.Throws(rpcException);
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await client.DaprClient.TrySaveStateAsync("test", "test", "testValue", "someETag");
});
Assert.Same(rpcException, ex.InnerException);
}
[Fact]
public async Task TrySaveStateAsync_ValidateETagRelatedExceptionReturnsFalse()
{
var client = new MockClient();
var response = client.CallStateApi<string>()
.Build();
var rpcException = new RpcException(new Status(StatusCode.Aborted, $"failed saving state in state store testStore"));
// Setup the mock client to throw an Rpc Exception with the expected details info
client.Mock
.Setup(m => m.SaveStateAsync(It.IsAny<Autogen.Grpc.v1.SaveStateRequest>(), It.IsAny<CallOptions>()))
.Throws(rpcException);
var operationResult = await client.DaprClient.TrySaveStateAsync("testStore", "test", "testValue", "invalidETag");
Assert.False(operationResult);
}
[Fact]
public async Task TrySaveStateAsync_NullEtagThrowsArgumentException()
{
var client = new MockClient();
var response = client.CallStateApi<string>()
.Build();
await FluentActions.Awaiting(async () => await client.DaprClient.TrySaveStateAsync("test", "test", "testValue", null))
.Should().ThrowAsync<ArgumentException>();
}
[Fact]
public async Task TrySaveStateAsync_EmptyEtagDoesNotThrow()
{
var client = new MockClient();
var response = client.CallStateApi<Google.Protobuf.WellKnownTypes.Empty>()
.Build();
// Setup the mock client to return success
client.Mock
.Setup(m => m.SaveStateAsync(It.IsAny<Autogen.Grpc.v1.SaveStateRequest>(), It.IsAny<CallOptions>()))
.Returns(response);
var result = await client.DaprClient.TrySaveStateAsync("test", "test", "testValue", "");
Assert.True(result);
}
[Fact]
public async Task TryDeleteStateAsync_ValidateNonETagErrorThrowsException()
{
var client = new MockClient();
var response = client.CallStateApi<string>()
.Build();
var rpcException = new RpcException(new Status(StatusCode.Internal, "Network Error"));
// Setup the mock client to throw an Rpc Exception with the expected details info
client.Mock
.Setup(m => m.DeleteStateAsync(It.IsAny<Autogen.Grpc.v1.DeleteStateRequest>(), It.IsAny<CallOptions>()))
.Throws(rpcException);
var ex = await Assert.ThrowsAsync<DaprException>(async () =>
{
await client.DaprClient.TryDeleteStateAsync("test", "test", "badEtag");
});
Assert.Same(rpcException, ex.InnerException);
}
[Fact]
public async Task TryDeleteStateAsync_NullEtagThrowsArgumentException()
{
var client = new MockClient();
var response = client.CallStateApi<string>()
.Build();
await FluentActions.Awaiting(async () => await client.DaprClient.TryDeleteStateAsync("test", "test", null))
.Should().ThrowAsync<ArgumentException>();
}
[Fact]
public async Task TryDeleteStateAsync_EmptyEtagDoesNotThrow()
{
var client = new MockClient();
var response = client.CallStateApi<Google.Protobuf.WellKnownTypes.Empty>()
.Build();
// Setup the mock client to return success
client.Mock
.Setup(m => m.DeleteStateAsync(It.IsAny<Autogen.Grpc.v1.DeleteStateRequest>(), It.IsAny<CallOptions>()))
.Returns(response);
var result = await client.DaprClient.TryDeleteStateAsync("test", "test", "");
Assert.True(result);
}
[Fact]
public async Task TryDeleteStateAsync_ValidateETagRelatedExceptionReturnsFalse()
{
var client = new MockClient();
var response = client.CallStateApi<string>()
.Build();
var rpcException = new RpcException(new Status(StatusCode.Aborted, $"failed deleting state with key test"));
// Setup the mock client to throw an Rpc Exception with the expected details info
client.Mock
.Setup(m => m.DeleteStateAsync(It.IsAny<Autogen.Grpc.v1.DeleteStateRequest>(), It.IsAny<CallOptions>()))
.Throws(rpcException);
var operationResult = await client.DaprClient.TryDeleteStateAsync("test", "test", "invalidETag");
Assert.False(operationResult);
}
[Theory]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyLastWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyLastWrite)]
public async Task DeleteStateAsync_ValidateOptions(
ConsistencyMode consistencyMode,
ConcurrencyMode concurrencyMode,
StateConsistency expectedConsistency,
StateConcurrency expectedConcurrency)
{
await using var client = TestClient.CreateForDaprClient();
var stateOptions = new StateOptions
{
Concurrency = concurrencyMode,
Consistency = consistencyMode
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.DeleteStateAsync("testStore", "test", stateOptions);
});
request.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.DeleteStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
envelope.Options.Concurrency.Should().Be(expectedConcurrency);
envelope.Options.Consistency.Should().Be(expectedConsistency);
}
[Theory]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Eventual, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyEventual, StateConcurrency.ConcurrencyLastWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.FirstWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyFirstWrite)]
[InlineData(ConsistencyMode.Strong, ConcurrencyMode.LastWrite, StateConsistency.ConsistencyStrong, StateConcurrency.ConcurrencyLastWrite)]
public async Task TryDeleteStateAsync_ValidateOptions(
ConsistencyMode consistencyMode,
ConcurrencyMode concurrencyMode,
StateConsistency expectedConsistency,
StateConcurrency expectedConcurrency)
{
await using var client = TestClient.CreateForDaprClient();
var stateOptions = new StateOptions
{
Concurrency = concurrencyMode,
Consistency = consistencyMode
};
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
return await daprClient.TryDeleteStateAsync("testStore", "test", "Test_Etag", stateOptions);
});
request.Dismiss();
// Get Request and validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.DeleteStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.Key.Should().Be("test");
envelope.Etag.Value.Should().Be("Test_Etag");
envelope.Options.Concurrency.Should().Be(expectedConcurrency);
envelope.Options.Consistency.Should().Be(expectedConsistency);
}
[Fact]
public async Task DeleteBulkStateAsync_ValidateRequest()
{
await using var client = TestClient.CreateForDaprClient();
var key = "test";
var etag = "etag";
var metadata = new Dictionary<string, string>
{
{ "partitionKey", "mypartition" }
};
var deleteBulkStateItem = new BulkDeleteStateItem(key, etag, null, metadata);
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.DeleteBulkStateAsync("testStore", new List<BulkDeleteStateItem>() { deleteBulkStateItem });
});
request.Dismiss();
// Create Response & Validate
var envelope = await request.GetRequestEnvelopeAsync<Autogenerated.DeleteBulkStateRequest>();
envelope.StoreName.Should().Be("testStore");
envelope.States.Count.Should().Be(1);
envelope.States[0].Key.Should().Be(key);
envelope.States[0].Metadata.Should().ContainKey("partitionKey");
}
private Autogenerated.GetStateResponse MakeGetStateResponse<T>(T state, string etag = null)
{
var data = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web));
var response = new Autogenerated.GetStateResponse
{
Data = data
};
if (etag != null)
{
response.Etag = etag;
}
return response;
}
private Autogenerated.GetBulkStateResponse MakeGetBulkStateResponse<T>(string key, T state)
{
var data = TypeConverters.ToJsonByteString(state, new JsonSerializerOptions(JsonSerializerDefaults.Web));
var response = new Autogenerated.GetBulkStateResponse
{
Items =
{
new Autogenerated.BulkStateItem()
{
Key = key,
Data = data,
}
}
};
return response;
}
private class Widget
{
public string Size { get; set; }
public string Color { get; set; }
}
}
}